Merge branch 'feature/new-ui' of github.com:GetStream/stream-chat-flutter into feat/mention-screen
Conflicts: lib/src/message_list_view.dart
@@ -1,63 +1,63 @@
|
|||||||
include: package:pedantic/analysis_options.yaml
|
#include: package:pedantic/analysis_options.yaml
|
||||||
|
#
|
||||||
analyzer:
|
#analyzer:
|
||||||
enable-experiment:
|
# enable-experiment:
|
||||||
- extension-methods
|
# - extension-methods
|
||||||
exclude:
|
# exclude:
|
||||||
- lib/**/*.g.dart
|
# - lib/**/*.g.dart
|
||||||
- example/**
|
# - example/**
|
||||||
|
#
|
||||||
linter:
|
#linter:
|
||||||
rules:
|
# rules:
|
||||||
# these rules are documented on and in the same order as
|
# # these rules are documented on and in the same order as
|
||||||
# the Dart Lint rules page to make maintenance easier
|
# # the Dart Lint rules page to make maintenance easier
|
||||||
# https://github.com/dart-lang/linter/blob/master/example/all.yaml
|
# # https://github.com/dart-lang/linter/blob/master/example/all.yaml
|
||||||
# - always_declare_return_types
|
# # - always_declare_return_types
|
||||||
# - always_specify_types
|
# # - always_specify_types
|
||||||
# - annotate_overrides
|
# # - annotate_overrides
|
||||||
# - avoid_as
|
# # - avoid_as
|
||||||
- avoid_empty_else
|
# - avoid_empty_else
|
||||||
- avoid_init_to_null
|
# - avoid_init_to_null
|
||||||
- avoid_return_types_on_setters
|
# - avoid_return_types_on_setters
|
||||||
- avoid_web_libraries_in_flutter
|
# - avoid_web_libraries_in_flutter
|
||||||
- await_only_futures
|
# - await_only_futures
|
||||||
- camel_case_types
|
# - camel_case_types
|
||||||
- cancel_subscriptions
|
# - cancel_subscriptions
|
||||||
- close_sinks
|
# - close_sinks
|
||||||
# - comment_references # we do not presume as to what people want to reference in their dartdocs
|
# # - comment_references # we do not presume as to what people want to reference in their dartdocs
|
||||||
# - constant_identifier_names # https://github.com/dart-lang/linter/issues/204
|
# # - constant_identifier_names # https://github.com/dart-lang/linter/issues/204
|
||||||
- control_flow_in_finally
|
# - control_flow_in_finally
|
||||||
- empty_constructor_bodies
|
# - empty_constructor_bodies
|
||||||
- empty_statements
|
# - empty_statements
|
||||||
- hash_and_equals
|
# - hash_and_equals
|
||||||
- implementation_imports
|
# - implementation_imports
|
||||||
# - invariant_booleans
|
# # - invariant_booleans
|
||||||
# - iterable_contains_unrelated_type
|
# # - iterable_contains_unrelated_type
|
||||||
- library_names
|
# - library_names
|
||||||
# - library_prefixes
|
# # - library_prefixes
|
||||||
# - list_remove_unrelated_type
|
# # - list_remove_unrelated_type
|
||||||
# - literal_only_boolean_expressions
|
# # - literal_only_boolean_expressions
|
||||||
- non_constant_identifier_names
|
# - non_constant_identifier_names
|
||||||
# - one_member_abstracts
|
# # - one_member_abstracts
|
||||||
# - only_throw_errors
|
# # - only_throw_errors
|
||||||
# - overridden_fields
|
# # - overridden_fields
|
||||||
# - package_api_docs
|
## - package_api_docs
|
||||||
- package_names
|
# - package_names
|
||||||
- package_prefixed_library_names
|
# - package_prefixed_library_names
|
||||||
- prefer_is_not_empty
|
# - prefer_is_not_empty
|
||||||
# - prefer_mixin # https://github.com/dart-lang/language/issues/32
|
# # - prefer_mixin # https://github.com/dart-lang/language/issues/32
|
||||||
- public_member_api_docs
|
# - public_member_api_docs
|
||||||
- slash_for_doc_comments
|
# - slash_for_doc_comments
|
||||||
# - sort_constructors_first
|
# # - sort_constructors_first
|
||||||
# - sort_unnamed_constructors_first
|
# # - sort_unnamed_constructors_first
|
||||||
# - super_goes_last # no longer needed w/ Dart 2
|
# # - super_goes_last # no longer needed w/ Dart 2
|
||||||
- test_types_in_equals
|
# - test_types_in_equals
|
||||||
- throw_in_finally
|
# - throw_in_finally
|
||||||
# - type_annotate_public_apis # subset of always_specify_types
|
# # - type_annotate_public_apis # subset of always_specify_types
|
||||||
- type_init_formals
|
# - type_init_formals
|
||||||
# - unawaited_futures
|
# # - unawaited_futures
|
||||||
- unnecessary_brace_in_string_interps
|
# - unnecessary_brace_in_string_interps
|
||||||
- unnecessary_getters_setters
|
# - unnecessary_getters_setters
|
||||||
- unnecessary_statements
|
# - unnecessary_statements
|
||||||
- unrelated_type_equality_checks
|
- unrelated_type_equality_checks
|
||||||
- valid_regexps
|
- valid_regexps
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 5.8 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 8.3 KiB |
|
Before Width: | Height: | Size: 6.3 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
@@ -1 +1 @@
|
|||||||
13a41b8138c4868054e44b5158f3bdd6
|
c770358a10272b4ee1cdc2eb432445f6
|
||||||
@@ -265,7 +265,7 @@ SPEC CHECKSUMS:
|
|||||||
FirebaseInstallations: 466c7b4d1f58fe16707693091da253726a731ed2
|
FirebaseInstallations: 466c7b4d1f58fe16707693091da253726a731ed2
|
||||||
FirebaseInstanceID: bd3ffc24367f901a43c063b36c640b345a4a5dd1
|
FirebaseInstanceID: bd3ffc24367f901a43c063b36c640b345a4a5dd1
|
||||||
FirebaseMessaging: 5eca4ef173de76253352511aafef774caa1cba2a
|
FirebaseMessaging: 5eca4ef173de76253352511aafef774caa1cba2a
|
||||||
Flutter: 0e3d915762c693b495b44d77113d4970485de6ec
|
Flutter: 434fef37c0980e73bb6479ef766c45957d4b510c
|
||||||
flutter_apns: ddc629f26016140bf52165040b0a8e8869f9ce32
|
flutter_apns: ddc629f26016140bf52165040b0a8e8869f9ce32
|
||||||
flutter_app_badger: 65de4d6f0c34a891df49e6cfb8a1c0496426fa68
|
flutter_app_badger: 65de4d6f0c34a891df49e6cfb8a1c0496426fa68
|
||||||
flutter_keyboard_visibility: 0339d06371254c3eb25eeb90ba8d17dca8f9c069
|
flutter_keyboard_visibility: 0339d06371254c3eb25eeb90ba8d17dca8f9c069
|
||||||
|
|||||||
@@ -314,7 +314,6 @@
|
|||||||
"${BUILT_PRODUCTS_DIR}/DKImagePickerController/DKImagePickerController.framework",
|
"${BUILT_PRODUCTS_DIR}/DKImagePickerController/DKImagePickerController.framework",
|
||||||
"${BUILT_PRODUCTS_DIR}/DKPhotoGallery/DKPhotoGallery.framework",
|
"${BUILT_PRODUCTS_DIR}/DKPhotoGallery/DKPhotoGallery.framework",
|
||||||
"${BUILT_PRODUCTS_DIR}/FMDB/FMDB.framework",
|
"${BUILT_PRODUCTS_DIR}/FMDB/FMDB.framework",
|
||||||
"${PODS_ROOT}/../Flutter/Flutter.framework",
|
|
||||||
"${BUILT_PRODUCTS_DIR}/GoogleUtilities/GoogleUtilities.framework",
|
"${BUILT_PRODUCTS_DIR}/GoogleUtilities/GoogleUtilities.framework",
|
||||||
"${BUILT_PRODUCTS_DIR}/PromisesObjC/FBLPromises.framework",
|
"${BUILT_PRODUCTS_DIR}/PromisesObjC/FBLPromises.framework",
|
||||||
"${BUILT_PRODUCTS_DIR}/Protobuf/Protobuf.framework",
|
"${BUILT_PRODUCTS_DIR}/Protobuf/Protobuf.framework",
|
||||||
@@ -348,7 +347,6 @@
|
|||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DKImagePickerController.framework",
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DKImagePickerController.framework",
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DKPhotoGallery.framework",
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DKPhotoGallery.framework",
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FMDB.framework",
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FMDB.framework",
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework",
|
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleUtilities.framework",
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleUtilities.framework",
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FBLPromises.framework",
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FBLPromises.framework",
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Protobuf.framework",
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Protobuf.framework",
|
||||||
|
|||||||
@@ -58,8 +58,6 @@
|
|||||||
<array>
|
<array>
|
||||||
<string>remote-notification</string>
|
<string>remote-notification</string>
|
||||||
</array>
|
</array>
|
||||||
<key>UIUserInterfaceStyle</key>
|
|
||||||
<string>Light</string>
|
|
||||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||||
<true/>
|
<true/>
|
||||||
</dict>
|
</dict>
|
||||||
|
|||||||
@@ -5,27 +5,39 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
<testcase classname="fastlane.lanes" name="0: Verifying fastlane version" time="0.00094">
|
<testcase classname="fastlane.lanes" name="0: Verifying fastlane version" time="0.00047">
|
||||||
|
|
||||||
</testcase>
|
</testcase>
|
||||||
|
|
||||||
|
|
||||||
<testcase classname="fastlane.lanes" name="1: default_platform" time="0.000521">
|
<testcase classname="fastlane.lanes" name="1: default_platform" time="0.000176">
|
||||||
|
|
||||||
</testcase>
|
</testcase>
|
||||||
|
|
||||||
|
|
||||||
<testcase classname="fastlane.lanes" name="2: is_ci" time="0.000605">
|
<testcase classname="fastlane.lanes" name="2: is_ci" time="0.000168">
|
||||||
|
|
||||||
</testcase>
|
</testcase>
|
||||||
|
|
||||||
|
|
||||||
<testcase classname="fastlane.lanes" name="3: is_ci" time="0.000675">
|
<testcase classname="fastlane.lanes" name="3: Switch to ios match_me lane" time="0.000157">
|
||||||
|
|
||||||
</testcase>
|
</testcase>
|
||||||
|
|
||||||
|
|
||||||
<testcase classname="fastlane.lanes" name="4: match" time="48.727951">
|
<testcase classname="fastlane.lanes" name="4: is_ci" time="0.000161">
|
||||||
|
|
||||||
|
</testcase>
|
||||||
|
|
||||||
|
|
||||||
|
<testcase classname="fastlane.lanes" name="5: match" time="32.299713">
|
||||||
|
|
||||||
|
</testcase>
|
||||||
|
|
||||||
|
|
||||||
|
<testcase classname="fastlane.lanes" name="6: gym" time="9.737819">
|
||||||
|
|
||||||
|
<failure message="/Library/Ruby/Gems/2.6.0/gems/fastlane-2.162.0/fastlane/lib/fastlane/actions/actions_helper.rb:67:in `execute_action' /Library/Ruby/Gems/2.6.0/gems/fastlane-2.162.0/fastlane/lib/fastlane/runner.rb:255:in `block in execute_action' /Library/Ruby/Gems/2.6.0/gems/fastlane-2.162.0/fastlane/lib/fastlane/runner.rb:229:in `chdir' /Library/Ruby/Gems/2.6.0/gems/fastlane-2.162.0/fastlane/lib/fastlane/runner.rb:229:in `execute_action' /Library/Ruby/Gems/2.6.0/gems/fastlane-2.162.0/fastlane/lib/fastlane/runner.rb:157:in `trigger_action_by_name' /Library/Ruby/Gems/2.6.0/gems/fastlane-2.162.0/fastlane/lib/fastlane/fast_file.rb:159:in `method_missing' Fastfile:28:in `block (2 levels) in parsing_binding' /Library/Ruby/Gems/2.6.0/gems/fastlane-2.162.0/fastlane/lib/fastlane/lane.rb:33:in `call' /Library/Ruby/Gems/2.6.0/gems/fastlane-2.162.0/fastlane/lib/fastlane/runner.rb:49:in `block in execute' /Library/Ruby/Gems/2.6.0/gems/fastlane-2.162.0/fastlane/lib/fastlane/runner.rb:45:in `chdir' /Library/Ruby/Gems/2.6.0/gems/fastlane-2.162.0/fastlane/lib/fastlane/runner.rb:45:in `execute' /Library/Ruby/Gems/2.6.0/gems/fastlane-2.162.0/fastlane/lib/fastlane/lane_manager.rb:47:in `cruise_lane' /Library/Ruby/Gems/2.6.0/gems/fastlane-2.162.0/fastlane/lib/fastlane/command_line_handler.rb:36:in `handle' /Library/Ruby/Gems/2.6.0/gems/fastlane-2.162.0/fastlane/lib/fastlane/commands_generator.rb:108:in `block (2 levels) in run' /Library/Ruby/Gems/2.6.0/gems/commander-fastlane-4.4.6/lib/commander/command.rb:178:in `call' /Library/Ruby/Gems/2.6.0/gems/commander-fastlane-4.4.6/lib/commander/command.rb:153:in `run' /Library/Ruby/Gems/2.6.0/gems/commander-fastlane-4.4.6/lib/commander/runner.rb:476:in `run_active_command' /Library/Ruby/Gems/2.6.0/gems/fastlane-2.162.0/fastlane_core/lib/fastlane_core/ui/fastlane_runner.rb:76:in `run!' /Library/Ruby/Gems/2.6.0/gems/commander-fastlane-4.4.6/lib/commander/delegates.rb:15:in `run!' /Library/Ruby/Gems/2.6.0/gems/fastlane-2.162.0/fastlane/lib/fastlane/commands_generator.rb:352:in `run' /Library/Ruby/Gems/2.6.0/gems/fastlane-2.162.0/fastlane/lib/fastlane/commands_generator.rb:41:in `start' /Library/Ruby/Gems/2.6.0/gems/fastlane-2.162.0/fastlane/lib/fastlane/cli_tools_distributor.rb:119:in `take_off' /Library/Ruby/Gems/2.6.0/gems/fastlane-2.162.0/bin/fastlane:23:in `<top (required)>' /usr/local/bin/fastlane:23:in `load' /usr/local/bin/fastlane:23:in `<top (required)>' /Library/Ruby/Gems/2.6.0/gems/bundler-2.0.2/lib/bundler/cli/exec.rb:74:in `load' /Library/Ruby/Gems/2.6.0/gems/bundler-2.0.2/lib/bundler/cli/exec.rb:74:in `kernel_load' /Library/Ruby/Gems/2.6.0/gems/bundler-2.0.2/lib/bundler/cli/exec.rb:28:in `run' /Library/Ruby/Gems/2.6.0/gems/bundler-2.0.2/lib/bundler/cli.rb:465:in `exec' /Library/Ruby/Gems/2.6.0/gems/bundler-2.0.2/lib/bundler/vendor/thor/lib/thor/command.rb:27:in `run' /Library/Ruby/Gems/2.6.0/gems/bundler-2.0.2/lib/bundler/vendor/thor/lib/thor/invocation.rb:126:in `invoke_command' /Library/Ruby/Gems/2.6.0/gems/bundler-2.0.2/lib/bundler/vendor/thor/lib/thor.rb:387:in `dispatch' /Library/Ruby/Gems/2.6.0/gems/bundler-2.0.2/lib/bundler/cli.rb:27:in `dispatch' /Library/Ruby/Gems/2.6.0/gems/bundler-2.0.2/lib/bundler/vendor/thor/lib/thor/base.rb:466:in `start' /Library/Ruby/Gems/2.6.0/gems/bundler-2.0.2/lib/bundler/cli.rb:18:in `start' /Library/Ruby/Gems/2.6.0/gems/bundler-2.0.2/exe/bundle:30:in `block in <top (required)>' /Library/Ruby/Gems/2.6.0/gems/bundler-2.0.2/lib/bundler/friendly_errors.rb:124:in `with_friendly_errors' /Library/Ruby/Gems/2.6.0/gems/bundler-2.0.2/exe/bundle:22:in `<top (required)>' /usr/local/bin/bundle:23:in `load' /usr/local/bin/bundle:23:in `<main>' Error building the application - see the log above" />
|
||||||
|
|
||||||
</testcase>
|
</testcase>
|
||||||
|
|
||||||
|
|||||||
@@ -3,12 +3,12 @@ import 'dart:io';
|
|||||||
import 'package:example/stream_version.dart';
|
import 'package:example/stream_version.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||||
|
|
||||||
|
import 'choose_user_page.dart';
|
||||||
import 'main.dart';
|
import 'main.dart';
|
||||||
import 'notifications_service.dart';
|
import 'notifications_service.dart';
|
||||||
import 'choose_user_page.dart';
|
|
||||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
|
||||||
|
|
||||||
class AdvancedOptionsPage extends StatefulWidget {
|
class AdvancedOptionsPage extends StatefulWidget {
|
||||||
@override
|
@override
|
||||||
@@ -34,20 +34,23 @@ class _AdvancedOptionsPageState extends State<AdvancedOptionsPage> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
|
backgroundColor: StreamChatTheme.of(context).colorTheme.whiteSnow,
|
||||||
resizeToAvoidBottomPadding: false,
|
resizeToAvoidBottomPadding: false,
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
backgroundColor: StreamChatTheme.of(context).primaryColor,
|
backgroundColor: StreamChatTheme.of(context).colorTheme.white,
|
||||||
elevation: 1,
|
elevation: 1,
|
||||||
centerTitle: true,
|
centerTitle: true,
|
||||||
|
brightness: Theme.of(context).brightness,
|
||||||
title: Text(
|
title: Text(
|
||||||
'Advanced Options',
|
'Advanced Options',
|
||||||
style: Theme.of(context).textTheme.subtitle1.copyWith(
|
style: StreamChatTheme.of(context)
|
||||||
fontWeight: FontWeight.bold,
|
.textTheme
|
||||||
),
|
.headlineBold
|
||||||
|
.copyWith(color: StreamChatTheme.of(context).colorTheme.black),
|
||||||
),
|
),
|
||||||
leading: IconButton(
|
leading: IconButton(
|
||||||
icon: StreamSvgIcon.left(
|
icon: StreamSvgIcon.left(
|
||||||
color: Colors.black,
|
color: StreamChatTheme.of(context).colorTheme.black,
|
||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
@@ -57,15 +60,11 @@ class _AdvancedOptionsPageState extends State<AdvancedOptionsPage> {
|
|||||||
body: Builder(
|
body: Builder(
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(
|
padding: const EdgeInsets.fromLTRB(16, 16, 16, 0),
|
||||||
16,
|
|
||||||
16,
|
|
||||||
16,
|
|
||||||
0,
|
|
||||||
),
|
|
||||||
child: Form(
|
child: Form(
|
||||||
key: _formKey,
|
key: _formKey,
|
||||||
child: Column(
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
TextFormField(
|
TextFormField(
|
||||||
controller: _apiKeyController,
|
controller: _apiKeyController,
|
||||||
@@ -87,31 +86,31 @@ class _AdvancedOptionsPageState extends State<AdvancedOptionsPage> {
|
|||||||
},
|
},
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
color: Colors.black,
|
color: StreamChatTheme.of(context).colorTheme.black,
|
||||||
),
|
),
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
errorStyle: TextStyle(fontSize: 0),
|
errorStyle: TextStyle(height: 0, fontSize: 0),
|
||||||
labelStyle: TextStyle(
|
labelStyle: TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: _apiKeyError != null
|
color: _apiKeyError != null
|
||||||
? Color(0xffff3742)
|
? StreamChatTheme.of(context).colorTheme.accentRed
|
||||||
: Colors.black.withOpacity(.5),
|
: StreamChatTheme.of(context).colorTheme.grey,
|
||||||
),
|
),
|
||||||
border: UnderlineInputBorder(
|
border: UnderlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
borderSide: BorderSide.none,
|
borderSide: BorderSide.none,
|
||||||
),
|
),
|
||||||
fillColor: Color(0xffF5F5F5),
|
fillColor:
|
||||||
|
StreamChatTheme.of(context).colorTheme.whiteSmoke,
|
||||||
filled: true,
|
filled: true,
|
||||||
labelText:
|
labelText:
|
||||||
'Chat API Key ${_apiKeyError != null ? ': $_apiKeyError' : ''}',
|
'Chat API Key ${_apiKeyError != null ? ': $_apiKeyError' : ''}',
|
||||||
),
|
),
|
||||||
textInputAction: TextInputAction.next,
|
textInputAction: TextInputAction.next,
|
||||||
),
|
),
|
||||||
Padding(
|
SizedBox(height: 8),
|
||||||
padding: const EdgeInsets.only(top: 8.0),
|
TextFormField(
|
||||||
child: TextFormField(
|
|
||||||
controller: _userIdController,
|
controller: _userIdController,
|
||||||
onChanged: (_) {
|
onChanged: (_) {
|
||||||
if (_userIdError != null) {
|
if (_userIdError != null) {
|
||||||
@@ -131,32 +130,31 @@ class _AdvancedOptionsPageState extends State<AdvancedOptionsPage> {
|
|||||||
},
|
},
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
color: Colors.black,
|
color: StreamChatTheme.of(context).colorTheme.black,
|
||||||
),
|
),
|
||||||
textInputAction: TextInputAction.next,
|
textInputAction: TextInputAction.next,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
errorStyle: TextStyle(fontSize: 0),
|
errorStyle: TextStyle(height: 0, fontSize: 0),
|
||||||
labelStyle: TextStyle(
|
labelStyle: TextStyle(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
color: _userIdError != null
|
color: _userIdError != null
|
||||||
? Color(0xffff3742)
|
? StreamChatTheme.of(context).colorTheme.accentRed
|
||||||
: Colors.black.withOpacity(.5),
|
: StreamChatTheme.of(context).colorTheme.grey,
|
||||||
),
|
),
|
||||||
border: UnderlineInputBorder(
|
border: UnderlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
borderSide: BorderSide.none,
|
borderSide: BorderSide.none,
|
||||||
),
|
),
|
||||||
fillColor: Color(0xffF5F5F5),
|
fillColor:
|
||||||
|
StreamChatTheme.of(context).colorTheme.whiteSmoke,
|
||||||
filled: true,
|
filled: true,
|
||||||
labelText:
|
labelText:
|
||||||
'User ID ${_userIdError != null ? ': $_userIdError' : ''}',
|
'User ID ${_userIdError != null ? ': $_userIdError' : ''}',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
SizedBox(height: 8),
|
||||||
Padding(
|
TextFormField(
|
||||||
padding: const EdgeInsets.only(top: 8.0),
|
|
||||||
child: TextFormField(
|
|
||||||
onChanged: (_) {
|
onChanged: (_) {
|
||||||
if (_userTokenError != null) {
|
if (_userTokenError != null) {
|
||||||
setState(() {
|
setState(() {
|
||||||
@@ -176,67 +174,53 @@ class _AdvancedOptionsPageState extends State<AdvancedOptionsPage> {
|
|||||||
},
|
},
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
color: Colors.black,
|
color: StreamChatTheme.of(context).colorTheme.black,
|
||||||
),
|
),
|
||||||
textInputAction: TextInputAction.next,
|
textInputAction: TextInputAction.next,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
errorStyle: TextStyle(fontSize: 0),
|
errorStyle: TextStyle(height: 0, fontSize: 0),
|
||||||
labelStyle: TextStyle(
|
labelStyle: TextStyle(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
color: _userTokenError != null
|
color: _userTokenError != null
|
||||||
? Color(0xffff3742)
|
? StreamChatTheme.of(context).colorTheme.accentRed
|
||||||
: Colors.black.withOpacity(.5),
|
: StreamChatTheme.of(context).colorTheme.grey,
|
||||||
),
|
),
|
||||||
border: UnderlineInputBorder(
|
border: UnderlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
borderSide: BorderSide.none,
|
borderSide: BorderSide.none,
|
||||||
),
|
),
|
||||||
fillColor: Color(0xffF5F5F5),
|
fillColor:
|
||||||
|
StreamChatTheme.of(context).colorTheme.whiteSmoke,
|
||||||
filled: true,
|
filled: true,
|
||||||
labelText:
|
labelText:
|
||||||
'User Token ${_userTokenError != null ? ': $_userTokenError' : ''}',
|
'User Token ${_userTokenError != null ? ': $_userTokenError' : ''}',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
SizedBox(height: 8),
|
||||||
Padding(
|
TextFormField(
|
||||||
padding: const EdgeInsets.only(top: 8.0),
|
|
||||||
child: TextFormField(
|
|
||||||
controller: _usernameController,
|
controller: _usernameController,
|
||||||
textInputAction: TextInputAction.done,
|
textInputAction: TextInputAction.done,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelStyle: TextStyle(
|
labelStyle: TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: Colors.black.withOpacity(.5),
|
color: StreamChatTheme.of(context).colorTheme.grey,
|
||||||
),
|
),
|
||||||
border: UnderlineInputBorder(
|
border: UnderlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
borderSide: BorderSide.none,
|
borderSide: BorderSide.none,
|
||||||
),
|
),
|
||||||
fillColor: Color(0xffF5F5F5),
|
fillColor:
|
||||||
|
StreamChatTheme.of(context).colorTheme.whiteSmoke,
|
||||||
filled: true,
|
filled: true,
|
||||||
labelText: 'Username (optional)',
|
labelText: 'Username (optional)',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
Spacer(),
|
||||||
Expanded(
|
RaisedButton(
|
||||||
child: Align(
|
elevation: 0,
|
||||||
alignment: Alignment.bottomCenter,
|
child: Text('Login', style: TextStyle(fontSize: 16)),
|
||||||
child: FlatButton(
|
|
||||||
color: StreamChatTheme.of(context).accentColor,
|
|
||||||
minWidth: double.infinity,
|
|
||||||
height: 48,
|
|
||||||
child: Text(
|
|
||||||
'Login',
|
|
||||||
style: TextStyle(
|
|
||||||
color: Colors.white,
|
|
||||||
fontSize: 16,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(26),
|
|
||||||
),
|
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return;
|
return;
|
||||||
@@ -251,11 +235,15 @@ class _AdvancedOptionsPageState extends State<AdvancedOptionsPage> {
|
|||||||
showDialog(
|
showDialog(
|
||||||
barrierDismissible: false,
|
barrierDismissible: false,
|
||||||
context: context,
|
context: context,
|
||||||
|
barrierColor:
|
||||||
|
StreamChatTheme.of(context).colorTheme.overlay,
|
||||||
builder: (context) => Center(
|
builder: (context) => Center(
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
color: Colors.white,
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.white,
|
||||||
),
|
),
|
||||||
height: 100,
|
height: 100,
|
||||||
width: 100,
|
width: 100,
|
||||||
@@ -269,8 +257,7 @@ class _AdvancedOptionsPageState extends State<AdvancedOptionsPage> {
|
|||||||
final client = Client(
|
final client = Client(
|
||||||
apiKey,
|
apiKey,
|
||||||
logLevel: Level.INFO,
|
logLevel: Level.INFO,
|
||||||
showLocalNotification:
|
showLocalNotification: (!kIsWeb && Platform.isAndroid)
|
||||||
(!kIsWeb && Platform.isAndroid)
|
|
||||||
? showLocalNotification
|
? showLocalNotification
|
||||||
: null,
|
: null,
|
||||||
persistenceEnabled: true,
|
persistenceEnabled: true,
|
||||||
@@ -328,8 +315,7 @@ class _AdvancedOptionsPageState extends State<AdvancedOptionsPage> {
|
|||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
theme: ThemeData.light(),
|
theme: ThemeData.light(),
|
||||||
darkTheme: ThemeData.dark(),
|
darkTheme: ThemeData.dark(),
|
||||||
//TODO change to system once dark theme is implemented
|
themeMode: ThemeMode.system,
|
||||||
themeMode: ThemeMode.light,
|
|
||||||
builder: (context, widget) {
|
builder: (context, widget) {
|
||||||
return StreamChat(
|
return StreamChat(
|
||||||
child: widget,
|
child: widget,
|
||||||
@@ -345,8 +331,6 @@ class _AdvancedOptionsPageState extends State<AdvancedOptionsPage> {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
|
||||||
),
|
|
||||||
StreamVersion(),
|
StreamVersion(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -3,7 +3,10 @@ import 'package:flutter/cupertino.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:jiffy/jiffy.dart';
|
import 'package:jiffy/jiffy.dart';
|
||||||
|
|
||||||
import '../stream_chat_flutter.dart';
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||||
|
|
||||||
|
import 'main.dart';
|
||||||
|
import 'routes/routes.dart';
|
||||||
|
|
||||||
/// Detail screen for a 1:1 chat correspondence
|
/// Detail screen for a 1:1 chat correspondence
|
||||||
class ChatInfoScreen extends StatefulWidget {
|
class ChatInfoScreen extends StatefulWidget {
|
||||||
@@ -21,16 +24,18 @@ class _ChatInfoScreenState extends State<ChatInfoScreen> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final channel = StreamChannel.of(context).channel;
|
final channel = StreamChannel.of(context).channel;
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: Color(0xFFe6e6e6),
|
backgroundColor: StreamChatTheme.of(context).colorTheme.whiteSnow,
|
||||||
body: ListView(
|
body: ListView(
|
||||||
children: [
|
children: [
|
||||||
_buildUserHeader(),
|
_buildUserHeader(),
|
||||||
SizedBox(
|
Container(
|
||||||
height: 8.0,
|
height: 8.0,
|
||||||
|
color: StreamChatTheme.of(context).colorTheme.greyGainsboro,
|
||||||
),
|
),
|
||||||
_buildOptionListTiles(),
|
_buildOptionListTiles(),
|
||||||
SizedBox(
|
Container(
|
||||||
height: 8.0,
|
height: 8.0,
|
||||||
|
color: StreamChatTheme.of(context).colorTheme.greyGainsboro,
|
||||||
),
|
),
|
||||||
if ([
|
if ([
|
||||||
'admin',
|
'admin',
|
||||||
@@ -47,7 +52,7 @@ class _ChatInfoScreenState extends State<ChatInfoScreen> {
|
|||||||
|
|
||||||
Widget _buildUserHeader() {
|
Widget _buildUserHeader() {
|
||||||
return Material(
|
return Material(
|
||||||
color: Colors.white,
|
color: StreamChatTheme.of(context).colorTheme.whiteSnow,
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
@@ -74,14 +79,19 @@ class _ChatInfoScreenState extends State<ChatInfoScreen> {
|
|||||||
SizedBox(height: 7.0),
|
SizedBox(height: 7.0),
|
||||||
_buildConnectedTitleState(),
|
_buildConnectedTitleState(),
|
||||||
SizedBox(height: 15.0),
|
SizedBox(height: 15.0),
|
||||||
_OptionListTile(
|
OptionListTile(
|
||||||
title: '@${widget.user.id}',
|
title: '@${widget.user.id}',
|
||||||
|
tileColor: StreamChatTheme.of(context).colorTheme.whiteSnow,
|
||||||
trailing: Padding(
|
trailing: Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||||
child: Text(
|
child: Text(
|
||||||
widget.user.name,
|
widget.user.name,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.black.withOpacity(0.5), fontSize: 16.0),
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.black
|
||||||
|
.withOpacity(0.5),
|
||||||
|
fontSize: 16.0),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onTap: () {},
|
onTap: () {},
|
||||||
@@ -92,7 +102,9 @@ class _ChatInfoScreenState extends State<ChatInfoScreen> {
|
|||||||
top: 21,
|
top: 21,
|
||||||
left: 16,
|
left: 16,
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
child: StreamSvgIcon.left(),
|
child: StreamSvgIcon.left(
|
||||||
|
color: StreamChatTheme.of(context).colorTheme.black,
|
||||||
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
@@ -113,7 +125,7 @@ class _ChatInfoScreenState extends State<ChatInfoScreen> {
|
|||||||
// title: 'Notifications',
|
// title: 'Notifications',
|
||||||
// leading: StreamSvgIcon.Icon_notification(
|
// leading: StreamSvgIcon.Icon_notification(
|
||||||
// size: 24.0,
|
// size: 24.0,
|
||||||
// color: Colors.black.withOpacity(0.5),
|
// color: StreamChatTheme.of(context).colorTheme.black.withOpacity(0.5),
|
||||||
// ),
|
// ),
|
||||||
// trailing: CupertinoSwitch(
|
// trailing: CupertinoSwitch(
|
||||||
// value: true,
|
// value: true,
|
||||||
@@ -124,11 +136,19 @@ class _ChatInfoScreenState extends State<ChatInfoScreen> {
|
|||||||
StreamBuilder<bool>(
|
StreamBuilder<bool>(
|
||||||
stream: StreamChannel.of(context).channel.isMutedStream,
|
stream: StreamChannel.of(context).channel.isMutedStream,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
return _OptionListTile(
|
return OptionListTile(
|
||||||
|
tileColor: StreamChatTheme.of(context).colorTheme.whiteSnow,
|
||||||
title: 'Mute user',
|
title: 'Mute user',
|
||||||
leading: StreamSvgIcon.mute(
|
titleTextStyle: StreamChatTheme.of(context).textTheme.body,
|
||||||
size: 23.0,
|
leading: Padding(
|
||||||
color: Colors.black.withOpacity(0.5),
|
padding: const EdgeInsets.symmetric(horizontal: 22.0),
|
||||||
|
child: StreamSvgIcon.mute(
|
||||||
|
size: 24.0,
|
||||||
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.black
|
||||||
|
.withOpacity(0.5),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
trailing: snapshot.data == null
|
trailing: snapshot.data == null
|
||||||
? CircularProgressIndicator()
|
? CircularProgressIndicator()
|
||||||
@@ -149,7 +169,7 @@ class _ChatInfoScreenState extends State<ChatInfoScreen> {
|
|||||||
// title: 'Block User',
|
// title: 'Block User',
|
||||||
// leading: StreamSvgIcon.Icon_user_delete(
|
// leading: StreamSvgIcon.Icon_user_delete(
|
||||||
// size: 24.0,
|
// size: 24.0,
|
||||||
// color: Colors.black.withOpacity(0.5),
|
// color: StreamChatTheme.of(context).colorTheme.black.withOpacity(0.5),
|
||||||
// ),
|
// ),
|
||||||
// trailing: CupertinoSwitch(
|
// trailing: CupertinoSwitch(
|
||||||
// value: widget.user.banned,
|
// value: widget.user.banned,
|
||||||
@@ -163,37 +183,118 @@ class _ChatInfoScreenState extends State<ChatInfoScreen> {
|
|||||||
// ),
|
// ),
|
||||||
// onTap: () {},
|
// onTap: () {},
|
||||||
// ),
|
// ),
|
||||||
_OptionListTile(
|
OptionListTile(
|
||||||
title: 'Photos & Videos',
|
title: 'Photos & Videos',
|
||||||
leading: StreamSvgIcon.pictures(
|
tileColor: StreamChatTheme.of(context).colorTheme.whiteSnow,
|
||||||
size: 32.0,
|
titleTextStyle: StreamChatTheme.of(context).textTheme.body,
|
||||||
color: Colors.black.withOpacity(0.5),
|
leading: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||||
|
child: StreamSvgIcon.pictures(
|
||||||
|
size: 36.0,
|
||||||
|
color:
|
||||||
|
StreamChatTheme.of(context).colorTheme.black.withOpacity(0.5),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
trailing: StreamSvgIcon.right(
|
||||||
|
color: StreamChatTheme.of(context).colorTheme.grey,
|
||||||
),
|
),
|
||||||
trailing: StreamSvgIcon.right(),
|
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.push(context,
|
final channel = StreamChannel.of(context).channel;
|
||||||
MaterialPageRoute(builder: (context) => _MediaDisplayScreen()));
|
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => StreamChannel(
|
||||||
|
channel: channel,
|
||||||
|
child: MessageSearchBloc(
|
||||||
|
child: ChannelMediaDisplayScreen(
|
||||||
|
sortOptions: [
|
||||||
|
SortOption(
|
||||||
|
'created_at',
|
||||||
|
direction: SortOption.ASC,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
paginationParams: PaginationParams(limit: 20),
|
||||||
|
onShowMessage: (m, c) async {
|
||||||
|
final client = StreamChat.of(context).client;
|
||||||
|
final message = m;
|
||||||
|
final channel = client.channel(
|
||||||
|
c.type,
|
||||||
|
id: c.id,
|
||||||
|
);
|
||||||
|
if (channel.state == null) {
|
||||||
|
await channel.watch();
|
||||||
|
}
|
||||||
|
Navigator.pushNamed(
|
||||||
|
context,
|
||||||
|
Routes.CHANNEL_PAGE,
|
||||||
|
arguments: ChannelPageArgs(
|
||||||
|
channel: channel,
|
||||||
|
initialMessage: message,
|
||||||
|
),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
_OptionListTile(
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
OptionListTile(
|
||||||
title: 'Files',
|
title: 'Files',
|
||||||
leading: StreamSvgIcon.files(
|
tileColor: StreamChatTheme.of(context).colorTheme.whiteSnow,
|
||||||
|
titleTextStyle: StreamChatTheme.of(context).textTheme.body,
|
||||||
|
leading: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 18.0),
|
||||||
|
child: StreamSvgIcon.files(
|
||||||
size: 32.0,
|
size: 32.0,
|
||||||
color: Colors.black.withOpacity(0.5),
|
color:
|
||||||
|
StreamChatTheme.of(context).colorTheme.black.withOpacity(0.5),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
trailing: StreamSvgIcon.right(
|
||||||
|
color: StreamChatTheme.of(context).colorTheme.grey,
|
||||||
),
|
),
|
||||||
trailing: StreamSvgIcon.right(),
|
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.push(context,
|
final channel = StreamChannel.of(context).channel;
|
||||||
MaterialPageRoute(builder: (context) => _FileDisplayScreen()));
|
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => StreamChannel(
|
||||||
|
channel: channel,
|
||||||
|
child: MessageSearchBloc(
|
||||||
|
child: ChannelFileDisplayScreen(
|
||||||
|
sortOptions: [
|
||||||
|
SortOption(
|
||||||
|
'created_at',
|
||||||
|
direction: SortOption.ASC,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
paginationParams: PaginationParams(limit: 20),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
_OptionListTile(
|
OptionListTile(
|
||||||
title: 'Shared groups',
|
title: 'Shared groups',
|
||||||
leading: StreamSvgIcon.Icon_group(
|
tileColor: StreamChatTheme.of(context).colorTheme.whiteSnow,
|
||||||
|
titleTextStyle: StreamChatTheme.of(context).textTheme.body,
|
||||||
|
leading: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 22.0),
|
||||||
|
child: StreamSvgIcon.Icon_group(
|
||||||
size: 24.0,
|
size: 24.0,
|
||||||
color: Colors.black.withOpacity(0.5),
|
color:
|
||||||
|
StreamChatTheme.of(context).colorTheme.black.withOpacity(0.5),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
trailing: StreamSvgIcon.right(
|
||||||
|
color: StreamChatTheme.of(context).colorTheme.grey,
|
||||||
),
|
),
|
||||||
trailing: StreamSvgIcon.right(),
|
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
@@ -207,16 +308,23 @@ class _ChatInfoScreenState extends State<ChatInfoScreen> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildDeleteListTile() {
|
Widget _buildDeleteListTile() {
|
||||||
return _OptionListTile(
|
return OptionListTile(
|
||||||
title: 'Delete',
|
title: 'Delete Conversation',
|
||||||
leading: StreamSvgIcon.delete(
|
tileColor: StreamChatTheme.of(context).colorTheme.whiteSnow,
|
||||||
color: Colors.red,
|
titleTextStyle: StreamChatTheme.of(context).textTheme.body.copyWith(
|
||||||
|
color: StreamChatTheme.of(context).colorTheme.accentRed,
|
||||||
|
),
|
||||||
|
leading: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 22.0),
|
||||||
|
child: StreamSvgIcon.delete(
|
||||||
|
color: StreamChatTheme.of(context).colorTheme.accentRed,
|
||||||
size: 24.0,
|
size: 24.0,
|
||||||
),
|
),
|
||||||
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
_showDeleteDialog();
|
_showDeleteDialog();
|
||||||
},
|
},
|
||||||
titleColor: Colors.red,
|
titleColor: StreamChatTheme.of(context).colorTheme.accentRed,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -228,7 +336,7 @@ class _ChatInfoScreenState extends State<ChatInfoScreen> {
|
|||||||
question: 'Are you sure you want to delete this conversation?',
|
question: 'Are you sure you want to delete this conversation?',
|
||||||
cancelText: 'CANCEL',
|
cancelText: 'CANCEL',
|
||||||
icon: StreamSvgIcon.delete(
|
icon: StreamSvgIcon.delete(
|
||||||
color: Colors.red,
|
color: StreamChatTheme.of(context).colorTheme.accentRed,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
var channel = StreamChannel.of(context).channel;
|
var channel = StreamChannel.of(context).channel;
|
||||||
@@ -248,12 +356,20 @@ class _ChatInfoScreenState extends State<ChatInfoScreen> {
|
|||||||
if (otherMember.online) {
|
if (otherMember.online) {
|
||||||
alternativeWidget = Text(
|
alternativeWidget = Text(
|
||||||
'Online',
|
'Online',
|
||||||
style: TextStyle(color: Colors.black.withOpacity(0.5)),
|
style: TextStyle(
|
||||||
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.black
|
||||||
|
.withOpacity(0.5)),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
alternativeWidget = Text(
|
alternativeWidget = Text(
|
||||||
'Last seen ${Jiffy(otherMember.lastActive).fromNow()}',
|
'Last seen ${Jiffy(otherMember.lastActive).fromNow()}',
|
||||||
style: TextStyle(color: Colors.black.withOpacity(0.5)),
|
style: TextStyle(
|
||||||
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.black
|
||||||
|
.withOpacity(0.5)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -272,10 +388,10 @@ class _ChatInfoScreenState extends State<ChatInfoScreen> {
|
|||||||
),
|
),
|
||||||
child: Material(
|
child: Material(
|
||||||
shape: CircleBorder(),
|
shape: CircleBorder(),
|
||||||
color: Color(0xff20E070),
|
color: StreamChatTheme.of(context).colorTheme.accentGreen,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
color: Colors.white,
|
color: StreamChatTheme.of(context).colorTheme.white,
|
||||||
),
|
),
|
||||||
alternativeWidget,
|
alternativeWidget,
|
||||||
],
|
],
|
||||||
@@ -283,72 +399,6 @@ class _ChatInfoScreenState extends State<ChatInfoScreen> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _OptionListTile extends StatelessWidget {
|
|
||||||
final String title;
|
|
||||||
final StreamSvgIcon leading;
|
|
||||||
final Widget trailing;
|
|
||||||
final VoidCallback onTap;
|
|
||||||
final Color titleColor;
|
|
||||||
|
|
||||||
_OptionListTile({
|
|
||||||
this.title,
|
|
||||||
this.leading,
|
|
||||||
this.trailing,
|
|
||||||
this.onTap,
|
|
||||||
this.titleColor,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Column(
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
color: Color(0xffe6e6e6),
|
|
||||||
height: 2.0,
|
|
||||||
),
|
|
||||||
Material(
|
|
||||||
color: Colors.white,
|
|
||||||
child: Container(
|
|
||||||
height: 56.0,
|
|
||||||
child: InkWell(
|
|
||||||
onTap: onTap,
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
if (leading != null)
|
|
||||||
Expanded(
|
|
||||||
child: Center(child: leading),
|
|
||||||
),
|
|
||||||
if (leading == null)
|
|
||||||
SizedBox(
|
|
||||||
width: 16.0,
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
flex: 4,
|
|
||||||
child: Text(
|
|
||||||
title,
|
|
||||||
style: TextStyle(
|
|
||||||
fontWeight: FontWeight.w600, color: titleColor),
|
|
||||||
)),
|
|
||||||
Expanded(
|
|
||||||
flex: 2,
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.only(right: 16.0),
|
|
||||||
child: Align(
|
|
||||||
alignment: Alignment.centerRight,
|
|
||||||
child: trailing ?? Container(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _SharedGroupsScreen extends StatefulWidget {
|
class _SharedGroupsScreen extends StatefulWidget {
|
||||||
final User mainUser;
|
final User mainUser;
|
||||||
final User otherUser;
|
final User otherUser;
|
||||||
@@ -365,14 +415,16 @@ class __SharedGroupsScreenState extends State<_SharedGroupsScreen> {
|
|||||||
var chat = StreamChat.of(context);
|
var chat = StreamChat.of(context);
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: Colors.white,
|
backgroundColor: StreamChatTheme.of(context).colorTheme.whiteSnow,
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
brightness: Theme.of(context).brightness,
|
brightness: Theme.of(context).brightness,
|
||||||
elevation: 1,
|
elevation: 1,
|
||||||
centerTitle: true,
|
centerTitle: true,
|
||||||
title: Text(
|
title: Text(
|
||||||
'Shared Groups',
|
'Shared Groups',
|
||||||
style: TextStyle(color: Colors.black, fontSize: 16.0),
|
style: TextStyle(
|
||||||
|
color: StreamChatTheme.of(context).colorTheme.black,
|
||||||
|
fontSize: 16.0),
|
||||||
),
|
),
|
||||||
leading: Center(
|
leading: Center(
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
@@ -381,7 +433,7 @@ class __SharedGroupsScreenState extends State<_SharedGroupsScreen> {
|
|||||||
},
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
child: StreamSvgIcon.left(
|
child: StreamSvgIcon.left(
|
||||||
color: Colors.black,
|
color: StreamChatTheme.of(context).colorTheme.black,
|
||||||
size: 24.0,
|
size: 24.0,
|
||||||
),
|
),
|
||||||
width: 24.0,
|
width: 24.0,
|
||||||
@@ -389,7 +441,7 @@ class __SharedGroupsScreenState extends State<_SharedGroupsScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
backgroundColor: StreamChatTheme.of(context).primaryColor,
|
backgroundColor: StreamChatTheme.of(context).colorTheme.white,
|
||||||
),
|
),
|
||||||
body: FutureBuilder<List<Channel>>(
|
body: FutureBuilder<List<Channel>>(
|
||||||
future: chat.client.queryChannels(
|
future: chat.client.queryChannels(
|
||||||
@@ -409,12 +461,46 @@ class __SharedGroupsScreenState extends State<_SharedGroupsScreen> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (snapshot.data == null) {
|
if (!snapshot.hasData) {
|
||||||
return Center(
|
return Center(
|
||||||
child: CircularProgressIndicator(),
|
child: CircularProgressIndicator(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (snapshot.data.isEmpty) {
|
||||||
|
return Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
StreamSvgIcon.message(
|
||||||
|
size: 136.0,
|
||||||
|
color: StreamChatTheme.of(context).colorTheme.greyGainsboro,
|
||||||
|
),
|
||||||
|
SizedBox(height: 16.0),
|
||||||
|
Text(
|
||||||
|
'No Shared Groups',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14.0,
|
||||||
|
color: StreamChatTheme.of(context).colorTheme.black,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 8.0),
|
||||||
|
Text(
|
||||||
|
'Group shared with User will appear here.',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14.0,
|
||||||
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.black
|
||||||
|
.withOpacity(0.5),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return ListView.builder(
|
return ListView.builder(
|
||||||
itemCount: snapshot.data.length,
|
itemCount: snapshot.data.length,
|
||||||
itemBuilder: (context, position) {
|
itemBuilder: (context, position) {
|
||||||
@@ -488,7 +574,11 @@ class __SharedGroupsScreenState extends State<_SharedGroupsScreen> {
|
|||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8.0),
|
||||||
child: Text(
|
child: Text(
|
||||||
'${channel.memberCount} members',
|
'${channel.memberCount} members',
|
||||||
style: TextStyle(color: Colors.black.withOpacity(0.5)),
|
style: TextStyle(
|
||||||
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.black
|
||||||
|
.withOpacity(0.5)),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
@@ -496,7 +586,8 @@ class __SharedGroupsScreenState extends State<_SharedGroupsScreen> {
|
|||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
height: 1.0,
|
height: 1.0,
|
||||||
color: Color(0xffe6e6e6),
|
color:
|
||||||
|
StreamChatTheme.of(context).colorTheme.black.withOpacity(.08),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@@ -504,71 +595,3 @@ class __SharedGroupsScreenState extends State<_SharedGroupsScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _MediaDisplayScreen extends StatelessWidget {
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Scaffold(
|
|
||||||
backgroundColor: Colors.white,
|
|
||||||
appBar: AppBar(
|
|
||||||
brightness: Theme.of(context).brightness,
|
|
||||||
elevation: 1,
|
|
||||||
centerTitle: true,
|
|
||||||
title: Text(
|
|
||||||
'Photos & Videos',
|
|
||||||
style: TextStyle(color: Colors.black, fontSize: 16.0),
|
|
||||||
),
|
|
||||||
leading: Center(
|
|
||||||
child: InkWell(
|
|
||||||
onTap: () {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
},
|
|
||||||
child: Container(
|
|
||||||
child: StreamSvgIcon.left(
|
|
||||||
color: Colors.black,
|
|
||||||
size: 24.0,
|
|
||||||
),
|
|
||||||
width: 24.0,
|
|
||||||
height: 24.0,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
backgroundColor: StreamChatTheme.of(context).primaryColor,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _FileDisplayScreen extends StatelessWidget {
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Scaffold(
|
|
||||||
backgroundColor: Colors.white,
|
|
||||||
appBar: AppBar(
|
|
||||||
brightness: Theme.of(context).brightness,
|
|
||||||
elevation: 1,
|
|
||||||
centerTitle: true,
|
|
||||||
title: Text(
|
|
||||||
'Files',
|
|
||||||
style: TextStyle(color: Colors.black, fontSize: 16.0),
|
|
||||||
),
|
|
||||||
leading: Center(
|
|
||||||
child: InkWell(
|
|
||||||
onTap: () {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
},
|
|
||||||
child: Container(
|
|
||||||
child: StreamSvgIcon.left(
|
|
||||||
color: Colors.black,
|
|
||||||
size: 24.0,
|
|
||||||
),
|
|
||||||
width: 24.0,
|
|
||||||
height: 24.0,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
backgroundColor: StreamChatTheme.of(context).primaryColor,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -66,7 +66,7 @@ class ChipInputTextFieldState<T> extends State<ChipsInputTextField<T>> {
|
|||||||
onTap: _pauseItemAddition ? resumeItemAddition : null,
|
onTap: _pauseItemAddition ? resumeItemAddition : null,
|
||||||
child: Material(
|
child: Material(
|
||||||
elevation: 1,
|
elevation: 1,
|
||||||
color: Colors.white,
|
color: StreamChatTheme.of(context).colorTheme.white,
|
||||||
child: Container(
|
child: Container(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(16, 16, 16, 16),
|
padding: const EdgeInsets.fromLTRB(16, 16, 16, 16),
|
||||||
@@ -78,10 +78,14 @@ class ChipInputTextFieldState<T> extends State<ChipsInputTextField<T>> {
|
|||||||
padding: const EdgeInsets.symmetric(vertical: 4.0),
|
padding: const EdgeInsets.symmetric(vertical: 4.0),
|
||||||
child: Text(
|
child: Text(
|
||||||
'TO:',
|
'TO:',
|
||||||
style: TextStyle(
|
style: StreamChatTheme.of(context)
|
||||||
fontSize: 12,
|
.textTheme
|
||||||
color: Colors.black.withOpacity(0.5),
|
.footnote
|
||||||
),
|
.copyWith(
|
||||||
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.black
|
||||||
|
.withOpacity(.5)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(width: 12),
|
SizedBox(width: 12),
|
||||||
@@ -111,10 +115,14 @@ class ChipInputTextFieldState<T> extends State<ChipsInputTextField<T>> {
|
|||||||
disabledBorder: InputBorder.none,
|
disabledBorder: InputBorder.none,
|
||||||
contentPadding: const EdgeInsets.only(top: 4.0),
|
contentPadding: const EdgeInsets.only(top: 4.0),
|
||||||
hintText: widget.hint,
|
hintText: widget.hint,
|
||||||
hintStyle: TextStyle(
|
hintStyle: StreamChatTheme.of(context)
|
||||||
color: Colors.black.withOpacity(0.5),
|
.textTheme
|
||||||
fontSize: 14,
|
.body
|
||||||
),
|
.copyWith(
|
||||||
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.black
|
||||||
|
.withOpacity(.5)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -126,11 +134,17 @@ class ChipInputTextFieldState<T> extends State<ChipsInputTextField<T>> {
|
|||||||
child: IconButton(
|
child: IconButton(
|
||||||
icon: _chips.isEmpty
|
icon: _chips.isEmpty
|
||||||
? StreamSvgIcon.user(
|
? StreamSvgIcon.user(
|
||||||
color: Colors.black.withOpacity(0.5),
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.black
|
||||||
|
.withOpacity(0.5),
|
||||||
size: 24,
|
size: 24,
|
||||||
)
|
)
|
||||||
: StreamSvgIcon.userAdd(
|
: StreamSvgIcon.userAdd(
|
||||||
color: Colors.black.withOpacity(0.5),
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.black
|
||||||
|
.withOpacity(0.5),
|
||||||
size: 24,
|
size: 24,
|
||||||
),
|
),
|
||||||
onPressed: resumeItemAddition,
|
onPressed: resumeItemAddition,
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import 'package:example/advanced_options_page.dart';
|
|
||||||
import 'package:example/main.dart';
|
|
||||||
import 'package:example/stream_version.dart';
|
import 'package:example/stream_version.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@@ -60,6 +58,7 @@ class ChooseUserPage extends StatelessWidget {
|
|||||||
(_, value) => value..extraData['image'] = getRandomPicUrl(value));
|
(_, value) => value..extraData['image'] = getRandomPicUrl(value));
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
|
backgroundColor: StreamChatTheme.of(context).colorTheme.whiteSnow,
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
@@ -73,6 +72,7 @@ class ChooseUserPage extends StatelessWidget {
|
|||||||
child: SvgPicture.asset(
|
child: SvgPicture.asset(
|
||||||
'assets/logo.svg',
|
'assets/logo.svg',
|
||||||
height: 40,
|
height: 40,
|
||||||
|
color: StreamChatTheme.of(context).colorTheme.accentBlue,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -80,19 +80,12 @@ class ChooseUserPage extends StatelessWidget {
|
|||||||
padding: const EdgeInsets.only(bottom: 13.0),
|
padding: const EdgeInsets.only(bottom: 13.0),
|
||||||
child: Text(
|
child: Text(
|
||||||
'Welcome to Stream Chat',
|
'Welcome to Stream Chat',
|
||||||
style: TextStyle(
|
style: StreamChatTheme.of(context).textTheme.title,
|
||||||
fontSize: 22,
|
|
||||||
color: Colors.black,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
'Select a user to try the Flutter SDK:',
|
'Select a user to try the Flutter SDK:',
|
||||||
style: TextStyle(
|
style: StreamChatTheme.of(context).textTheme.body,
|
||||||
fontSize: 14.5,
|
|
||||||
color: Colors.black,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
@@ -100,9 +93,8 @@ class ChooseUserPage extends StatelessWidget {
|
|||||||
child: ListView.separated(
|
child: ListView.separated(
|
||||||
separatorBuilder: (context, i) {
|
separatorBuilder: (context, i) {
|
||||||
return Container(
|
return Container(
|
||||||
width: double.infinity,
|
|
||||||
color: Colors.black12,
|
|
||||||
height: 1,
|
height: 1,
|
||||||
|
color: StreamChatTheme.of(context).colorTheme.greyWhisper,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
itemCount: users.length + 1,
|
itemCount: users.length + 1,
|
||||||
@@ -116,11 +108,16 @@ class ChooseUserPage extends StatelessWidget {
|
|||||||
showDialog(
|
showDialog(
|
||||||
barrierDismissible: false,
|
barrierDismissible: false,
|
||||||
context: context,
|
context: context,
|
||||||
|
barrierColor: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.overlay,
|
||||||
builder: (context) => Center(
|
builder: (context) => Center(
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
color: Colors.white,
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.white,
|
||||||
),
|
),
|
||||||
height: 100,
|
height: 100,
|
||||||
width: 100,
|
width: 100,
|
||||||
@@ -169,13 +166,24 @@ class ChooseUserPage extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
title: Text(
|
title: Text(
|
||||||
user.name,
|
user.name,
|
||||||
style: TextStyle(fontWeight: FontWeight.bold),
|
style:
|
||||||
|
StreamChatTheme.of(context).textTheme.bodyBold,
|
||||||
),
|
),
|
||||||
subtitle: Text('Stream test account'),
|
subtitle: Text(
|
||||||
trailing: SvgPicture.asset(
|
'Stream test account',
|
||||||
'assets/icon_arrow_right.svg',
|
style: StreamChatTheme.of(context)
|
||||||
height: 24,
|
.textTheme
|
||||||
width: 24,
|
.footnote
|
||||||
|
.copyWith(
|
||||||
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.grey,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
trailing: StreamSvgIcon.arrow_right(
|
||||||
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.accentBlue,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
@@ -185,16 +193,26 @@ class ChooseUserPage extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
leading: CircleAvatar(
|
leading: CircleAvatar(
|
||||||
child: StreamSvgIcon.settings(
|
child: StreamSvgIcon.settings(
|
||||||
color: Colors.black,
|
color: StreamChatTheme.of(context).colorTheme.black,
|
||||||
),
|
),
|
||||||
backgroundColor:
|
backgroundColor: StreamChatTheme.of(context)
|
||||||
StreamChatTheme.of(context).secondaryColor,
|
.colorTheme
|
||||||
|
.greyWhisper,
|
||||||
),
|
),
|
||||||
title: Text(
|
title: Text(
|
||||||
'Advanced Options',
|
'Advanced Options',
|
||||||
style: TextStyle(fontWeight: FontWeight.bold),
|
style: StreamChatTheme.of(context).textTheme.bodyBold,
|
||||||
|
),
|
||||||
|
subtitle: Text(
|
||||||
|
'Custom settings',
|
||||||
|
style: StreamChatTheme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.footnote
|
||||||
|
.copyWith(
|
||||||
|
color:
|
||||||
|
StreamChatTheme.of(context).colorTheme.grey,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
subtitle: Text('Custom settings'),
|
|
||||||
trailing: SvgPicture.asset(
|
trailing: SvgPicture.asset(
|
||||||
'assets/icon_arrow_right.svg',
|
'assets/icon_arrow_right.svg',
|
||||||
height: 24,
|
height: 24,
|
||||||
|
|||||||
@@ -39,27 +39,29 @@ class MyApp extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = ThemeData(
|
final themeData = ThemeData(primarySwatch: Colors.green);
|
||||||
primarySwatch: Colors.green,
|
final defaultTheme = StreamChatThemeData.fromTheme(themeData);
|
||||||
);
|
final customTheme = defaultTheme.merge(StreamChatThemeData(
|
||||||
|
|
||||||
return MaterialApp(
|
|
||||||
theme: theme,
|
|
||||||
builder: (context, child) => StreamChat(
|
|
||||||
child: child,
|
|
||||||
client: client,
|
|
||||||
streamChatThemeData: StreamChatThemeData.fromTheme(theme).copyWith(
|
|
||||||
ownMessageTheme: MessageTheme(
|
ownMessageTheme: MessageTheme(
|
||||||
messageBackgroundColor: Colors.black,
|
messageBackgroundColor: StreamChatTheme.of(context).colorTheme.black,
|
||||||
messageText: TextStyle(
|
messageText: TextStyle(
|
||||||
color: Colors.white,
|
color: StreamChatTheme.of(context).colorTheme.white,
|
||||||
),
|
),
|
||||||
avatarTheme: AvatarTheme(
|
avatarTheme: AvatarTheme(
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
));
|
||||||
),
|
|
||||||
|
return MaterialApp(
|
||||||
|
theme: themeData,
|
||||||
|
builder: (context, child) {
|
||||||
|
return StreamChat(
|
||||||
|
child: child,
|
||||||
|
client: client,
|
||||||
|
streamChatThemeData: customTheme,
|
||||||
|
);
|
||||||
|
},
|
||||||
home: ChannelListPage(),
|
home: ChannelListPage(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,10 +84,12 @@ class ChannelListPage extends StatelessWidget {
|
|||||||
channel: channel,
|
channel: channel,
|
||||||
),
|
),
|
||||||
title: ChannelName(
|
title: ChannelName(
|
||||||
channel: channel,
|
|
||||||
textStyle:
|
textStyle:
|
||||||
StreamChatTheme.of(context).channelPreviewTheme.title.copyWith(
|
StreamChatTheme.of(context).channelPreviewTheme.title.copyWith(
|
||||||
color: Colors.black.withOpacity(opacity),
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.black
|
||||||
|
.withOpacity(opacity),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
subtitle: Text(subtitle),
|
subtitle: Text(subtitle),
|
||||||
|
|||||||
@@ -127,7 +127,6 @@ class ChannelPage extends StatelessWidget {
|
|||||||
showTimestamp: !details.isNextUser,
|
showTimestamp: !details.isNextUser,
|
||||||
showUsername: !details.isNextUser,
|
showUsername: !details.isNextUser,
|
||||||
showReactions: false,
|
showReactions: false,
|
||||||
showReplyIndicator: false,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,15 +59,16 @@ class _GroupChatDetailsScreenState extends State<GroupChatDetailsScreen> {
|
|||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
backgroundColor: Color.fromRGBO(252, 252, 252, 1),
|
backgroundColor: StreamChatTheme.of(context).colorTheme.whiteSnow,
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
|
brightness: Theme.of(context).brightness,
|
||||||
elevation: 1,
|
elevation: 1,
|
||||||
backgroundColor: Colors.white,
|
backgroundColor: StreamChatTheme.of(context).colorTheme.white,
|
||||||
leading: const StreamBackButton(),
|
leading: const StreamBackButton(),
|
||||||
title: Text(
|
title: Text(
|
||||||
'Name of Group Chat',
|
'Name of Group Chat',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.black,
|
color: StreamChatTheme.of(context).colorTheme.black,
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -82,7 +83,7 @@ class _GroupChatDetailsScreenState extends State<GroupChatDetailsScreen> {
|
|||||||
'NAME',
|
'NAME',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
color: Colors.black.withOpacity(0.5),
|
color: StreamChatTheme.of(context).colorTheme.grey,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(width: 16),
|
SizedBox(width: 16),
|
||||||
@@ -99,7 +100,9 @@ class _GroupChatDetailsScreenState extends State<GroupChatDetailsScreen> {
|
|||||||
contentPadding: const EdgeInsets.all(0),
|
contentPadding: const EdgeInsets.all(0),
|
||||||
hintText: 'Choose a group chat name',
|
hintText: 'Choose a group chat name',
|
||||||
hintStyle: TextStyle(
|
hintStyle: TextStyle(
|
||||||
fontSize: 14, color: Colors.black.withOpacity(.5)),
|
fontSize: 14,
|
||||||
|
color: StreamChatTheme.of(context).colorTheme.grey,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -113,7 +116,9 @@ class _GroupChatDetailsScreenState extends State<GroupChatDetailsScreen> {
|
|||||||
padding: const EdgeInsets.all(0),
|
padding: const EdgeInsets.all(0),
|
||||||
icon: StreamSvgIcon.check(
|
icon: StreamSvgIcon.check(
|
||||||
size: 24,
|
size: 24,
|
||||||
color: _isGroupNameEmpty ? Colors.grey : Color(0xFF006CFF),
|
color: _isGroupNameEmpty
|
||||||
|
? StreamChatTheme.of(context).colorTheme.grey
|
||||||
|
: StreamChatTheme.of(context).colorTheme.accentBlue,
|
||||||
),
|
),
|
||||||
onPressed: _isGroupNameEmpty
|
onPressed: _isGroupNameEmpty
|
||||||
? null
|
? null
|
||||||
@@ -144,7 +149,9 @@ class _GroupChatDetailsScreenState extends State<GroupChatDetailsScreen> {
|
|||||||
children: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
width: double.maxFinite,
|
width: double.maxFinite,
|
||||||
color: Colors.grey.shade50,
|
decoration: BoxDecoration(
|
||||||
|
gradient: StreamChatTheme.of(context).colorTheme.bgGradient,
|
||||||
|
),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
vertical: 8,
|
vertical: 8,
|
||||||
@@ -153,7 +160,7 @@ class _GroupChatDetailsScreenState extends State<GroupChatDetailsScreen> {
|
|||||||
child: Text(
|
child: Text(
|
||||||
'$_totalUsers ${_totalUsers > 1 ? 'Members' : 'Member'}',
|
'$_totalUsers ${_totalUsers > 1 ? 'Members' : 'Member'}',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontWeight: FontWeight.w500,
|
color: StreamChatTheme.of(context).colorTheme.grey,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -166,17 +173,14 @@ class _GroupChatDetailsScreenState extends State<GroupChatDetailsScreen> {
|
|||||||
itemCount: _selectedUsers.length + 1,
|
itemCount: _selectedUsers.length + 1,
|
||||||
separatorBuilder: (_, __) => Container(
|
separatorBuilder: (_, __) => Container(
|
||||||
height: 1,
|
height: 1,
|
||||||
color: Theme.of(context).brightness == Brightness.dark
|
color: StreamChatTheme.of(context).colorTheme.greyWhisper,
|
||||||
? Colors.white.withOpacity(0.1)
|
|
||||||
: Colors.black.withOpacity(0.1),
|
|
||||||
),
|
),
|
||||||
itemBuilder: (_, index) {
|
itemBuilder: (_, index) {
|
||||||
if (index == _selectedUsers.length) {
|
if (index == _selectedUsers.length) {
|
||||||
return Container(
|
return Container(
|
||||||
height: 1,
|
height: 1,
|
||||||
color: Theme.of(context).brightness == Brightness.dark
|
color:
|
||||||
? Colors.white.withOpacity(0.1)
|
StreamChatTheme.of(context).colorTheme.greyWhisper,
|
||||||
: Colors.black.withOpacity(0.1),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
final user = _selectedUsers[index];
|
final user = _selectedUsers[index];
|
||||||
@@ -200,7 +204,7 @@ class _GroupChatDetailsScreenState extends State<GroupChatDetailsScreen> {
|
|||||||
trailing: IconButton(
|
trailing: IconButton(
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
Icons.clear_rounded,
|
Icons.clear_rounded,
|
||||||
color: Colors.black,
|
color: StreamChatTheme.of(context).colorTheme.black,
|
||||||
),
|
),
|
||||||
padding: const EdgeInsets.all(0),
|
padding: const EdgeInsets.all(0),
|
||||||
splashRadius: 24,
|
splashRadius: 24,
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:example/chat_info_screen.dart';
|
||||||
import 'package:example/choose_user_page.dart';
|
import 'package:example/choose_user_page.dart';
|
||||||
import 'package:example/new_chat_screen.dart';
|
import 'package:example/group_info_screen.dart';
|
||||||
import 'package:example/new_group_chat_screen.dart';
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@@ -13,7 +14,6 @@ import 'notifications_service.dart';
|
|||||||
import 'routes/app_routes.dart';
|
import 'routes/app_routes.dart';
|
||||||
import 'routes/routes.dart';
|
import 'routes/routes.dart';
|
||||||
import 'search_text_field.dart';
|
import 'search_text_field.dart';
|
||||||
import 'dart:async';
|
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
@@ -27,7 +27,7 @@ void main() async {
|
|||||||
logLevel: Level.INFO,
|
logLevel: Level.INFO,
|
||||||
showLocalNotification:
|
showLocalNotification:
|
||||||
(!kIsWeb && Platform.isAndroid) ? showLocalNotification : null,
|
(!kIsWeb && Platform.isAndroid) ? showLocalNotification : null,
|
||||||
persistenceEnabled: true,
|
persistenceEnabled: false,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (userId != null) {
|
if (userId != null) {
|
||||||
@@ -51,18 +51,20 @@ class MyApp extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
return MaterialApp(
|
||||||
|
builder: (context, child) {
|
||||||
return StreamChat(
|
return StreamChat(
|
||||||
client: client,
|
client: client,
|
||||||
child: MaterialApp(
|
child: child,
|
||||||
|
);
|
||||||
|
},
|
||||||
debugShowCheckedModeBanner: false,
|
debugShowCheckedModeBanner: false,
|
||||||
theme: ThemeData.light(),
|
theme: ThemeData.light(),
|
||||||
darkTheme: ThemeData.dark(),
|
darkTheme: ThemeData.dark(),
|
||||||
//TODO change to system once dark theme is implemented
|
themeMode: ThemeMode.system,
|
||||||
themeMode: ThemeMode.light,
|
|
||||||
onGenerateRoute: AppRoutes.generateRoute,
|
onGenerateRoute: AppRoutes.generateRoute,
|
||||||
initialRoute:
|
initialRoute:
|
||||||
client.state.user == null ? Routes.CHOOSE_USER : Routes.HOME,
|
client.state.user == null ? Routes.CHOOSE_USER : Routes.HOME,
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -84,7 +86,9 @@ class _HomePageState extends State<HomePage> {
|
|||||||
overflow: Overflow.visible,
|
overflow: Overflow.visible,
|
||||||
children: [
|
children: [
|
||||||
StreamSvgIcon.message(
|
StreamSvgIcon.message(
|
||||||
color: _isSelected(0) ? Colors.black : Colors.grey,
|
color: _isSelected(0)
|
||||||
|
? StreamChatTheme.of(context).colorTheme.black
|
||||||
|
: Colors.grey,
|
||||||
),
|
),
|
||||||
Positioned(
|
Positioned(
|
||||||
top: -3,
|
top: -3,
|
||||||
@@ -100,7 +104,9 @@ class _HomePageState extends State<HomePage> {
|
|||||||
overflow: Overflow.visible,
|
overflow: Overflow.visible,
|
||||||
children: [
|
children: [
|
||||||
StreamSvgIcon.mentions(
|
StreamSvgIcon.mentions(
|
||||||
color: _isSelected(1) ? Colors.black : Colors.grey,
|
color: _isSelected(1)
|
||||||
|
? StreamChatTheme.of(context).colorTheme.black
|
||||||
|
: Colors.grey,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -113,6 +119,7 @@ class _HomePageState extends State<HomePage> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final user = StreamChat.of(context).user;
|
final user = StreamChat.of(context).user;
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
|
backgroundColor: StreamChatTheme.of(context).colorTheme.whiteSnow,
|
||||||
appBar: ChannelListHeader(
|
appBar: ChannelListHeader(
|
||||||
onNewChatButtonTap: () {
|
onNewChatButtonTap: () {
|
||||||
Navigator.pushNamed(context, Routes.NEW_CHAT);
|
Navigator.pushNamed(context, Routes.NEW_CHAT);
|
||||||
@@ -121,10 +128,14 @@ class _HomePageState extends State<HomePage> {
|
|||||||
drawer: _buildDrawer(context, user),
|
drawer: _buildDrawer(context, user),
|
||||||
drawerEdgeDragWidth: 50,
|
drawerEdgeDragWidth: 50,
|
||||||
bottomNavigationBar: BottomNavigationBar(
|
bottomNavigationBar: BottomNavigationBar(
|
||||||
|
backgroundColor: StreamChatTheme.of(context).colorTheme.white,
|
||||||
currentIndex: _currentIndex,
|
currentIndex: _currentIndex,
|
||||||
items: _navBarItems,
|
items: _navBarItems,
|
||||||
|
selectedLabelStyle: StreamChatTheme.of(context).textTheme.footnoteBold,
|
||||||
|
unselectedLabelStyle:
|
||||||
|
StreamChatTheme.of(context).textTheme.footnoteBold,
|
||||||
type: BottomNavigationBarType.fixed,
|
type: BottomNavigationBarType.fixed,
|
||||||
selectedItemColor: Colors.black,
|
selectedItemColor: StreamChatTheme.of(context).colorTheme.black,
|
||||||
unselectedItemColor: Colors.grey,
|
unselectedItemColor: Colors.grey,
|
||||||
onTap: (index) {
|
onTap: (index) {
|
||||||
setState(() => _currentIndex = index);
|
setState(() => _currentIndex = index);
|
||||||
@@ -142,6 +153,8 @@ class _HomePageState extends State<HomePage> {
|
|||||||
|
|
||||||
Drawer _buildDrawer(BuildContext context, User user) {
|
Drawer _buildDrawer(BuildContext context, User user) {
|
||||||
return Drawer(
|
return Drawer(
|
||||||
|
child: Container(
|
||||||
|
color: StreamChatTheme.of(context).colorTheme.white,
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.only(
|
padding: EdgeInsets.only(
|
||||||
@@ -176,7 +189,10 @@ class _HomePageState extends State<HomePage> {
|
|||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: StreamSvgIcon.penWrite(
|
leading: StreamSvgIcon.penWrite(
|
||||||
color: Colors.black.withOpacity(.5),
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.black
|
||||||
|
.withOpacity(.5),
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.popAndPushNamed(
|
Navigator.popAndPushNamed(
|
||||||
@@ -193,7 +209,10 @@ class _HomePageState extends State<HomePage> {
|
|||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: StreamSvgIcon.contacts(
|
leading: StreamSvgIcon.contacts(
|
||||||
color: Colors.black.withOpacity(.5),
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.black
|
||||||
|
.withOpacity(.5),
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.popAndPushNamed(
|
Navigator.popAndPushNamed(
|
||||||
@@ -228,7 +247,10 @@ class _HomePageState extends State<HomePage> {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
leading: StreamSvgIcon.user(
|
leading: StreamSvgIcon.user(
|
||||||
color: Colors.black.withOpacity(.5),
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.black
|
||||||
|
.withOpacity(.5),
|
||||||
),
|
),
|
||||||
title: Text(
|
title: Text(
|
||||||
'Sign out',
|
'Sign out',
|
||||||
@@ -243,6 +265,7 @@ class _HomePageState extends State<HomePage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -467,6 +490,32 @@ class _ChannelListPageState extends State<ChannelListPage> {
|
|||||||
limit: 20,
|
limit: 20,
|
||||||
),
|
),
|
||||||
channelWidget: ChannelPage(),
|
channelWidget: ChannelPage(),
|
||||||
|
onViewInfoTap: (channel) {
|
||||||
|
if (channel.memberCount == 2 &&
|
||||||
|
channel.isDistinct) {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => StreamChannel(
|
||||||
|
channel: channel,
|
||||||
|
child: ChatInfoScreen(
|
||||||
|
user: channel.state.members.first.user,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => StreamChannel(
|
||||||
|
channel: channel,
|
||||||
|
child: GroupInfoScreen(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -489,7 +538,7 @@ class ChannelPageArgs {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
class ChannelPage extends StatelessWidget {
|
class ChannelPage extends StatefulWidget {
|
||||||
final int initialScrollIndex;
|
final int initialScrollIndex;
|
||||||
final double initialAlignment;
|
final double initialAlignment;
|
||||||
final bool highlightInitialMessage;
|
final bool highlightInitialMessage;
|
||||||
@@ -501,12 +550,75 @@ class ChannelPage extends StatelessWidget {
|
|||||||
this.highlightInitialMessage = false,
|
this.highlightInitialMessage = false,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_ChannelPageState createState() => _ChannelPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ChannelPageState extends State<ChannelPage> {
|
||||||
|
Message _quotedMessage;
|
||||||
|
FocusNode _focusNode;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_focusNode = FocusNode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_focusNode.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _reply(Message message) {
|
||||||
|
setState(() => _quotedMessage = message);
|
||||||
|
_focusNode.requestFocus();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: Color.fromRGBO(252, 252, 252, 1),
|
backgroundColor: StreamChatTheme.of(context).colorTheme.whiteSnow,
|
||||||
appBar: ChannelHeader(
|
appBar: ChannelHeader(
|
||||||
showTypingIndicator: false,
|
showTypingIndicator: false,
|
||||||
|
onImageTap: () async {
|
||||||
|
var channel = StreamChannel.of(context).channel;
|
||||||
|
|
||||||
|
if (channel.memberCount == 2 && channel.isDistinct) {
|
||||||
|
final currentUser = StreamChat.of(context).user;
|
||||||
|
final otherUser = channel.state.members.firstWhere(
|
||||||
|
(element) => element.user.id != currentUser.id,
|
||||||
|
orElse: () => null,
|
||||||
|
);
|
||||||
|
if (otherUser != null) {
|
||||||
|
final pop = await Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => StreamChannel(
|
||||||
|
child: ChatInfoScreen(
|
||||||
|
user: otherUser.user,
|
||||||
|
),
|
||||||
|
channel: channel,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (pop == true) {
|
||||||
|
Navigator.pop(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => StreamChannel(
|
||||||
|
child: GroupInfoScreen(),
|
||||||
|
channel: channel,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
),
|
),
|
||||||
body: Column(
|
body: Column(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
@@ -514,14 +626,35 @@ class ChannelPage extends StatelessWidget {
|
|||||||
child: Stack(
|
child: Stack(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
MessageListView(
|
MessageListView(
|
||||||
initialScrollIndex: initialScrollIndex,
|
initialScrollIndex: widget.initialScrollIndex,
|
||||||
initialAlignment: initialAlignment,
|
initialAlignment: widget.initialAlignment,
|
||||||
highlightInitialMessage: highlightInitialMessage,
|
highlightInitialMessage: widget.highlightInitialMessage,
|
||||||
|
onMessageSwiped: _reply,
|
||||||
|
onReplyTap: _reply,
|
||||||
threadBuilder: (_, parentMessage) {
|
threadBuilder: (_, parentMessage) {
|
||||||
return ThreadPage(
|
return ThreadPage(
|
||||||
parent: parentMessage,
|
parent: parentMessage,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
onShowMessage: (m, c) async {
|
||||||
|
final client = StreamChat.of(context).client;
|
||||||
|
final message = m;
|
||||||
|
final channel = client.channel(
|
||||||
|
c.type,
|
||||||
|
id: c.id,
|
||||||
|
);
|
||||||
|
if (channel.state == null) {
|
||||||
|
await channel.watch();
|
||||||
|
}
|
||||||
|
Navigator.pushReplacementNamed(
|
||||||
|
context,
|
||||||
|
Routes.CHANNEL_PAGE,
|
||||||
|
arguments: ChannelPageArgs(
|
||||||
|
channel: channel,
|
||||||
|
initialMessage: message,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
Positioned(
|
Positioned(
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
@@ -529,7 +662,10 @@ class ChannelPage extends StatelessWidget {
|
|||||||
right: 0,
|
right: 0,
|
||||||
child: Container(
|
child: Container(
|
||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
color: Color(0xffFCFCFC).withOpacity(.9),
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.whiteSnow
|
||||||
|
.withOpacity(.9),
|
||||||
child: TypingIndicator(
|
child: TypingIndicator(
|
||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
@@ -542,7 +678,13 @@ class ChannelPage extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
MessageInput(),
|
MessageInput(
|
||||||
|
focusNode: _focusNode,
|
||||||
|
quotedMessage: _quotedMessage,
|
||||||
|
onQuotedMessageCleared: () {
|
||||||
|
setState(() => _quotedMessage = null);
|
||||||
|
},
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -564,7 +706,7 @@ class ThreadPage extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: Color.fromRGBO(252, 252, 252, 1),
|
backgroundColor: StreamChatTheme.of(context).colorTheme.whiteSnow,
|
||||||
appBar: ThreadHeader(
|
appBar: ThreadHeader(
|
||||||
parent: parent,
|
parent: parent,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
|||||||
|
|
||||||
import 'chips_input_text_field.dart';
|
import 'chips_input_text_field.dart';
|
||||||
import 'main.dart';
|
import 'main.dart';
|
||||||
import 'new_group_chat_screen.dart';
|
|
||||||
import 'routes/routes.dart';
|
import 'routes/routes.dart';
|
||||||
|
|
||||||
class NewChatScreen extends StatefulWidget {
|
class NewChatScreen extends StatefulWidget {
|
||||||
@@ -37,8 +36,6 @@ class _NewChatScreenState extends State<NewChatScreen> {
|
|||||||
|
|
||||||
bool _showUserList = true;
|
bool _showUserList = true;
|
||||||
|
|
||||||
bool _channelExisted = false;
|
|
||||||
|
|
||||||
void _userNameListener() {
|
void _userNameListener() {
|
||||||
if (_debounce?.isActive ?? false) _debounce.cancel();
|
if (_debounce?.isActive ?? false) _debounce.cancel();
|
||||||
_debounce = Timer(const Duration(milliseconds: 350), () {
|
_debounce = Timer(const Duration(milliseconds: 350), () {
|
||||||
@@ -122,17 +119,18 @@ class _NewChatScreenState extends State<NewChatScreen> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: Color.fromRGBO(252, 252, 252, 1),
|
backgroundColor: StreamChatTheme.of(context).colorTheme.whiteSnow,
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
|
brightness: Theme.of(context).brightness,
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
backgroundColor: Colors.white,
|
backgroundColor: StreamChatTheme.of(context).colorTheme.white,
|
||||||
leading: const StreamBackButton(),
|
leading: const StreamBackButton(),
|
||||||
title: Text(
|
title: Text(
|
||||||
'New Chat',
|
'New Chat',
|
||||||
style: TextStyle(
|
style: StreamChatTheme.of(context)
|
||||||
color: Colors.black,
|
.textTheme
|
||||||
fontSize: 16,
|
.headlineBold
|
||||||
),
|
.copyWith(color: StreamChatTheme.of(context).colorTheme.black),
|
||||||
),
|
),
|
||||||
centerTitle: true,
|
centerTitle: true,
|
||||||
),
|
),
|
||||||
@@ -157,7 +155,9 @@ class _NewChatScreenState extends State<NewChatScreen> {
|
|||||||
children: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.black.withOpacity(0.05),
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.greyGainsboro,
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
),
|
),
|
||||||
padding: const EdgeInsets.only(left: 24),
|
padding: const EdgeInsets.only(left: 24),
|
||||||
@@ -165,12 +165,19 @@ class _NewChatScreenState extends State<NewChatScreen> {
|
|||||||
padding: const EdgeInsets.fromLTRB(8, 4, 12, 4),
|
padding: const EdgeInsets.fromLTRB(8, 4, 12, 4),
|
||||||
child: Text(
|
child: Text(
|
||||||
user.name,
|
user.name,
|
||||||
style: TextStyle(color: Colors.black),
|
maxLines: 1,
|
||||||
|
style: TextStyle(
|
||||||
|
color:
|
||||||
|
StreamChatTheme.of(context).colorTheme.black,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Opacity(
|
),
|
||||||
opacity: .8,
|
Container(
|
||||||
|
foregroundDecoration: BoxDecoration(
|
||||||
|
color: StreamChatTheme.of(context).colorTheme.overlay,
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
child: UserAvatar(
|
child: UserAvatar(
|
||||||
showOnlineStatus: false,
|
showOnlineStatus: false,
|
||||||
user: user,
|
user: user,
|
||||||
@@ -180,11 +187,7 @@ class _NewChatScreenState extends State<NewChatScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Positioned(
|
StreamSvgIcon.close(),
|
||||||
child: StreamSvgIcon.close(
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -212,7 +215,9 @@ class _NewChatScreenState extends State<NewChatScreen> {
|
|||||||
StreamNeumorphicButton(
|
StreamNeumorphicButton(
|
||||||
child: Center(
|
child: Center(
|
||||||
child: StreamSvgIcon.contacts(
|
child: StreamSvgIcon.contacts(
|
||||||
color: Color(0xFF006CFF),
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.accentBlue,
|
||||||
size: 24,
|
size: 24,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -220,10 +225,7 @@ class _NewChatScreenState extends State<NewChatScreen> {
|
|||||||
SizedBox(width: 8),
|
SizedBox(width: 8),
|
||||||
Text(
|
Text(
|
||||||
'Create a Group',
|
'Create a Group',
|
||||||
style: TextStyle(
|
style: StreamChatTheme.of(context).textTheme.bodyBold,
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
fontSize: 16,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -234,15 +236,7 @@ class _NewChatScreenState extends State<NewChatScreen> {
|
|||||||
Container(
|
Container(
|
||||||
width: double.maxFinite,
|
width: double.maxFinite,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
gradient: LinearGradient(
|
gradient: StreamChatTheme.of(context).colorTheme.bgGradient,
|
||||||
begin: Alignment.centerLeft,
|
|
||||||
end: Alignment.centerRight,
|
|
||||||
colors: [
|
|
||||||
Colors.black.withOpacity(0.02),
|
|
||||||
Colors.white.withOpacity(0.05),
|
|
||||||
],
|
|
||||||
stops: [0, 1],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
@@ -253,10 +247,14 @@ class _NewChatScreenState extends State<NewChatScreen> {
|
|||||||
_isSearchActive
|
_isSearchActive
|
||||||
? "Matches for \"$_userNameQuery\""
|
? "Matches for \"$_userNameQuery\""
|
||||||
: 'On the platform',
|
: 'On the platform',
|
||||||
style: TextStyle(
|
style: StreamChatTheme.of(context)
|
||||||
color: Colors.black.withOpacity(0.5),
|
.textTheme
|
||||||
),
|
.footnote
|
||||||
),
|
.copyWith(
|
||||||
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.black
|
||||||
|
.withOpacity(.5))),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
@@ -316,7 +314,17 @@ class _NewChatScreenState extends State<NewChatScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
'No user matches these keywords...'),
|
'No user matches these keywords...',
|
||||||
|
style: StreamChatTheme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.footnote
|
||||||
|
.copyWith(
|
||||||
|
color: StreamChatTheme.of(
|
||||||
|
context)
|
||||||
|
.colorTheme
|
||||||
|
.black
|
||||||
|
.withOpacity(.5)),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -340,7 +348,10 @@ class _NewChatScreenState extends State<NewChatScreen> {
|
|||||||
'No chats here yet...',
|
'No chats here yet...',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
color: Colors.black.withOpacity(.5),
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.black
|
||||||
|
.withOpacity(.5),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,9 +3,8 @@ import 'dart:async';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||||
|
|
||||||
import 'group_chat_details_screen.dart';
|
|
||||||
import 'search_text_field.dart';
|
|
||||||
import 'routes/routes.dart';
|
import 'routes/routes.dart';
|
||||||
|
import 'search_text_field.dart';
|
||||||
|
|
||||||
class NewGroupChatScreen extends StatefulWidget {
|
class NewGroupChatScreen extends StatefulWidget {
|
||||||
@override
|
@override
|
||||||
@@ -51,23 +50,16 @@ class _NewGroupChatScreenState extends State<NewGroupChatScreen> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SafeArea(
|
return Scaffold(
|
||||||
child: Scaffold(
|
backgroundColor: StreamChatTheme.of(context).colorTheme.whiteSnow,
|
||||||
backgroundColor: Color.fromRGBO(252, 252, 252, 1),
|
appBar: AppBar(
|
||||||
body: NestedScrollView(
|
|
||||||
floatHeaderSlivers: true,
|
|
||||||
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
|
|
||||||
return <Widget>[
|
|
||||||
SliverAppBar(
|
|
||||||
pinned: true,
|
|
||||||
forceElevated: true,
|
|
||||||
elevation: 1,
|
elevation: 1,
|
||||||
backgroundColor: Colors.white,
|
backgroundColor: StreamChatTheme.of(context).colorTheme.white,
|
||||||
leading: const StreamBackButton(),
|
leading: const StreamBackButton(),
|
||||||
title: Text(
|
title: Text(
|
||||||
'Add Group Members',
|
'Add Group Members',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.black,
|
color: StreamChatTheme.of(context).colorTheme.black,
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -76,7 +68,7 @@ class _NewGroupChatScreenState extends State<NewGroupChatScreen> {
|
|||||||
if (_selectedUsers.isNotEmpty)
|
if (_selectedUsers.isNotEmpty)
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: StreamSvgIcon.arrow_right(
|
icon: StreamSvgIcon.arrow_right(
|
||||||
color: Color(0xFF006CFF),
|
color: StreamChatTheme.of(context).colorTheme.accentBlue,
|
||||||
),
|
),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final updatedList = await Navigator.pushNamed(
|
final updatedList = await Navigator.pushNamed(
|
||||||
@@ -95,6 +87,10 @@ class _NewGroupChatScreenState extends State<NewGroupChatScreen> {
|
|||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
body: NestedScrollView(
|
||||||
|
floatHeaderSlivers: true,
|
||||||
|
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
|
||||||
|
return <Widget>[
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: SearchTextField(
|
child: SearchTextField(
|
||||||
controller: _controller,
|
controller: _controller,
|
||||||
@@ -137,21 +133,24 @@ class _NewGroupChatScreenState extends State<NewGroupChatScreen> {
|
|||||||
},
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.white,
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.white,
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: Colors.grey.shade100,
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.whiteSnow,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(0.0),
|
|
||||||
child: StreamSvgIcon.close(
|
child: StreamSvgIcon.close(
|
||||||
color: Colors.black,
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.black,
|
||||||
size: 24,
|
size: 24,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -176,15 +175,7 @@ class _NewGroupChatScreenState extends State<NewGroupChatScreen> {
|
|||||||
child: Container(
|
child: Container(
|
||||||
width: double.maxFinite,
|
width: double.maxFinite,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
gradient: LinearGradient(
|
gradient: StreamChatTheme.of(context).colorTheme.bgGradient,
|
||||||
begin: Alignment.centerLeft,
|
|
||||||
end: Alignment.centerRight,
|
|
||||||
colors: [
|
|
||||||
Colors.black.withOpacity(0.02),
|
|
||||||
Colors.white.withOpacity(0.05),
|
|
||||||
],
|
|
||||||
stops: [0, 1],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
@@ -196,7 +187,7 @@ class _NewGroupChatScreenState extends State<NewGroupChatScreen> {
|
|||||||
? 'Matches for \"$_userNameQuery\"'
|
? 'Matches for \"$_userNameQuery\"'
|
||||||
: 'On the platform',
|
: 'On the platform',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.black.withOpacity(0.5),
|
color: StreamChatTheme.of(context).colorTheme.grey,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -258,10 +249,22 @@ class _NewGroupChatScreenState extends State<NewGroupChatScreen> {
|
|||||||
padding: const EdgeInsets.all(24),
|
padding: const EdgeInsets.all(24),
|
||||||
child: StreamSvgIcon.search(
|
child: StreamSvgIcon.search(
|
||||||
size: 96,
|
size: 96,
|
||||||
color: Colors.grey,
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.grey,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'No user matches these keywords...',
|
||||||
|
style: StreamChatTheme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.footnote
|
||||||
|
.copyWith(
|
||||||
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.grey,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text('No user matches these keywords...'),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -274,7 +277,6 @@ class _NewGroupChatScreenState extends State<NewGroupChatScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -292,7 +294,7 @@ class _HeaderDelegate extends SliverPersistentHeaderDelegate {
|
|||||||
Widget build(
|
Widget build(
|
||||||
BuildContext context, double shrinkOffset, bool overlapsContent) {
|
BuildContext context, double shrinkOffset, bool overlapsContent) {
|
||||||
return Container(
|
return Container(
|
||||||
color: Colors.white,
|
color: StreamChatTheme.of(context).colorTheme.white,
|
||||||
child: child,
|
child: child,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import '../main.dart';
|
|||||||
import '../group_chat_details_screen.dart';
|
import '../group_chat_details_screen.dart';
|
||||||
import '../new_group_chat_screen.dart';
|
import '../new_group_chat_screen.dart';
|
||||||
import '../new_chat_screen.dart';
|
import '../new_chat_screen.dart';
|
||||||
|
import '../chat_info_screen.dart';
|
||||||
|
import '../group_info_screen.dart';
|
||||||
|
|
||||||
class AppRoutes {
|
class AppRoutes {
|
||||||
/// Add entry for new route here
|
/// Add entry for new route here
|
||||||
@@ -63,6 +65,20 @@ class AppRoutes {
|
|||||||
selectedUsers: args,
|
selectedUsers: args,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
case Routes.CHAT_INFO_SCREEN:
|
||||||
|
return MaterialPageRoute(
|
||||||
|
settings: const RouteSettings(name: Routes.CHAT_INFO_SCREEN),
|
||||||
|
builder: (_) {
|
||||||
|
return ChatInfoScreen(
|
||||||
|
user: args,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
case Routes.GROUP_INFO_SCREEN:
|
||||||
|
return MaterialPageRoute(
|
||||||
|
settings: const RouteSettings(name: Routes.GROUP_INFO_SCREEN),
|
||||||
|
builder: (_) {
|
||||||
|
return GroupInfoScreen();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,4 +7,6 @@ class Routes {
|
|||||||
static const String NEW_CHAT = '/new_chat';
|
static const String NEW_CHAT = '/new_chat';
|
||||||
static const String NEW_GROUP_CHAT = '/new_group_chat';
|
static const String NEW_GROUP_CHAT = '/new_group_chat';
|
||||||
static const String NEW_GROUP_CHAT_DETAILS = '/new_group_chat_details';
|
static const String NEW_GROUP_CHAT_DETAILS = '/new_group_chat_details';
|
||||||
|
static const String CHAT_INFO_SCREEN = '/chat_info_screen';
|
||||||
|
static const String GROUP_INFO_SCREEN = '/group_info_screen';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,9 +22,9 @@ class SearchTextField extends StatelessWidget {
|
|||||||
return Container(
|
return Container(
|
||||||
height: 36,
|
height: 36,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.white,
|
color: StreamChatTheme.of(context).colorTheme.white,
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: Colors.grey.shade300,
|
color: StreamChatTheme.of(context).colorTheme.greyWhisper,
|
||||||
),
|
),
|
||||||
borderRadius: BorderRadius.circular(24),
|
borderRadius: BorderRadius.circular(24),
|
||||||
),
|
),
|
||||||
@@ -40,6 +40,7 @@ class SearchTextField extends StatelessWidget {
|
|||||||
controller: controller,
|
controller: controller,
|
||||||
onChanged: onChanged,
|
onChanged: onChanged,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
|
prefixText: ' ',
|
||||||
prefixIconConstraints: BoxConstraints.tight(Size(40, 24)),
|
prefixIconConstraints: BoxConstraints.tight(Size(40, 24)),
|
||||||
prefixIcon: Padding(
|
prefixIcon: Padding(
|
||||||
padding: const EdgeInsets.only(
|
padding: const EdgeInsets.only(
|
||||||
@@ -47,15 +48,16 @@ class SearchTextField extends StatelessWidget {
|
|||||||
right: 8,
|
right: 8,
|
||||||
),
|
),
|
||||||
child: StreamSvgIcon.search(
|
child: StreamSvgIcon.search(
|
||||||
color: Colors.black,
|
color: StreamChatTheme.of(context).colorTheme.black,
|
||||||
size: 24,
|
size: 24,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
hintText: hintText,
|
hintText: hintText,
|
||||||
hintStyle: TextStyle(
|
hintStyle: StreamChatTheme.of(context).textTheme.body.copyWith(
|
||||||
color: Colors.black.withOpacity(0.5),
|
color: StreamChatTheme.of(context)
|
||||||
fontSize: 14,
|
.colorTheme
|
||||||
),
|
.black
|
||||||
|
.withOpacity(.5)),
|
||||||
contentPadding: const EdgeInsets.all(0),
|
contentPadding: const EdgeInsets.all(0),
|
||||||
border: OutlineInputBorder(
|
border: OutlineInputBorder(
|
||||||
borderSide: BorderSide.none,
|
borderSide: BorderSide.none,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:yaml/yaml.dart';
|
import 'package:yaml/yaml.dart';
|
||||||
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||||
|
|
||||||
class StreamVersion extends StatelessWidget {
|
class StreamVersion extends StatelessWidget {
|
||||||
const StreamVersion({
|
const StreamVersion({
|
||||||
@@ -27,8 +28,8 @@ class StreamVersion extends StatelessWidget {
|
|||||||
return Text(
|
return Text(
|
||||||
'Stream SDK v ${streamChatDep}',
|
'Stream SDK v ${streamChatDep}',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 14.5,
|
fontSize: 14,
|
||||||
color: Colors.black.withOpacity(.13),
|
color: StreamChatTheme.of(context).colorTheme.greyGainsboro,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
name: example
|
name: example
|
||||||
description: A new Flutter project.
|
description: A new Flutter project.
|
||||||
version: 1.0.94+97
|
version: 1.0.111+114
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.2.2 <3.0.0"
|
sdk: ">=2.2.2 <3.0.0"
|
||||||
@@ -22,7 +22,7 @@ dev_dependencies:
|
|||||||
sdk: flutter
|
sdk: flutter
|
||||||
flutter_driver:
|
flutter_driver:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
flutter_launcher_icons: ^0.8.0
|
flutter_launcher_icons: ^0.8.1
|
||||||
test: any
|
test: any
|
||||||
|
|
||||||
flutter:
|
flutter:
|
||||||
@@ -34,4 +34,5 @@ flutter:
|
|||||||
flutter_icons:
|
flutter_icons:
|
||||||
android: true
|
android: true
|
||||||
ios: true
|
ios: true
|
||||||
image_path: "assets/icon.png"
|
image_path_android: "assets/android_icon.png"
|
||||||
|
image_path_ios: "assets/ios_icon.png"
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import 'dart:io';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:stream_chat/stream_chat.dart';
|
import 'package:stream_chat/stream_chat.dart';
|
||||||
|
|
||||||
|
import '../stream_chat_flutter.dart';
|
||||||
|
|
||||||
class AttachmentError extends StatelessWidget {
|
class AttachmentError extends StatelessWidget {
|
||||||
final Attachment attachment;
|
final Attachment attachment;
|
||||||
final Size size;
|
final Size size;
|
||||||
@@ -23,12 +25,12 @@ class AttachmentError extends StatelessWidget {
|
|||||||
return Center(
|
return Center(
|
||||||
child: Container(
|
child: Container(
|
||||||
width: size?.width,
|
width: size?.width,
|
||||||
height: size?.height,
|
height: size?.height ?? 200,
|
||||||
color: Color(0xffd0021B).withOpacity(.1),
|
color: StreamChatTheme.of(context).colorTheme.accentRed.withOpacity(.1),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Icon(
|
child: Icon(
|
||||||
Icons.error_outline,
|
Icons.error_outline,
|
||||||
color: Colors.white,
|
color: StreamChatTheme.of(context).colorTheme.black,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ class AttachmentTitle extends StatelessWidget {
|
|||||||
attachment.title,
|
attachment.title,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
style: messageTheme.messageText.copyWith(
|
style: messageTheme.messageText.copyWith(
|
||||||
color: StreamChatTheme.of(context).accentColor,
|
color: StreamChatTheme.of(context).colorTheme.accentBlue,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
|
import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
|
||||||
import 'package:stream_chat_flutter/src/unread_indicator.dart';
|
import 'package:stream_chat_flutter/src/unread_indicator.dart';
|
||||||
|
|
||||||
|
import '../stream_chat_flutter.dart';
|
||||||
|
|
||||||
class StreamBackButton extends StatelessWidget {
|
class StreamBackButton extends StatelessWidget {
|
||||||
const StreamBackButton({
|
const StreamBackButton({
|
||||||
Key key,
|
Key key,
|
||||||
@@ -35,9 +37,7 @@ class StreamBackButton extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
child: StreamSvgIcon.left(
|
child: StreamSvgIcon.left(
|
||||||
size: 24,
|
size: 24,
|
||||||
color: Theme.of(context).brightness == Brightness.dark
|
color: StreamChatTheme.of(context).colorTheme.black,
|
||||||
? Colors.white
|
|
||||||
: Colors.black,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,193 +1,223 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:stream_chat/stream_chat.dart';
|
|
||||||
import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
|
|
||||||
import 'package:stream_chat_flutter/src/utils.dart';
|
|
||||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
|
||||||
|
|
||||||
|
import '../stream_chat_flutter.dart';
|
||||||
import 'channel_info.dart';
|
import 'channel_info.dart';
|
||||||
import 'channel_name.dart';
|
import 'option_list_tile.dart';
|
||||||
import 'stream_chat.dart';
|
|
||||||
import 'stream_chat_theme.dart';
|
|
||||||
import 'user_avatar.dart';
|
|
||||||
|
|
||||||
class ChannelBottomSheet extends StatelessWidget {
|
class ChannelBottomSheet extends StatefulWidget {
|
||||||
const ChannelBottomSheet({
|
final VoidCallback onViewInfoTap;
|
||||||
Key key,
|
|
||||||
}) : super(key: key);
|
ChannelBottomSheet({this.onViewInfoTap});
|
||||||
|
|
||||||
|
@override
|
||||||
|
_ChannelBottomSheetState createState() => _ChannelBottomSheetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ChannelBottomSheetState extends State<ChannelBottomSheet> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final channel = StreamChannel.of(context).channel;
|
var channel = StreamChannel.of(context).channel;
|
||||||
return SafeArea(
|
|
||||||
|
var members = channel.state.members;
|
||||||
|
|
||||||
|
var userAsMember =
|
||||||
|
members.firstWhere((e) => e.user.id == StreamChat.of(context).user.id);
|
||||||
|
var isOwner = userAsMember.role == 'owner';
|
||||||
|
|
||||||
|
return Material(
|
||||||
|
color: StreamChatTheme.of(context).colorTheme.white,
|
||||||
|
clipBehavior: Clip.antiAlias,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(16.0),
|
||||||
|
topRight: Radius.circular(16.0),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: ListView(
|
||||||
|
shrinkWrap: true,
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
height: 24.0,
|
||||||
|
),
|
||||||
|
Center(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
||||||
children: <Widget>[
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
vertical: 2.0,
|
|
||||||
),
|
|
||||||
child: Center(
|
|
||||||
child: StreamChannel(
|
|
||||||
showLoading: false,
|
|
||||||
channel: channel,
|
|
||||||
child: ChannelName(
|
child: ChannelName(
|
||||||
textStyle:
|
textStyle: StreamChatTheme.of(context).textTheme.headlineBold,
|
||||||
StreamChatTheme.of(context).channelPreviewTheme.title,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: 5.0,
|
||||||
),
|
),
|
||||||
Padding(
|
Center(
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
vertical: 2.0,
|
|
||||||
),
|
|
||||||
child: Center(
|
|
||||||
child: ChannelInfo(
|
child: ChannelInfo(
|
||||||
channel: channel,
|
showTypingIndicator: false,
|
||||||
|
channel: StreamChannel.of(context).channel,
|
||||||
textStyle:
|
textStyle:
|
||||||
StreamChatTheme.of(context).channelPreviewTheme.subtitle,
|
StreamChatTheme.of(context).channelPreviewTheme.subtitle,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: 17.0,
|
||||||
),
|
),
|
||||||
Padding(
|
if (channel.isDistinct && channel.memberCount == 2)
|
||||||
padding: const EdgeInsets.symmetric(
|
Column(
|
||||||
vertical: 15.0,
|
children: [
|
||||||
|
UserAvatar(
|
||||||
|
user: members
|
||||||
|
.firstWhere((e) => e.user.id != userAsMember.user.id)
|
||||||
|
.user,
|
||||||
|
constraints: BoxConstraints(
|
||||||
|
maxHeight: 64.0,
|
||||||
|
maxWidth: 64.0,
|
||||||
),
|
),
|
||||||
child: Center(
|
borderRadius: BorderRadius.circular(32.0),
|
||||||
child: StreamBuilder<List<Member>>(
|
onlineIndicatorConstraints:
|
||||||
stream: channel.state.membersStream.map((event) => event
|
BoxConstraints.tight(Size(16.0, 16.0)),
|
||||||
.where((m) => m.userId != StreamChat.of(context).user.id)
|
|
||||||
.toList()),
|
|
||||||
initialData: channel.state.members
|
|
||||||
.where((m) => m.userId != StreamChat.of(context).user.id)
|
|
||||||
.toList(),
|
|
||||||
builder: _buildMembers,
|
|
||||||
),
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: 6.0,
|
||||||
),
|
),
|
||||||
|
Text(
|
||||||
|
members
|
||||||
|
.firstWhere((e) => e.user.id != userAsMember.user.id)
|
||||||
|
.user
|
||||||
|
.name,
|
||||||
|
style: StreamChatTheme.of(context).textTheme.footnoteBold,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
Divider(),
|
],
|
||||||
if (channel.isGroup && !channel.isDistinct)
|
|
||||||
ListTile(
|
|
||||||
leading: StreamSvgIcon.userRemove(
|
|
||||||
size: 24,
|
|
||||||
color: Color(0xff7A7A7A),
|
|
||||||
),
|
),
|
||||||
title: Text(
|
if (!(channel.isDistinct && channel.memberCount == 2))
|
||||||
'Leave Group',
|
Container(
|
||||||
style: TextStyle(fontWeight: FontWeight.bold),
|
height: 94.0,
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: ListView.builder(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
itemCount: members.length,
|
||||||
|
shrinkWrap: true,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
UserAvatar(
|
||||||
|
user: members[index].user,
|
||||||
|
constraints: BoxConstraints(
|
||||||
|
maxHeight: 64.0,
|
||||||
|
maxWidth: 64.0,
|
||||||
),
|
),
|
||||||
onTap: () async {
|
borderRadius: BorderRadius.circular(32.0),
|
||||||
final confirm = await showConfirmationDialog(
|
),
|
||||||
context,
|
SizedBox(
|
||||||
title: 'Leave Group',
|
height: 6.0,
|
||||||
okText: 'LEAVE',
|
),
|
||||||
question: 'Are you sure you want to leave this group?',
|
Text(
|
||||||
cancelText: 'CANCEL',
|
members[index].user.name,
|
||||||
icon: StreamSvgIcon.userRemove(
|
style: StreamChatTheme.of(context)
|
||||||
color: Colors.red,
|
.textTheme
|
||||||
|
.footnoteBold,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
if (confirm == true) {
|
|
||||||
await channel
|
|
||||||
.removeMembers([StreamChat.of(context).user.id]);
|
|
||||||
Navigator.pop(context);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
if ([
|
|
||||||
'admin',
|
|
||||||
'owner',
|
|
||||||
].contains(channel.state.members
|
|
||||||
.firstWhere((m) => m.userId == channel.client.state.user.id,
|
|
||||||
orElse: () => null)
|
|
||||||
?.role))
|
|
||||||
ListTile(
|
|
||||||
leading: StreamSvgIcon.delete(
|
|
||||||
color: Color(0xFFFF3742),
|
|
||||||
size: 24,
|
|
||||||
),
|
),
|
||||||
title: Text(
|
SizedBox(
|
||||||
'Delete chat',
|
height: 24.0,
|
||||||
style: TextStyle(
|
),
|
||||||
color: Color(0xFFFF3742),
|
OptionListTile(
|
||||||
|
leading: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||||
|
child: StreamSvgIcon.user(
|
||||||
|
color: StreamChatTheme.of(context).colorTheme.grey,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
title: 'View Info',
|
||||||
|
onTap: widget.onViewInfoTap,
|
||||||
|
),
|
||||||
|
if (!channel.isDistinct)
|
||||||
|
OptionListTile(
|
||||||
|
leading: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||||
|
child: StreamSvgIcon.userRemove(
|
||||||
|
color: StreamChatTheme.of(context).colorTheme.grey,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
title: 'Leave Group',
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
|
_showLeaveDialog();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
if (isOwner)
|
||||||
|
OptionListTile(
|
||||||
|
leading: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||||
|
child: StreamSvgIcon.delete(
|
||||||
|
color: StreamChatTheme.of(context).colorTheme.accentRed,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
title: 'Delete Conversation',
|
||||||
|
titleColor: StreamChatTheme.of(context).colorTheme.accentRed,
|
||||||
|
onTap: () async {
|
||||||
|
_showDeleteDialog();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
OptionListTile(
|
||||||
|
leading: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||||
|
child: StreamSvgIcon.close_small(
|
||||||
|
color: StreamChatTheme.of(context).colorTheme.grey,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
title: 'Cancel',
|
||||||
|
onTap: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showDeleteDialog() async {
|
||||||
final res = await showConfirmationDialog(
|
final res = await showConfirmationDialog(
|
||||||
context,
|
context,
|
||||||
title: 'Delete Conversation',
|
title: 'Delete Conversation',
|
||||||
okText: 'DELETE',
|
okText: 'DELETE',
|
||||||
question:
|
question: 'Are you sure you want to delete this conversation?',
|
||||||
'Are you sure you want to delete this conversation?',
|
|
||||||
cancelText: 'CANCEL',
|
cancelText: 'CANCEL',
|
||||||
icon: StreamSvgIcon.delete(
|
icon: StreamSvgIcon.delete(
|
||||||
color: Colors.red,
|
color: StreamChatTheme.of(context).colorTheme.accentRed,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
var channel = StreamChannel.of(context).channel;
|
var channel = StreamChannel.of(context).channel;
|
||||||
if (res == true) {
|
if (res == true) {
|
||||||
await channel.delete().then((value) {
|
await channel.delete();
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildMembers(
|
void _showLeaveDialog() async {
|
||||||
BuildContext context,
|
final res = await showConfirmationDialog(
|
||||||
AsyncSnapshot<List<Member>> snapshot,
|
context,
|
||||||
) {
|
title: 'Leave conversation',
|
||||||
if (snapshot.data.isEmpty) {
|
okText: 'LEAVE',
|
||||||
return SizedBox();
|
question: 'Are you sure you want to leave this conversation?',
|
||||||
}
|
cancelText: 'CANCEL',
|
||||||
|
icon: StreamSvgIcon.userRemove(
|
||||||
return Container(
|
color: StreamChatTheme.of(context).colorTheme.accentRed,
|
||||||
height: 83,
|
|
||||||
child: ListView(
|
|
||||||
padding: EdgeInsets.only(
|
|
||||||
left: (MediaQuery.of(context).size.width / 2) - 48,
|
|
||||||
),
|
|
||||||
scrollDirection: Axis.horizontal,
|
|
||||||
children: snapshot.data.map((m) {
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 8.0,
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
children: <Widget>[
|
|
||||||
UserAvatar(
|
|
||||||
showOnlineStatus: true,
|
|
||||||
user: m.user,
|
|
||||||
borderRadius: BorderRadius.circular(32),
|
|
||||||
constraints: BoxConstraints.tight(
|
|
||||||
Size.square(64),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(top: 5.0),
|
|
||||||
child: Text(
|
|
||||||
m.user.name?.split(' ')?.elementAt(0),
|
|
||||||
style: StreamChatTheme.of(context)
|
|
||||||
.channelPreviewTheme
|
|
||||||
.title
|
|
||||||
.copyWith(
|
|
||||||
fontSize: 12,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}).toList(),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
var channel = StreamChannel.of(context).channel;
|
||||||
|
if (res == true) {
|
||||||
|
await channel.removeMembers([StreamChat.of(context).user.id]);
|
||||||
|
Navigator.pop(context);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,182 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:stream_chat_flutter/src/lazy_load_scroll_view.dart';
|
||||||
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||||
|
|
||||||
|
class ChannelFileDisplayScreen extends StatefulWidget {
|
||||||
|
/// The sorting used for the channels matching the filters.
|
||||||
|
/// Sorting is based on field and direction, multiple sorting options can be provided.
|
||||||
|
/// You can sort based on last_updated, last_message_at, updated_at, created_at or member_count.
|
||||||
|
/// Direction can be ascending or descending.
|
||||||
|
final List<SortOption> sortOptions;
|
||||||
|
|
||||||
|
/// Pagination parameters
|
||||||
|
/// limit: the number of users to return (max is 30)
|
||||||
|
/// offset: the offset (max is 1000)
|
||||||
|
/// message_limit: how many messages should be included to each channel
|
||||||
|
final PaginationParams paginationParams;
|
||||||
|
|
||||||
|
/// The builder used when the file list is empty.
|
||||||
|
final WidgetBuilder emptyBuilder;
|
||||||
|
|
||||||
|
const ChannelFileDisplayScreen({
|
||||||
|
this.sortOptions,
|
||||||
|
this.paginationParams,
|
||||||
|
this.emptyBuilder,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
_ChannelFileDisplayScreenState createState() =>
|
||||||
|
_ChannelFileDisplayScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ChannelFileDisplayScreenState extends State<ChannelFileDisplayScreen> {
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
final messageSearchBloc = MessageSearchBloc.of(context);
|
||||||
|
messageSearchBloc.search(
|
||||||
|
filter: {
|
||||||
|
'cid': {
|
||||||
|
r'$in': ['messaging:${StreamChannel.of(context).channel.id}']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
messageFilter: {
|
||||||
|
'attachments.type': {
|
||||||
|
r'$in': ['file'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
sort: widget.sortOptions,
|
||||||
|
pagination: widget.paginationParams,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: StreamChatTheme.of(context).colorTheme.white,
|
||||||
|
appBar: AppBar(
|
||||||
|
brightness: Theme.of(context).brightness,
|
||||||
|
elevation: 1,
|
||||||
|
centerTitle: true,
|
||||||
|
title: Text(
|
||||||
|
'Files',
|
||||||
|
style: TextStyle(
|
||||||
|
color: StreamChatTheme.of(context).colorTheme.black,
|
||||||
|
fontSize: 16.0),
|
||||||
|
),
|
||||||
|
leading: Center(
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
child: StreamSvgIcon.left(
|
||||||
|
color: StreamChatTheme.of(context).colorTheme.black,
|
||||||
|
size: 24.0,
|
||||||
|
),
|
||||||
|
width: 24.0,
|
||||||
|
height: 24.0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
backgroundColor: StreamChatTheme.of(context).colorTheme.white,
|
||||||
|
),
|
||||||
|
body: _buildMediaGrid(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildMediaGrid() {
|
||||||
|
final messageSearchBloc = MessageSearchBloc.of(context);
|
||||||
|
|
||||||
|
return StreamBuilder<List<GetMessageResponse>>(
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.data == null) {
|
||||||
|
return Center(
|
||||||
|
child: const CircularProgressIndicator(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (snapshot.data.isEmpty) {
|
||||||
|
if (widget.emptyBuilder != null) {
|
||||||
|
return widget.emptyBuilder(context);
|
||||||
|
}
|
||||||
|
return Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
StreamSvgIcon.files(
|
||||||
|
size: 136.0,
|
||||||
|
color: StreamChatTheme.of(context).colorTheme.greyGainsboro,
|
||||||
|
),
|
||||||
|
SizedBox(height: 16.0),
|
||||||
|
Text(
|
||||||
|
'No Files',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14.0,
|
||||||
|
color: StreamChatTheme.of(context).colorTheme.black,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 8.0),
|
||||||
|
Text(
|
||||||
|
'Files sent in this chat will appear here',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14.0,
|
||||||
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.black
|
||||||
|
.withOpacity(0.5),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final media = <Attachment, Message>{};
|
||||||
|
|
||||||
|
for (var item in snapshot.data) {
|
||||||
|
item.message.attachments.where((e) => e.type == 'file').forEach((e) {
|
||||||
|
media[e] = item.message;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return LazyLoadScrollView(
|
||||||
|
onEndOfPage: () => messageSearchBloc.search(
|
||||||
|
filter: {
|
||||||
|
'cid': {
|
||||||
|
r'$in': ['messaging:${StreamChannel.of(context).channel.id}']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
messageFilter: {
|
||||||
|
'attachments.type': {
|
||||||
|
r'$in': ['file']
|
||||||
|
},
|
||||||
|
},
|
||||||
|
sort: widget.sortOptions,
|
||||||
|
pagination: widget.paginationParams.copyWith(
|
||||||
|
offset: messageSearchBloc.messageResponses?.length ?? 0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: ListView.builder(
|
||||||
|
itemBuilder: (context, position) {
|
||||||
|
var channel = StreamChannel.of(context).channel;
|
||||||
|
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.all(1.0),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: FileAttachment(
|
||||||
|
attachment: media.keys.toList()[position],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
itemCount: media.length,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
stream: messageSearchBloc.messagesStream,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,7 +8,6 @@ import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
|
|||||||
import '../stream_chat_flutter.dart';
|
import '../stream_chat_flutter.dart';
|
||||||
import './channel_name.dart';
|
import './channel_name.dart';
|
||||||
import 'channel_image.dart';
|
import 'channel_image.dart';
|
||||||
import 'chat_info_screen.dart';
|
|
||||||
import 'stream_channel.dart';
|
import 'stream_channel.dart';
|
||||||
|
|
||||||
/// 
|
/// 
|
||||||
@@ -99,33 +98,7 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
padding: const EdgeInsets.only(right: 10.0),
|
padding: const EdgeInsets.only(right: 10.0),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: ChannelImage(
|
child: ChannelImage(
|
||||||
onTap: onImageTap ??
|
onTap: onImageTap,
|
||||||
() async {
|
|
||||||
if (channel.memberCount == 2 && channel.isDistinct) {
|
|
||||||
final currentUser = StreamChat.of(context).user;
|
|
||||||
final otherUser = channel.state.members.firstWhere(
|
|
||||||
(element) => element.user.id != currentUser.id,
|
|
||||||
orElse: () => null,
|
|
||||||
);
|
|
||||||
if (otherUser != null) {
|
|
||||||
final pop = await Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => StreamChannel(
|
|
||||||
channel: channel,
|
|
||||||
child: ChatInfoScreen(
|
|
||||||
user: otherUser.user,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (pop == true) {
|
|
||||||
Navigator.pop(context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -52,6 +52,9 @@ class ChannelImage extends StatelessWidget {
|
|||||||
this.onTap,
|
this.onTap,
|
||||||
this.showOnlineStatus = true,
|
this.showOnlineStatus = true,
|
||||||
this.borderRadius,
|
this.borderRadius,
|
||||||
|
this.selected = false,
|
||||||
|
this.selectionColor,
|
||||||
|
this.selectionThickness = 4,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
final BorderRadius borderRadius;
|
final BorderRadius borderRadius;
|
||||||
@@ -67,6 +70,12 @@ class ChannelImage extends StatelessWidget {
|
|||||||
|
|
||||||
final bool showOnlineStatus;
|
final bool showOnlineStatus;
|
||||||
|
|
||||||
|
final bool selected;
|
||||||
|
|
||||||
|
final Color selectionColor;
|
||||||
|
|
||||||
|
final double selectionThickness;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final streamChat = StreamChat.of(context);
|
final streamChat = StreamChat.of(context);
|
||||||
@@ -94,11 +103,11 @@ class ChannelImage extends StatelessWidget {
|
|||||||
.channelPreviewTheme
|
.channelPreviewTheme
|
||||||
.avatarTheme
|
.avatarTheme
|
||||||
.constraints,
|
.constraints,
|
||||||
onTap: onTap != null
|
onTap: onTap != null ? (_) => onTap() : null,
|
||||||
? (_) {
|
selected: selected,
|
||||||
onTap();
|
selectionColor: selectionColor ??
|
||||||
}
|
StreamChatTheme.of(context).colorTheme.accentBlue,
|
||||||
: null,
|
selectionThickness: selectionThickness,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -111,16 +120,21 @@ class ChannelImage extends StatelessWidget {
|
|||||||
.toList();
|
.toList();
|
||||||
return GroupImage(
|
return GroupImage(
|
||||||
images: images,
|
images: images,
|
||||||
|
borderRadius: borderRadius,
|
||||||
constraints: constraints ??
|
constraints: constraints ??
|
||||||
StreamChatTheme.of(context)
|
StreamChatTheme.of(context)
|
||||||
.channelPreviewTheme
|
.channelPreviewTheme
|
||||||
.avatarTheme
|
.avatarTheme
|
||||||
.constraints,
|
.constraints,
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
|
selected: selected,
|
||||||
|
selectionColor: selectionColor ??
|
||||||
|
StreamChatTheme.of(context).colorTheme.accentBlue,
|
||||||
|
selectionThickness: selectionThickness,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ClipRRect(
|
Widget child = ClipRRect(
|
||||||
borderRadius: borderRadius ??
|
borderRadius: borderRadius ??
|
||||||
StreamChatTheme.of(context)
|
StreamChatTheme.of(context)
|
||||||
.channelPreviewTheme
|
.channelPreviewTheme
|
||||||
@@ -133,7 +147,7 @@ class ChannelImage extends StatelessWidget {
|
|||||||
.avatarTheme
|
.avatarTheme
|
||||||
.constraints,
|
.constraints,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: StreamChatTheme.of(context).accentColor,
|
color: StreamChatTheme.of(context).colorTheme.accentBlue,
|
||||||
),
|
),
|
||||||
child: Stack(
|
child: Stack(
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
@@ -149,7 +163,9 @@ class ChannelImage extends StatelessWidget {
|
|||||||
? snapshot.data['name'][0]
|
? snapshot.data['name'][0]
|
||||||
: '',
|
: '',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.white,
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.white,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -169,6 +185,30 @@ class ChannelImage extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
if (selected) {
|
||||||
|
child = ClipRRect(
|
||||||
|
borderRadius: (borderRadius ??
|
||||||
|
StreamChatTheme.of(context)
|
||||||
|
.ownMessageTheme
|
||||||
|
.avatarTheme
|
||||||
|
.borderRadius) +
|
||||||
|
BorderRadius.circular(selectionThickness),
|
||||||
|
child: Container(
|
||||||
|
constraints: constraints ??
|
||||||
|
StreamChatTheme.of(context)
|
||||||
|
.ownMessageTheme
|
||||||
|
.avatarTheme
|
||||||
|
.constraints,
|
||||||
|
color: selectionColor ??
|
||||||
|
StreamChatTheme.of(context).colorTheme.accentBlue,
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.all(selectionThickness),
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return child;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ class ChannelInfo extends StatelessWidget {
|
|||||||
child: Text(
|
child: Text(
|
||||||
'Try Again',
|
'Try Again',
|
||||||
style: textStyle.copyWith(
|
style: textStyle.copyWith(
|
||||||
color: Color(0xFF006CFF),
|
color: StreamChatTheme.of(context).colorTheme.accentBlue,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
var color;
|
var color;
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case ConnectionStatus.connected:
|
case ConnectionStatus.connected:
|
||||||
color = Color(0xFF006CFF);
|
color = StreamChatTheme.of(context).colorTheme.accentBlue;
|
||||||
break;
|
break;
|
||||||
case ConnectionStatus.connecting:
|
case ConnectionStatus.connecting:
|
||||||
color = Colors.grey;
|
color = Colors.grey;
|
||||||
@@ -144,13 +144,8 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
|
|
||||||
Widget _buildConnectedTitleState(BuildContext context) => Text(
|
Widget _buildConnectedTitleState(BuildContext context) => Text(
|
||||||
'Stream Chat',
|
'Stream Chat',
|
||||||
style: StreamChatTheme.of(context)
|
style: StreamChatTheme.of(context).textTheme.headlineBold.copyWith(
|
||||||
.channelTheme
|
color: StreamChatTheme.of(context).colorTheme.black,
|
||||||
.channelHeaderTheme
|
|
||||||
.title
|
|
||||||
.copyWith(
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -210,7 +205,7 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
.copyWith(
|
.copyWith(
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: Color(0xFF006CFF),
|
color: StreamChatTheme.of(context).colorTheme.accentBlue,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import 'package:stream_chat/stream_chat.dart';
|
|||||||
import 'package:stream_chat_flutter/src/channels_bloc.dart';
|
import 'package:stream_chat_flutter/src/channels_bloc.dart';
|
||||||
import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
|
import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
|
||||||
import 'package:stream_chat_flutter/src/utils.dart';
|
import 'package:stream_chat_flutter/src/utils.dart';
|
||||||
|
import 'dart:ui' as ui;
|
||||||
|
|
||||||
import '../stream_chat_flutter.dart';
|
import '../stream_chat_flutter.dart';
|
||||||
import 'channel_bottom_sheet.dart';
|
import 'channel_bottom_sheet.dart';
|
||||||
@@ -22,6 +23,8 @@ typedef ChannelTapCallback = void Function(Channel, Widget);
|
|||||||
/// Builder used to create a custom [ChannelPreview] from a [Channel]
|
/// Builder used to create a custom [ChannelPreview] from a [Channel]
|
||||||
typedef ChannelPreviewBuilder = Widget Function(BuildContext, Channel);
|
typedef ChannelPreviewBuilder = Widget Function(BuildContext, Channel);
|
||||||
|
|
||||||
|
typedef ViewInfoCallback = void Function(Channel);
|
||||||
|
|
||||||
/// 
|
/// 
|
||||||
/// 
|
/// 
|
||||||
///
|
///
|
||||||
@@ -74,6 +77,10 @@ class ChannelListView extends StatefulWidget {
|
|||||||
this.onStartChatPressed,
|
this.onStartChatPressed,
|
||||||
this.swipeToAction = false,
|
this.swipeToAction = false,
|
||||||
this.pullToRefresh = true,
|
this.pullToRefresh = true,
|
||||||
|
this.crossAxisCount = 1,
|
||||||
|
this.padding,
|
||||||
|
this.selectedChannels = const [],
|
||||||
|
this.onViewInfoTap,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
/// The builder that will be used in case of error
|
/// The builder that will be used in case of error
|
||||||
@@ -134,6 +141,16 @@ class ChannelListView extends StatefulWidget {
|
|||||||
/// Callback used in the default empty list widget
|
/// Callback used in the default empty list widget
|
||||||
final VoidCallback onStartChatPressed;
|
final VoidCallback onStartChatPressed;
|
||||||
|
|
||||||
|
/// The number of children in the cross axis.
|
||||||
|
final int crossAxisCount;
|
||||||
|
|
||||||
|
/// The amount of space by which to inset the children.
|
||||||
|
final EdgeInsetsGeometry padding;
|
||||||
|
|
||||||
|
final List<Channel> selectedChannels;
|
||||||
|
|
||||||
|
final ViewInfoCallback onViewInfoTap;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_ChannelListViewState createState() => _ChannelListViewState();
|
_ChannelListViewState createState() => _ChannelListViewState();
|
||||||
}
|
}
|
||||||
@@ -141,6 +158,7 @@ class ChannelListView extends StatefulWidget {
|
|||||||
class _ChannelListViewState extends State<ChannelListView>
|
class _ChannelListViewState extends State<ChannelListView>
|
||||||
with WidgetsBindingObserver {
|
with WidgetsBindingObserver {
|
||||||
final ScrollController _scrollController = ScrollController();
|
final ScrollController _scrollController = ScrollController();
|
||||||
|
final SlidableController _slideController = SlidableController();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -203,16 +221,18 @@ class _ChannelListViewState extends State<ChannelListView>
|
|||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8.0),
|
||||||
child: StreamSvgIcon.message(
|
child: StreamSvgIcon.message(
|
||||||
size: 136,
|
size: 136,
|
||||||
color: Color(0xffDBDBDB),
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.greyGainsboro,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8.0),
|
||||||
child: Text(
|
child: Text(
|
||||||
'Let’s start chatting!',
|
'Let’s start chatting!',
|
||||||
style: TextStyle(
|
style: StreamChatTheme.of(context)
|
||||||
fontSize: 16,
|
.textTheme
|
||||||
),
|
.headline,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Padding(
|
Padding(
|
||||||
@@ -223,9 +243,13 @@ class _ChannelListViewState extends State<ChannelListView>
|
|||||||
child: Text(
|
child: Text(
|
||||||
'How about sending your first message to a friend?',
|
'How about sending your first message to a friend?',
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
style: StreamChatTheme.of(context)
|
||||||
fontSize: 14,
|
.textTheme
|
||||||
color: Color(0xff7A7A7A),
|
.body
|
||||||
|
.copyWith(
|
||||||
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.grey,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -242,10 +266,13 @@ class _ChannelListViewState extends State<ChannelListView>
|
|||||||
onPressed: widget.onStartChatPressed,
|
onPressed: widget.onStartChatPressed,
|
||||||
child: Text(
|
child: Text(
|
||||||
'Start a chat',
|
'Start a chat',
|
||||||
style: TextStyle(
|
style: StreamChatTheme.of(context)
|
||||||
color:
|
.textTheme
|
||||||
StreamChatTheme.of(context).accentColor,
|
.bodyBold
|
||||||
fontWeight: FontWeight.bold,
|
.copyWith(
|
||||||
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.accentBlue,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -259,22 +286,35 @@ class _ChannelListViewState extends State<ChannelListView>
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (channels.isNotEmpty) {
|
if (channels.isNotEmpty) {
|
||||||
child = ListView.custom(
|
if (widget.crossAxisCount > 1) {
|
||||||
|
child = GridView.builder(
|
||||||
|
padding: widget.padding,
|
||||||
|
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
|
crossAxisCount: widget.crossAxisCount),
|
||||||
|
itemCount: channels.length,
|
||||||
physics: AlwaysScrollableScrollPhysics(),
|
physics: AlwaysScrollableScrollPhysics(),
|
||||||
controller: _scrollController,
|
controller: _scrollController,
|
||||||
childrenDelegate: SliverChildBuilderDelegate(
|
itemBuilder: (context, index) {
|
||||||
(context, i) {
|
return _gridItemBuilder(context, index, channels);
|
||||||
return _itemBuilder(context, i, channels);
|
|
||||||
},
|
},
|
||||||
childCount: (channels.length * 2) + 1,
|
|
||||||
findChildIndexCallback: (key) {
|
|
||||||
final ValueKey<String> valueKey = key;
|
|
||||||
final index = channels.indexWhere(
|
|
||||||
(channel) => 'CHANNEL-${channel.id}' == valueKey.value);
|
|
||||||
return index != -1 ? (index * 2) : null;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
child = ListView.separated(
|
||||||
|
padding: widget.padding,
|
||||||
|
physics: AlwaysScrollableScrollPhysics(),
|
||||||
|
itemCount:
|
||||||
|
channels.isNotEmpty ? channels.length + 1 : channels.length,
|
||||||
|
separatorBuilder: (_, index) {
|
||||||
|
if (widget.separatorBuilder != null) {
|
||||||
|
return widget.separatorBuilder(context, index);
|
||||||
|
}
|
||||||
|
return _separatorBuilder(context, index);
|
||||||
|
},
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return _listItemBuilder(context, index, channels);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -288,16 +328,19 @@ class _ChannelListViewState extends State<ChannelListView>
|
|||||||
|
|
||||||
Widget _buildLoadingWidget() {
|
Widget _buildLoadingWidget() {
|
||||||
return ListView(
|
return ListView(
|
||||||
|
padding: widget.padding,
|
||||||
physics: AlwaysScrollableScrollPhysics(),
|
physics: AlwaysScrollableScrollPhysics(),
|
||||||
children: List.generate(
|
children: List.generate(
|
||||||
25,
|
25,
|
||||||
(i) {
|
(i) {
|
||||||
|
if (widget.crossAxisCount == 1) {
|
||||||
if (i % 2 != 0) {
|
if (i % 2 != 0) {
|
||||||
if (widget.separatorBuilder != null) {
|
if (widget.separatorBuilder != null) {
|
||||||
return widget.separatorBuilder(context, i);
|
return widget.separatorBuilder(context, i);
|
||||||
}
|
}
|
||||||
return _separatorBuilder(context, i);
|
return _separatorBuilder(context, i);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return _buildLoadingItem();
|
return _buildLoadingItem();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -305,13 +348,43 @@ class _ChannelListViewState extends State<ChannelListView>
|
|||||||
}
|
}
|
||||||
|
|
||||||
Shimmer _buildLoadingItem() {
|
Shimmer _buildLoadingItem() {
|
||||||
|
if (widget.crossAxisCount > 1) {
|
||||||
return Shimmer.fromColors(
|
return Shimmer.fromColors(
|
||||||
baseColor: Color(0xffE5E5E5),
|
baseColor: StreamChatTheme.of(context).colorTheme.greyGainsboro,
|
||||||
highlightColor: Color(0xffffffff),
|
highlightColor: StreamChatTheme.of(context).colorTheme.whiteSmoke,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
SizedBox(height: 4.0),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
|
children: [
|
||||||
|
for (int i = 0; i < widget.crossAxisCount; i++)
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
constraints: BoxConstraints.tightFor(
|
||||||
|
height: 70,
|
||||||
|
width: 70,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: 16.0,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return Shimmer.fromColors(
|
||||||
|
baseColor: StreamChatTheme.of(context).colorTheme.greyGainsboro,
|
||||||
|
highlightColor: StreamChatTheme.of(context).colorTheme.whiteSmoke,
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
leading: Container(
|
leading: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.white,
|
color: StreamChatTheme.of(context).colorTheme.white,
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
),
|
),
|
||||||
constraints: BoxConstraints.tightFor(
|
constraints: BoxConstraints.tightFor(
|
||||||
@@ -319,11 +392,15 @@ class _ChannelListViewState extends State<ChannelListView>
|
|||||||
width: 40,
|
width: 40,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
contentPadding: const EdgeInsets.only(
|
||||||
|
left: 8,
|
||||||
|
right: 8,
|
||||||
|
),
|
||||||
title: Align(
|
title: Align(
|
||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.white,
|
color: StreamChatTheme.of(context).colorTheme.white,
|
||||||
borderRadius: BorderRadius.circular(11),
|
borderRadius: BorderRadius.circular(11),
|
||||||
),
|
),
|
||||||
constraints: BoxConstraints.tightFor(
|
constraints: BoxConstraints.tightFor(
|
||||||
@@ -333,12 +410,13 @@ class _ChannelListViewState extends State<ChannelListView>
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
subtitle: Row(
|
subtitle: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Align(
|
Align(
|
||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.white,
|
color: StreamChatTheme.of(context).colorTheme.white,
|
||||||
borderRadius: BorderRadius.circular(11),
|
borderRadius: BorderRadius.circular(11),
|
||||||
),
|
),
|
||||||
constraints: BoxConstraints.tightFor(
|
constraints: BoxConstraints.tightFor(
|
||||||
@@ -350,7 +428,7 @@ class _ChannelListViewState extends State<ChannelListView>
|
|||||||
Container(
|
Container(
|
||||||
margin: const EdgeInsets.only(left: 16),
|
margin: const EdgeInsets.only(left: 16),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.white,
|
color: StreamChatTheme.of(context).colorTheme.white,
|
||||||
borderRadius: BorderRadius.circular(11),
|
borderRadius: BorderRadius.circular(11),
|
||||||
),
|
),
|
||||||
constraints: BoxConstraints.tightFor(
|
constraints: BoxConstraints.tightFor(
|
||||||
@@ -363,6 +441,7 @@ class _ChannelListViewState extends State<ChannelListView>
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildErrorWidget(
|
Widget _buildErrorWidget(
|
||||||
AsyncSnapshot<List<Channel>> snapshot,
|
AsyncSnapshot<List<Channel>> snapshot,
|
||||||
@@ -428,20 +507,10 @@ class _ChannelListViewState extends State<ChannelListView>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _itemBuilder(context, int i, List<Channel> channels) {
|
Widget _listItemBuilder(BuildContext context, int i, List<Channel> channels) {
|
||||||
if (i % 2 != 0) {
|
|
||||||
if (widget.separatorBuilder != null) {
|
|
||||||
return widget.separatorBuilder(context, i);
|
|
||||||
}
|
|
||||||
return _separatorBuilder(context, i);
|
|
||||||
}
|
|
||||||
|
|
||||||
i = i ~/ 2;
|
|
||||||
|
|
||||||
final channelsProvider = ChannelsBloc.of(context);
|
final channelsProvider = ChannelsBloc.of(context);
|
||||||
if (i < channels.length) {
|
if (i < channels.length) {
|
||||||
final channel = channels[i];
|
final channel = channels[i];
|
||||||
|
|
||||||
ChannelTapCallback onTap;
|
ChannelTapCallback onTap;
|
||||||
if (widget.onChannelTap != null) {
|
if (widget.onChannelTap != null) {
|
||||||
onTap = widget.onChannelTap;
|
onTap = widget.onChannelTap;
|
||||||
@@ -493,10 +562,9 @@ class _ChannelListViewState extends State<ChannelListView>
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
final backgroundColor =
|
final backgroundColor =
|
||||||
Theme.of(context).brightness == Brightness.light
|
StreamChatTheme.of(context).colorTheme.whiteSmoke;
|
||||||
? Color(0xffEBEBEB)
|
|
||||||
: Color(0xff141414);
|
|
||||||
child = Slidable(
|
child = Slidable(
|
||||||
|
controller: _slideController,
|
||||||
enabled: widget.swipeToAction,
|
enabled: widget.swipeToAction,
|
||||||
actionPane: SlidableBehindActionPane(),
|
actionPane: SlidableBehindActionPane(),
|
||||||
actionExtentRatio: 0.12,
|
actionExtentRatio: 0.12,
|
||||||
@@ -517,7 +585,11 @@ class _ChannelListViewState extends State<ChannelListView>
|
|||||||
context: context,
|
context: context,
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return StreamChannel(
|
return StreamChannel(
|
||||||
child: ChannelBottomSheet(),
|
child: ChannelBottomSheet(
|
||||||
|
onViewInfoTap: () {
|
||||||
|
widget.onViewInfoTap(channel);
|
||||||
|
},
|
||||||
|
),
|
||||||
channel: channel,
|
channel: channel,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -535,7 +607,7 @@ class _ChannelListViewState extends State<ChannelListView>
|
|||||||
IconSlideAction(
|
IconSlideAction(
|
||||||
color: backgroundColor,
|
color: backgroundColor,
|
||||||
iconWidget: StreamSvgIcon.delete(
|
iconWidget: StreamSvgIcon.delete(
|
||||||
color: Color(0xFFFF3742),
|
color: StreamChatTheme.of(context).colorTheme.accentRed,
|
||||||
),
|
),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
final res = await showConfirmationDialog(
|
final res = await showConfirmationDialog(
|
||||||
@@ -546,7 +618,9 @@ class _ChannelListViewState extends State<ChannelListView>
|
|||||||
'Are you sure you want to delete this conversation?',
|
'Are you sure you want to delete this conversation?',
|
||||||
cancelText: 'CANCEL',
|
cancelText: 'CANCEL',
|
||||||
icon: StreamSvgIcon.delete(
|
icon: StreamSvgIcon.delete(
|
||||||
color: Color(0xFFFF3742),
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.accentRed,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
if (res == true) {
|
if (res == true) {
|
||||||
@@ -556,7 +630,7 @@ class _ChannelListViewState extends State<ChannelListView>
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
child: Container(
|
child: Container(
|
||||||
color: StreamChatTheme.of(context).backgroundColor,
|
color: StreamChatTheme.of(context).colorTheme.whiteSnow,
|
||||||
child: ChannelPreview(
|
child: ChannelPreview(
|
||||||
onLongPress: widget.onChannelLongPress,
|
onLongPress: widget.onChannelLongPress,
|
||||||
channel: channel,
|
channel: channel,
|
||||||
@@ -581,6 +655,47 @@ class _ChannelListViewState extends State<ChannelListView>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _gridItemBuilder(BuildContext context, int i, List<Channel> channels) {
|
||||||
|
var channel = channels[i];
|
||||||
|
|
||||||
|
var selected = widget.selectedChannels.contains(channel);
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
key: ValueKey<String>('CHANNEL-${channel.id}'),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
ChannelImage(
|
||||||
|
channel: channel,
|
||||||
|
borderRadius: BorderRadius.circular(32),
|
||||||
|
selected: selected,
|
||||||
|
constraints: BoxConstraints.tightFor(
|
||||||
|
width: 64,
|
||||||
|
height: 64,
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
widget.onChannelTap(channel, null);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
SizedBox(height: 7),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
|
child: StreamChannel(
|
||||||
|
child: ChannelName(
|
||||||
|
textStyle: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
channel: channel,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildQueryProgressIndicator(
|
Widget _buildQueryProgressIndicator(
|
||||||
context,
|
context,
|
||||||
ChannelsBlocState channelsProvider,
|
ChannelsBlocState channelsProvider,
|
||||||
@@ -591,7 +706,10 @@ class _ChannelListViewState extends State<ChannelListView>
|
|||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (snapshot.hasError) {
|
if (snapshot.hasError) {
|
||||||
return Container(
|
return Container(
|
||||||
color: Color(0xffd0021B).withAlpha(26),
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.accentRed
|
||||||
|
.withOpacity(.2),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 16.0),
|
padding: const EdgeInsets.symmetric(vertical: 16.0),
|
||||||
child: Center(
|
child: Center(
|
||||||
@@ -611,11 +729,17 @@ class _ChannelListViewState extends State<ChannelListView>
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _separatorBuilder(context, i) {
|
Widget _separatorBuilder(context, i) {
|
||||||
return Container(
|
var effect = StreamChatTheme.of(context).colorTheme.borderTop;
|
||||||
|
|
||||||
|
return BackdropFilter(
|
||||||
|
filter: ui.ImageFilter.blur(
|
||||||
|
sigmaX: effect.sigmaX,
|
||||||
|
sigmaY: effect.sigmaY,
|
||||||
|
),
|
||||||
|
child: Container(
|
||||||
height: 1,
|
height: 1,
|
||||||
color: Theme.of(context).brightness == Brightness.dark
|
color: effect.color.withOpacity(0.08),
|
||||||
? Colors.white.withOpacity(0.1)
|
),
|
||||||
: Colors.black.withOpacity(0.1),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,231 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:stream_chat_flutter/src/lazy_load_scroll_view.dart';
|
||||||
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||||
|
import 'package:video_player/video_player.dart';
|
||||||
|
|
||||||
|
class ChannelMediaDisplayScreen extends StatefulWidget {
|
||||||
|
/// The sorting used for the channels matching the filters.
|
||||||
|
/// Sorting is based on field and direction, multiple sorting options can be provided.
|
||||||
|
/// You can sort based on last_updated, last_message_at, updated_at, created_at or member_count.
|
||||||
|
/// Direction can be ascending or descending.
|
||||||
|
final List<SortOption> sortOptions;
|
||||||
|
|
||||||
|
/// Pagination parameters
|
||||||
|
/// limit: the number of users to return (max is 30)
|
||||||
|
/// offset: the offset (max is 1000)
|
||||||
|
/// message_limit: how many messages should be included to each channel
|
||||||
|
final PaginationParams paginationParams;
|
||||||
|
|
||||||
|
/// The builder used when the file list is empty.
|
||||||
|
final WidgetBuilder emptyBuilder;
|
||||||
|
|
||||||
|
final ShowMessageCallback onShowMessage;
|
||||||
|
|
||||||
|
const ChannelMediaDisplayScreen({
|
||||||
|
this.sortOptions,
|
||||||
|
this.paginationParams,
|
||||||
|
this.emptyBuilder,
|
||||||
|
this.onShowMessage,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
_ChannelMediaDisplayScreenState createState() =>
|
||||||
|
_ChannelMediaDisplayScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ChannelMediaDisplayScreenState extends State<ChannelMediaDisplayScreen> {
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
final messageSearchBloc = MessageSearchBloc.of(context);
|
||||||
|
messageSearchBloc.search(
|
||||||
|
filter: {
|
||||||
|
'cid': {
|
||||||
|
r'$in': ['messaging:${StreamChannel.of(context).channel.id}']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
messageFilter: {
|
||||||
|
'attachments.type': {
|
||||||
|
r'$in': ['image', 'video']
|
||||||
|
},
|
||||||
|
},
|
||||||
|
sort: widget.sortOptions,
|
||||||
|
pagination: widget.paginationParams,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: StreamChatTheme.of(context).colorTheme.white,
|
||||||
|
appBar: AppBar(
|
||||||
|
brightness: Theme.of(context).brightness,
|
||||||
|
elevation: 1,
|
||||||
|
centerTitle: true,
|
||||||
|
title: Text(
|
||||||
|
'Photos & Videos',
|
||||||
|
style: TextStyle(
|
||||||
|
color: StreamChatTheme.of(context).colorTheme.black,
|
||||||
|
fontSize: 16.0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
leading: Center(
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
child: StreamSvgIcon.left(
|
||||||
|
color: StreamChatTheme.of(context).colorTheme.black,
|
||||||
|
size: 24.0,
|
||||||
|
),
|
||||||
|
width: 24.0,
|
||||||
|
height: 24.0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
backgroundColor: StreamChatTheme.of(context).colorTheme.white,
|
||||||
|
),
|
||||||
|
body: _buildMediaGrid(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildMediaGrid() {
|
||||||
|
final messageSearchBloc = MessageSearchBloc.of(context);
|
||||||
|
|
||||||
|
return StreamBuilder<List<GetMessageResponse>>(
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.data == null) {
|
||||||
|
return Center(
|
||||||
|
child: const CircularProgressIndicator(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (snapshot.data.isEmpty) {
|
||||||
|
if (widget.emptyBuilder != null) {
|
||||||
|
return widget.emptyBuilder(context);
|
||||||
|
}
|
||||||
|
return Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
StreamSvgIcon.pictures(
|
||||||
|
size: 136.0,
|
||||||
|
color: StreamChatTheme.of(context).colorTheme.greyGainsboro,
|
||||||
|
),
|
||||||
|
SizedBox(height: 16.0),
|
||||||
|
Text(
|
||||||
|
'No Media',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14.0,
|
||||||
|
color: StreamChatTheme.of(context).colorTheme.black,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 8.0),
|
||||||
|
Text(
|
||||||
|
'Photos or video sent in this chat will \nappear here',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14.0,
|
||||||
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.black
|
||||||
|
.withOpacity(0.5),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final media = <_AssetPackage>[];
|
||||||
|
|
||||||
|
for (var item in snapshot.data) {
|
||||||
|
item.message.attachments
|
||||||
|
.where((e) => e.type == 'image' || e.type == 'video')
|
||||||
|
.forEach((e) {
|
||||||
|
VideoPlayerController controller;
|
||||||
|
if (e.type == 'video') {
|
||||||
|
controller = VideoPlayerController.network(e.assetUrl);
|
||||||
|
controller.initialize();
|
||||||
|
}
|
||||||
|
media.add(_AssetPackage(e, item.message, controller));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return LazyLoadScrollView(
|
||||||
|
onEndOfPage: () => messageSearchBloc.search(
|
||||||
|
filter: {
|
||||||
|
'cid': {
|
||||||
|
r'$in': ['messaging:${StreamChannel.of(context).channel.id}']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
messageFilter: {
|
||||||
|
'attachments.type': {
|
||||||
|
r'$in': ['image', 'video']
|
||||||
|
},
|
||||||
|
},
|
||||||
|
sort: widget.sortOptions,
|
||||||
|
pagination: widget.paginationParams.copyWith(
|
||||||
|
offset: messageSearchBloc.messageResponses?.length ?? 0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: GridView.builder(
|
||||||
|
gridDelegate:
|
||||||
|
SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3),
|
||||||
|
itemBuilder: (context, position) {
|
||||||
|
var channel = StreamChannel.of(context).channel;
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.all(1.0),
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => StreamChannel(
|
||||||
|
channel: channel,
|
||||||
|
child: FullScreenMedia(
|
||||||
|
mediaAttachments:
|
||||||
|
media.map((e) => e.attachment).toList(),
|
||||||
|
startIndex: position,
|
||||||
|
message: media[position].message,
|
||||||
|
sentAt: media[position].message.createdAt,
|
||||||
|
userName: media[position].message.user.name,
|
||||||
|
onShowMessage: widget.onShowMessage,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: media[position].attachment.type == 'image'
|
||||||
|
? IgnorePointer(
|
||||||
|
child: ImageAttachment(
|
||||||
|
attachment: media[position].attachment,
|
||||||
|
message: media[position].message,
|
||||||
|
showTitle: false,
|
||||||
|
size: Size(
|
||||||
|
MediaQuery.of(context).size.width * 0.8,
|
||||||
|
MediaQuery.of(context).size.height * 0.3,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: VideoPlayer(media[position].videoPlayer),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
itemCount: media.length,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
stream: messageSearchBloc.messagesStream,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AssetPackage {
|
||||||
|
Attachment attachment;
|
||||||
|
Message message;
|
||||||
|
VideoPlayerController videoPlayer;
|
||||||
|
|
||||||
|
_AssetPackage(this.attachment, this.message, this.videoPlayer);
|
||||||
|
}
|
||||||
@@ -7,7 +7,6 @@ import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
|
|||||||
import '../stream_chat_flutter.dart';
|
import '../stream_chat_flutter.dart';
|
||||||
import 'channel_name.dart';
|
import 'channel_name.dart';
|
||||||
import 'channel_unread_indicator.dart';
|
import 'channel_unread_indicator.dart';
|
||||||
import 'chat_info_screen.dart';
|
|
||||||
|
|
||||||
/// 
|
/// 
|
||||||
/// 
|
/// 
|
||||||
@@ -50,6 +49,9 @@ class ChannelPreview extends StatelessWidget {
|
|||||||
return Opacity(
|
return Opacity(
|
||||||
opacity: snapshot.data ? 0.5 : 1,
|
opacity: snapshot.data ? 0.5 : 1,
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 8,
|
||||||
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (onTap != null) {
|
if (onTap != null) {
|
||||||
onTap(channel);
|
onTap(channel);
|
||||||
@@ -61,29 +63,7 @@ class ChannelPreview extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
leading: ChannelImage(
|
leading: ChannelImage(
|
||||||
onTap: onImageTap ??
|
onTap: onImageTap,
|
||||||
() {
|
|
||||||
if (channel.memberCount == 2 && channel.isDistinct) {
|
|
||||||
final currentUser = StreamChat.of(context).user;
|
|
||||||
final otherUser = channel.state.members.firstWhere(
|
|
||||||
(element) => element.user.id != currentUser.id,
|
|
||||||
orElse: () => null,
|
|
||||||
);
|
|
||||||
if (otherUser != null) {
|
|
||||||
Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => StreamChannel(
|
|
||||||
channel: channel,
|
|
||||||
child: ChatInfoScreen(
|
|
||||||
user: otherUser.user,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
title: Row(
|
title: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
@@ -122,12 +102,18 @@ class ChannelPreview extends StatelessWidget {
|
|||||||
padding: const EdgeInsets.only(right: 4.0),
|
padding: const EdgeInsets.only(right: 4.0),
|
||||||
child: SendingIndicator(
|
child: SendingIndicator(
|
||||||
message: channel.state.lastMessage,
|
message: channel.state.lastMessage,
|
||||||
allRead: channel.state.read
|
size: StreamChatTheme.of(context)
|
||||||
.where((element) => element.lastRead
|
.channelPreviewTheme
|
||||||
|
.indicatorIconSize,
|
||||||
|
isMessageRead: channel.state.read
|
||||||
|
?.where((element) =>
|
||||||
|
element.user.id !=
|
||||||
|
channel.client.state.user.id)
|
||||||
|
?.where((element) => element.lastRead
|
||||||
.isAfter(channel
|
.isAfter(channel
|
||||||
.state.lastMessage.createdAt))
|
.state.lastMessage.createdAt))
|
||||||
.length ==
|
?.isNotEmpty ==
|
||||||
(channel.memberCount ?? 0),
|
true,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -155,12 +141,18 @@ class ChannelPreview extends StatelessWidget {
|
|||||||
String stringDate;
|
String stringDate;
|
||||||
final now = DateTime.now();
|
final now = DateTime.now();
|
||||||
|
|
||||||
if (now.year != lastMessageAt.year ||
|
var startOfDay = DateTime(now.year, now.month, now.day);
|
||||||
now.month != lastMessageAt.month ||
|
|
||||||
now.day != lastMessageAt.day) {
|
if (lastMessageAt.millisecondsSinceEpoch >=
|
||||||
stringDate = Jiffy(lastMessageAt.toLocal()).format('dd/MM/yyyy');
|
startOfDay.millisecondsSinceEpoch) {
|
||||||
} else {
|
|
||||||
stringDate = Jiffy(lastMessageAt.toLocal()).format('HH:mm');
|
stringDate = Jiffy(lastMessageAt.toLocal()).format('HH:mm');
|
||||||
|
} else if (lastMessageAt.millisecondsSinceEpoch >=
|
||||||
|
startOfDay.subtract(Duration(days: 1)).millisecondsSinceEpoch) {
|
||||||
|
stringDate = 'Yesterday';
|
||||||
|
} else if (startOfDay.difference(lastMessageAt).inDays < 7) {
|
||||||
|
stringDate = Jiffy(lastMessageAt.toLocal()).EEEE;
|
||||||
|
} else {
|
||||||
|
stringDate = Jiffy(lastMessageAt.toLocal()).format('dd/MM/yyyy');
|
||||||
}
|
}
|
||||||
|
|
||||||
return Text(
|
return Text(
|
||||||
@@ -227,7 +219,9 @@ class ChannelPreview extends StatelessWidget {
|
|||||||
} else if (e.type == 'giphy') {
|
} else if (e.type == 'giphy') {
|
||||||
return '[GIF]';
|
return '[GIF]';
|
||||||
}
|
}
|
||||||
return null;
|
return e == lastMessage.attachments.last
|
||||||
|
? (e.title ?? 'File')
|
||||||
|
: '${e.title ?? 'File'} , ';
|
||||||
}).where((e) => e != null),
|
}).where((e) => e != null),
|
||||||
lastMessage.text ?? '',
|
lastMessage.text ?? '',
|
||||||
];
|
];
|
||||||
@@ -235,11 +229,19 @@ class ChannelPreview extends StatelessWidget {
|
|||||||
text = parts.join(' ');
|
text = parts.join(' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
return Text(
|
return Text.rich(
|
||||||
|
_getDisplayText(
|
||||||
text,
|
text,
|
||||||
maxLines: 1,
|
lastMessage.mentionedUsers,
|
||||||
overflow: TextOverflow.ellipsis,
|
lastMessage.attachments,
|
||||||
style:
|
StreamChatTheme.of(context).channelPreviewTheme.subtitle.copyWith(
|
||||||
|
color: StreamChatTheme.of(context)
|
||||||
|
.channelPreviewTheme
|
||||||
|
.subtitle
|
||||||
|
.color,
|
||||||
|
fontStyle: (lastMessage.isSystem || lastMessage.isDeleted)
|
||||||
|
? FontStyle.italic
|
||||||
|
: FontStyle.normal),
|
||||||
StreamChatTheme.of(context).channelPreviewTheme.subtitle.copyWith(
|
StreamChatTheme.of(context).channelPreviewTheme.subtitle.copyWith(
|
||||||
color: StreamChatTheme.of(context)
|
color: StreamChatTheme.of(context)
|
||||||
.channelPreviewTheme
|
.channelPreviewTheme
|
||||||
@@ -248,9 +250,48 @@ class ChannelPreview extends StatelessWidget {
|
|||||||
fontStyle: (lastMessage.isSystem || lastMessage.isDeleted)
|
fontStyle: (lastMessage.isSystem || lastMessage.isDeleted)
|
||||||
? FontStyle.italic
|
? FontStyle.italic
|
||||||
: FontStyle.normal,
|
: FontStyle.normal,
|
||||||
|
fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TextSpan _getDisplayText(
|
||||||
|
String text,
|
||||||
|
List<User> mentions,
|
||||||
|
List<Attachment> attachments,
|
||||||
|
TextStyle normalTextStyle,
|
||||||
|
TextStyle mentionsTextStyle) {
|
||||||
|
var textList = text.split(' ');
|
||||||
|
List<TextSpan> resList = [];
|
||||||
|
for (var e in textList) {
|
||||||
|
if (mentions != null &&
|
||||||
|
mentions.isNotEmpty &&
|
||||||
|
mentions.any((element) => '@${element.name}' == e)) {
|
||||||
|
resList.add(TextSpan(
|
||||||
|
text: '$e ',
|
||||||
|
style: mentionsTextStyle,
|
||||||
|
));
|
||||||
|
} else if (attachments != null &&
|
||||||
|
attachments.isNotEmpty &&
|
||||||
|
attachments
|
||||||
|
.where((e) => e.title != null)
|
||||||
|
.any((element) => element.title == e)) {
|
||||||
|
resList.add(TextSpan(
|
||||||
|
text: '$e ',
|
||||||
|
style: normalTextStyle.copyWith(fontStyle: FontStyle.italic),
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
resList.add(TextSpan(
|
||||||
|
text: e == textList.last ? '$e' : '$e ',
|
||||||
|
style: normalTextStyle,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return TextSpan(children: resList);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ class ChannelUnreadIndicator extends StatelessWidget {
|
|||||||
if (!snapshot.hasData || snapshot.data == 0) {
|
if (!snapshot.hasData || snapshot.data == 0) {
|
||||||
return SizedBox();
|
return SizedBox();
|
||||||
}
|
}
|
||||||
|
|
||||||
return Material(
|
return Material(
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
color: StreamChatTheme.of(context)
|
color: StreamChatTheme.of(context)
|
||||||
@@ -33,7 +34,7 @@ class ChannelUnreadIndicator extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
'${snapshot.data}',
|
'${snapshot.data > 99 ? '99+' : snapshot.data}',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:jiffy/jiffy.dart';
|
import 'package:jiffy/jiffy.dart';
|
||||||
|
import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
|
||||||
|
|
||||||
/// It shows a date divider depending on the date difference
|
/// It shows a date divider depending on the date difference
|
||||||
class DateDivider extends StatelessWidget {
|
class DateDivider extends StatelessWidget {
|
||||||
@@ -39,19 +40,15 @@ class DateDivider extends StatelessWidget {
|
|||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: (Theme.of(context).brightness == Brightness.light
|
color: StreamChatTheme.of(context).colorTheme.black.withOpacity(0.5),
|
||||||
? Colors.black
|
|
||||||
: Colors.white)
|
|
||||||
.withOpacity(0.5),
|
|
||||||
borderRadius: BorderRadius.circular(
|
borderRadius: BorderRadius.circular(
|
||||||
8,
|
8,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
dayInfo,
|
dayInfo,
|
||||||
style: TextStyle(
|
style: StreamChatTheme.of(context).textTheme.footnoteBold.copyWith(
|
||||||
fontSize: 12,
|
color: StreamChatTheme.of(context).colorTheme.white,
|
||||||
color: Colors.white,
|
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -41,8 +41,14 @@ class DeletedMessage extends StatelessWidget {
|
|||||||
side: borderSide ??
|
side: borderSide ??
|
||||||
BorderSide(
|
BorderSide(
|
||||||
color: Theme.of(context).brightness == Brightness.dark
|
color: Theme.of(context).brightness == Brightness.dark
|
||||||
? Colors.white.withAlpha(24)
|
? StreamChatTheme.of(context)
|
||||||
: Colors.black.withAlpha(24),
|
.colorTheme
|
||||||
|
.white
|
||||||
|
.withAlpha(24)
|
||||||
|
: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.black
|
||||||
|
.withAlpha(24),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
@@ -58,8 +64,8 @@ class DeletedMessage extends StatelessWidget {
|
|||||||
style: messageTheme.messageText.copyWith(
|
style: messageTheme.messageText.copyWith(
|
||||||
fontStyle: FontStyle.italic,
|
fontStyle: FontStyle.italic,
|
||||||
color: (Theme.of(context).brightness == Brightness.dark
|
color: (Theme.of(context).brightness == Brightness.dark
|
||||||
? Colors.white
|
? StreamChatTheme.of(context).colorTheme.white
|
||||||
: Colors.black)
|
: StreamChatTheme.of(context).colorTheme.black)
|
||||||
.withOpacity(.5),
|
.withOpacity(.5),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
extension StringExtension on String {
|
||||||
|
String capitalize() {
|
||||||
|
return "${this[0].toUpperCase()}${this.substring(1)}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// List extension
|
||||||
|
extension IterableX<T> on Iterable<T> {
|
||||||
|
/// Insert any item<T> inBetween the list items
|
||||||
|
List<T> insertBetween(T item) => expand((e) sync* {
|
||||||
|
yield item;
|
||||||
|
yield e;
|
||||||
|
}).skip(1).toList(growable: false);
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
|
|||||||
import 'package:stream_chat_flutter/src/utils.dart';
|
import 'package:stream_chat_flutter/src/utils.dart';
|
||||||
import 'package:video_compress/video_compress.dart';
|
import 'package:video_compress/video_compress.dart';
|
||||||
import 'package:video_player/video_player.dart';
|
import 'package:video_player/video_player.dart';
|
||||||
|
|
||||||
import 'media_utils.dart';
|
import 'media_utils.dart';
|
||||||
|
|
||||||
enum FileAttachmentType { local, online }
|
enum FileAttachmentType { local, online }
|
||||||
@@ -61,13 +62,13 @@ class _FileAttachmentState extends State<FileAttachment> {
|
|||||||
child: Container(
|
child: Container(
|
||||||
width: widget.size?.width ?? 100,
|
width: widget.size?.width ?? 100,
|
||||||
height: 56.0,
|
height: 56.0,
|
||||||
margin: widget.trailing != null ? EdgeInsets.only(top: 4.0) : null,
|
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.white,
|
color: StreamChatTheme.of(context).colorTheme.white,
|
||||||
borderRadius:
|
borderRadius:
|
||||||
widget.trailing != null ? BorderRadius.circular(16.0) : null,
|
widget.trailing != null ? BorderRadius.circular(16.0) : null,
|
||||||
border: widget.trailing != null
|
border: widget.trailing != null
|
||||||
? Border.fromBorderSide(BorderSide(color: Color(0xFFE6E6E6)))
|
? Border.fromBorderSide(BorderSide(
|
||||||
|
color: StreamChatTheme.of(context).colorTheme.greyWhisper))
|
||||||
: null,
|
: null,
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
@@ -88,10 +89,7 @@ class _FileAttachmentState extends State<FileAttachment> {
|
|||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
widget.attachment?.title ?? 'File',
|
widget.attachment?.title ?? 'File',
|
||||||
style: TextStyle(
|
style: StreamChatTheme.of(context).textTheme.bodyBold,
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
fontSize: 14.0,
|
|
||||||
),
|
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
@@ -99,11 +97,12 @@ class _FileAttachmentState extends State<FileAttachment> {
|
|||||||
height: 3.0,
|
height: 3.0,
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
'${_getSizeText(widget.attachment.extraData['file_size'])}',
|
'${getSizeText(widget.attachment.extraData['file_size'])}',
|
||||||
style: TextStyle(
|
style: StreamChatTheme.of(context).textTheme.body.copyWith(
|
||||||
color: Colors.black.withOpacity(0.5),
|
color: StreamChatTheme.of(context)
|
||||||
fontSize: 14.0,
|
.colorTheme
|
||||||
),
|
.black
|
||||||
|
.withOpacity(0.5)),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -113,7 +112,7 @@ class _FileAttachmentState extends State<FileAttachment> {
|
|||||||
widget.trailing ??
|
widget.trailing ??
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: StreamSvgIcon.cloud_download(
|
icon: StreamSvgIcon.cloud_download(
|
||||||
color: Colors.black,
|
color: StreamChatTheme.of(context).colorTheme.black,
|
||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
launchURL(context, widget.attachment.assetUrl);
|
launchURL(context, widget.attachment.assetUrl);
|
||||||
@@ -123,37 +122,6 @@ class _FileAttachmentState extends State<FileAttachment> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
||||||
// ListTile(
|
|
||||||
// dense: true,
|
|
||||||
// leading: Container(
|
|
||||||
// child: _getFileTypeImage(attachment.extraData['mime_type']),
|
|
||||||
// height: 40.0,
|
|
||||||
// width: 33.33,
|
|
||||||
// ),
|
|
||||||
// title: Text(
|
|
||||||
// attachment?.title ?? 'File',
|
|
||||||
// style: TextStyle(
|
|
||||||
// fontWeight: FontWeight.bold,
|
|
||||||
// ),
|
|
||||||
// maxLines: 3,
|
|
||||||
// ),
|
|
||||||
// subtitle: Text(
|
|
||||||
// '${attachment.extraData['file_size'] ?? 'N/A'} bytes',
|
|
||||||
// style: TextStyle(
|
|
||||||
// color: Colors.black.withOpacity(0.5),
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// trailing: trailing ??
|
|
||||||
// IconButton(
|
|
||||||
// icon: StreamSvgIcon.cloud_download(
|
|
||||||
// color: Colors.black,
|
|
||||||
// ),
|
|
||||||
// onPressed: () {
|
|
||||||
// launchURL(context, attachment.assetUrl);
|
|
||||||
// },
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -179,7 +147,8 @@ class _FileAttachmentState extends State<FileAttachment> {
|
|||||||
width: 20.0,
|
width: 20.0,
|
||||||
height: 20.0,
|
height: 20.0,
|
||||||
child: CircularProgressIndicator(
|
child: CircularProgressIndicator(
|
||||||
backgroundColor: StreamChatTheme.of(context).accentColor,
|
backgroundColor:
|
||||||
|
StreamChatTheme.of(context).colorTheme.accentBlue,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -226,76 +195,6 @@ class _FileAttachmentState extends State<FileAttachment> {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return getFileTypeImage(widget.attachment.extraData['mime_type']);
|
||||||
switch (widget.attachment.extraData['mime_type']) {
|
|
||||||
case '7z':
|
|
||||||
return StreamSvgIcon.filetype_7z();
|
|
||||||
break;
|
|
||||||
case 'csv':
|
|
||||||
return StreamSvgIcon.filetype_csv();
|
|
||||||
break;
|
|
||||||
case 'doc':
|
|
||||||
return StreamSvgIcon.filetype_doc();
|
|
||||||
break;
|
|
||||||
case 'docx':
|
|
||||||
return StreamSvgIcon.filetype_docx();
|
|
||||||
break;
|
|
||||||
case 'html':
|
|
||||||
return StreamSvgIcon.filetype_html();
|
|
||||||
break;
|
|
||||||
case 'md':
|
|
||||||
return StreamSvgIcon.filetype_md();
|
|
||||||
break;
|
|
||||||
case 'odt':
|
|
||||||
return StreamSvgIcon.filetype_odt();
|
|
||||||
break;
|
|
||||||
case 'pdf':
|
|
||||||
return StreamSvgIcon.filetype_pdf();
|
|
||||||
break;
|
|
||||||
case 'ppt':
|
|
||||||
return StreamSvgIcon.filetype_ppt();
|
|
||||||
break;
|
|
||||||
case 'pptx':
|
|
||||||
return StreamSvgIcon.filetype_pptx();
|
|
||||||
break;
|
|
||||||
case 'rar':
|
|
||||||
return StreamSvgIcon.filetype_rar();
|
|
||||||
break;
|
|
||||||
case 'rtf':
|
|
||||||
return StreamSvgIcon.filetype_rtf();
|
|
||||||
break;
|
|
||||||
case 'tar':
|
|
||||||
return StreamSvgIcon.filetype_tar();
|
|
||||||
break;
|
|
||||||
case 'txt':
|
|
||||||
return StreamSvgIcon.filetype_txt();
|
|
||||||
break;
|
|
||||||
case 'xls':
|
|
||||||
return StreamSvgIcon.filetype_xls();
|
|
||||||
break;
|
|
||||||
case 'xlsx':
|
|
||||||
return StreamSvgIcon.filetype_xlsx();
|
|
||||||
break;
|
|
||||||
case 'zip':
|
|
||||||
return StreamSvgIcon.filetype_zip();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return StreamSvgIcon.filetype_Generic();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String _getSizeText(int bytes) {
|
|
||||||
if (bytes == null) {
|
|
||||||
return 'Size N/A';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bytes <= 1000) {
|
|
||||||
return '${bytes} bytes';
|
|
||||||
} else if (bytes <= 100000) {
|
|
||||||
return '${(bytes / 1000).toStringAsFixed(2)} KB';
|
|
||||||
} else {
|
|
||||||
return '${(bytes / 1000000).toStringAsFixed(2)} MB';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,9 +6,12 @@ import 'package:photo_view/photo_view.dart';
|
|||||||
import 'package:stream_chat_flutter/src/image_footer.dart';
|
import 'package:stream_chat_flutter/src/image_footer.dart';
|
||||||
import 'package:stream_chat_flutter/src/image_header.dart';
|
import 'package:stream_chat_flutter/src/image_header.dart';
|
||||||
import 'package:video_player/video_player.dart';
|
import 'package:video_player/video_player.dart';
|
||||||
|
import 'stream_channel.dart';
|
||||||
|
|
||||||
import '../stream_chat_flutter.dart';
|
import '../stream_chat_flutter.dart';
|
||||||
|
|
||||||
|
typedef ShowMessageCallback = void Function(Message message, Channel channel);
|
||||||
|
|
||||||
/// A full screen image widget
|
/// A full screen image widget
|
||||||
class FullScreenMedia extends StatefulWidget {
|
class FullScreenMedia extends StatefulWidget {
|
||||||
/// The url of the image
|
/// The url of the image
|
||||||
@@ -18,6 +21,7 @@ class FullScreenMedia extends StatefulWidget {
|
|||||||
final int startIndex;
|
final int startIndex;
|
||||||
final String userName;
|
final String userName;
|
||||||
final DateTime sentAt;
|
final DateTime sentAt;
|
||||||
|
final ShowMessageCallback onShowMessage;
|
||||||
|
|
||||||
/// Instantiate a new FullScreenImage
|
/// Instantiate a new FullScreenImage
|
||||||
const FullScreenMedia({
|
const FullScreenMedia({
|
||||||
@@ -27,6 +31,7 @@ class FullScreenMedia extends StatefulWidget {
|
|||||||
this.startIndex = 0,
|
this.startIndex = 0,
|
||||||
this.userName = '',
|
this.userName = '',
|
||||||
this.sentAt,
|
this.sentAt,
|
||||||
|
this.onShowMessage,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -167,6 +172,10 @@ class _FullScreenMediaState extends State<FullScreenMedia>
|
|||||||
message: widget.message,
|
message: widget.message,
|
||||||
urls: widget.mediaAttachments,
|
urls: widget.mediaAttachments,
|
||||||
currentIndex: _currentPage,
|
currentIndex: _currentPage,
|
||||||
|
onShowMessage: () {
|
||||||
|
widget.onShowMessage(
|
||||||
|
widget.message, StreamChannel.of(context).channel);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
ImageFooter(
|
ImageFooter(
|
||||||
currentPage: _currentPage,
|
currentPage: _currentPage,
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ class GiphyAttachment extends StatelessWidget {
|
|||||||
final MessageTheme messageTheme;
|
final MessageTheme messageTheme;
|
||||||
final Message message;
|
final Message message;
|
||||||
final Size size;
|
final Size size;
|
||||||
|
final ShowMessageCallback onShowMessage;
|
||||||
|
|
||||||
const GiphyAttachment({
|
const GiphyAttachment({
|
||||||
Key key,
|
Key key,
|
||||||
@@ -19,6 +20,7 @@ class GiphyAttachment extends StatelessWidget {
|
|||||||
this.messageTheme,
|
this.messageTheme,
|
||||||
this.message,
|
this.message,
|
||||||
this.size,
|
this.size,
|
||||||
|
this.onShowMessage,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -75,6 +77,7 @@ class GiphyAttachment extends StatelessWidget {
|
|||||||
userName: message.user.name,
|
userName: message.user.name,
|
||||||
sentAt: message.createdAt,
|
sentAt: message.createdAt,
|
||||||
message: message,
|
message: message,
|
||||||
|
onShowMessage: onShowMessage,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}));
|
}));
|
||||||
@@ -112,7 +115,10 @@ class GiphyAttachment extends StatelessWidget {
|
|||||||
bottom: 16,
|
bottom: 16,
|
||||||
left: 16,
|
left: 16,
|
||||||
child: Material(
|
child: Material(
|
||||||
color: Colors.black.withOpacity(.5),
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.black
|
||||||
|
.withOpacity(.5),
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
),
|
),
|
||||||
@@ -124,13 +130,16 @@ class GiphyAttachment extends StatelessWidget {
|
|||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
StreamSvgIcon.lightning(
|
StreamSvgIcon.lightning(
|
||||||
color: Colors.white,
|
color:
|
||||||
|
StreamChatTheme.of(context).colorTheme.white,
|
||||||
size: 16,
|
size: 16,
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
'GIPHY',
|
'GIPHY',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.white,
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.white,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
),
|
),
|
||||||
@@ -202,7 +211,10 @@ class GiphyAttachment extends StatelessWidget {
|
|||||||
height: 4.0,
|
height: 4.0,
|
||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
color: Colors.black.withOpacity(0.2),
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.black
|
||||||
|
.withOpacity(0.2),
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
height: 0.5,
|
height: 0.5,
|
||||||
),
|
),
|
||||||
@@ -220,15 +232,24 @@ class GiphyAttachment extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
child: Text(
|
child: Text(
|
||||||
'Cancel',
|
'Cancel',
|
||||||
style: TextStyle(
|
style: StreamChatTheme.of(context)
|
||||||
fontWeight: FontWeight.bold,
|
.textTheme
|
||||||
color: Colors.black.withOpacity(0.5)),
|
.bodyBold
|
||||||
|
.copyWith(
|
||||||
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.black
|
||||||
|
.withOpacity(0.5),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
width: 0.5,
|
width: 0.5,
|
||||||
color: Colors.black.withOpacity(0.2),
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.black
|
||||||
|
.withOpacity(0.2),
|
||||||
height: 50.0,
|
height: 50.0,
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
@@ -242,7 +263,9 @@ class GiphyAttachment extends StatelessWidget {
|
|||||||
child: Text(
|
child: Text(
|
||||||
'Send',
|
'Send',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: StreamChatTheme.of(context).accentColor,
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.accentBlue,
|
||||||
fontWeight: FontWeight.bold),
|
fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -263,7 +286,10 @@ class GiphyAttachment extends StatelessWidget {
|
|||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
StreamSvgIcon.eye(
|
StreamSvgIcon.eye(
|
||||||
color: Colors.black.withOpacity(0.5),
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.black
|
||||||
|
.withOpacity(0.5),
|
||||||
size: 16.0,
|
size: 16.0,
|
||||||
),
|
),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
@@ -271,10 +297,14 @@ class GiphyAttachment extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
'Only visible to you',
|
'Only visible to you',
|
||||||
style: TextStyle(
|
style: StreamChatTheme.of(context)
|
||||||
color: Colors.black.withOpacity(0.5),
|
.textTheme
|
||||||
fontSize: 12.0,
|
.footnote
|
||||||
),
|
.copyWith(
|
||||||
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.black
|
||||||
|
.withOpacity(0.5)),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -300,6 +330,7 @@ class GiphyAttachment extends StatelessWidget {
|
|||||||
userName: message.user.name,
|
userName: message.user.name,
|
||||||
sentAt: message.createdAt,
|
sentAt: message.createdAt,
|
||||||
message: message,
|
message: message,
|
||||||
|
onShowMessage: onShowMessage,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}));
|
}));
|
||||||
@@ -331,7 +362,10 @@ class GiphyAttachment extends StatelessWidget {
|
|||||||
bottom: 8,
|
bottom: 8,
|
||||||
left: 8,
|
left: 8,
|
||||||
child: Material(
|
child: Material(
|
||||||
color: Colors.black.withOpacity(.5),
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.black
|
||||||
|
.withOpacity(.5),
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
),
|
),
|
||||||
@@ -343,13 +377,13 @@ class GiphyAttachment extends StatelessWidget {
|
|||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
StreamSvgIcon.lightning(
|
StreamSvgIcon.lightning(
|
||||||
color: Colors.white,
|
color: StreamChatTheme.of(context).colorTheme.white,
|
||||||
size: 16,
|
size: 16,
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
'GIPHY',
|
'GIPHY',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.white,
|
color: StreamChatTheme.of(context).colorTheme.white,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -9,18 +9,30 @@ class GroupImage extends StatelessWidget {
|
|||||||
@required this.images,
|
@required this.images,
|
||||||
this.constraints,
|
this.constraints,
|
||||||
this.onTap,
|
this.onTap,
|
||||||
|
this.borderRadius,
|
||||||
|
this.selected = false,
|
||||||
|
this.selectionColor,
|
||||||
|
this.selectionThickness = 4,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
final List<String> images;
|
final List<String> images;
|
||||||
final BoxConstraints constraints;
|
final BoxConstraints constraints;
|
||||||
final VoidCallback onTap;
|
final VoidCallback onTap;
|
||||||
|
final bool selected;
|
||||||
|
final BorderRadius borderRadius;
|
||||||
|
final Color selectionColor;
|
||||||
|
final double selectionThickness;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return GestureDetector(
|
var avatar;
|
||||||
|
final streamChatTheme = StreamChatTheme.of(context);
|
||||||
|
|
||||||
|
avatar = GestureDetector(
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
borderRadius: StreamChatTheme.of(context)
|
borderRadius: borderRadius ??
|
||||||
|
StreamChatTheme.of(context)
|
||||||
.ownMessageTheme
|
.ownMessageTheme
|
||||||
.avatarTheme
|
.avatarTheme
|
||||||
.borderRadius,
|
.borderRadius,
|
||||||
@@ -31,7 +43,7 @@ class GroupImage extends StatelessWidget {
|
|||||||
.avatarTheme
|
.avatarTheme
|
||||||
.constraints,
|
.constraints,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: StreamChatTheme.of(context).accentColor,
|
color: StreamChatTheme.of(context).colorTheme.accentBlue,
|
||||||
),
|
),
|
||||||
child: Flex(
|
child: Flex(
|
||||||
direction: Axis.vertical,
|
direction: Axis.vertical,
|
||||||
@@ -91,5 +103,25 @@ class GroupImage extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (selected) {
|
||||||
|
avatar = ClipRRect(
|
||||||
|
borderRadius: (borderRadius ??
|
||||||
|
streamChatTheme.ownMessageTheme.avatarTheme.borderRadius) +
|
||||||
|
BorderRadius.circular(selectionThickness),
|
||||||
|
child: Container(
|
||||||
|
color: selectionColor ??
|
||||||
|
StreamChatTheme.of(context).colorTheme.accentBlue,
|
||||||
|
height: 64.0,
|
||||||
|
width: 64.0,
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.all(selectionThickness),
|
||||||
|
child: avatar,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return avatar;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,48 +1,36 @@
|
|||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:dio/dio.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:dio/dio.dart';
|
|
||||||
import 'package:image_gallery_saver/image_gallery_saver.dart';
|
import 'package:image_gallery_saver/image_gallery_saver.dart';
|
||||||
import '../stream_chat_flutter.dart';
|
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
|
||||||
|
import '../stream_chat_flutter.dart';
|
||||||
|
|
||||||
class ImageActionsModal extends StatelessWidget {
|
class ImageActionsModal extends StatelessWidget {
|
||||||
final Message message;
|
final Message message;
|
||||||
final String userName;
|
final String userName;
|
||||||
final String sentAt;
|
final String sentAt;
|
||||||
final List<Attachment> urls;
|
final List<Attachment> urls;
|
||||||
final currentIndex;
|
final currentIndex;
|
||||||
|
final VoidCallback onShowMessage;
|
||||||
|
|
||||||
ImageActionsModal(
|
ImageActionsModal(
|
||||||
{this.message, this.userName, this.sentAt, this.urls, this.currentIndex});
|
{this.message,
|
||||||
|
this.userName,
|
||||||
|
this.sentAt,
|
||||||
|
this.urls,
|
||||||
|
this.currentIndex,
|
||||||
|
this.onShowMessage});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
behavior: HitTestBehavior.translucent,
|
behavior: HitTestBehavior.translucent,
|
||||||
onTap: () {
|
onTap: () => Navigator.maybePop(context),
|
||||||
Navigator.pop(context);
|
child: _buildPage(context),
|
||||||
},
|
|
||||||
child: Stack(
|
|
||||||
children: [
|
|
||||||
Positioned.fill(
|
|
||||||
child: Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
gradient: LinearGradient(
|
|
||||||
begin: Alignment.topCenter,
|
|
||||||
end: Alignment.bottomCenter,
|
|
||||||
colors: [
|
|
||||||
Colors.black.withOpacity(0.8),
|
|
||||||
Colors.transparent,
|
|
||||||
],
|
|
||||||
stops: [0.0, 0.4],
|
|
||||||
)),
|
|
||||||
)),
|
|
||||||
_buildPage(context),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,49 +38,15 @@ class ImageActionsModal extends StatelessWidget {
|
|||||||
return Material(
|
return Material(
|
||||||
color: Colors.transparent,
|
color: Colors.transparent,
|
||||||
child: Column(
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
Row(
|
SizedBox(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
height: kToolbarHeight,
|
||||||
children: [
|
child: IconButton(
|
||||||
Padding(
|
icon: StreamSvgIcon.close(),
|
||||||
padding: const EdgeInsets.all(8.0),
|
onPressed: () => Navigator.maybePop(context),
|
||||||
child: Container(
|
|
||||||
width: 40.0,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Column(
|
|
||||||
children: [
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(top: 8.0),
|
|
||||||
child: Text(
|
|
||||||
userName,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 16.0,
|
|
||||||
fontWeight: FontWeight.w700,
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
sentAt,
|
|
||||||
style: StreamChatTheme.of(context)
|
|
||||||
.channelPreviewTheme
|
|
||||||
.subtitle
|
|
||||||
.copyWith(color: Colors.white.withOpacity(0.5)),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
icon: StreamSvgIcon.close(
|
|
||||||
size: 24.0,
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.pop(context);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Align(
|
Align(
|
||||||
alignment: Alignment.centerRight,
|
alignment: Alignment.centerRight,
|
||||||
child: Container(
|
child: Container(
|
||||||
@@ -104,10 +58,10 @@ class ImageActionsModal extends StatelessWidget {
|
|||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
),
|
),
|
||||||
color: Color(0xffe5e5e5),
|
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: ListTile.divideTiles(
|
children: ListTile.divideTiles(
|
||||||
|
color: StreamChatTheme.of(context).colorTheme.greyWhisper,
|
||||||
context: context,
|
context: context,
|
||||||
tiles: [
|
tiles: [
|
||||||
_buildButton(
|
_buildButton(
|
||||||
@@ -115,7 +69,8 @@ class ImageActionsModal extends StatelessWidget {
|
|||||||
'Reply',
|
'Reply',
|
||||||
StreamSvgIcon.Icon_curve_line_left_up(
|
StreamSvgIcon.Icon_curve_line_left_up(
|
||||||
size: 24.0,
|
size: 24.0,
|
||||||
color: Colors.black.withOpacity(0.5),
|
color:
|
||||||
|
StreamChatTheme.of(context).colorTheme.grey,
|
||||||
),
|
),
|
||||||
() {}),
|
() {}),
|
||||||
_buildButton(
|
_buildButton(
|
||||||
@@ -123,17 +78,17 @@ class ImageActionsModal extends StatelessWidget {
|
|||||||
'Show in Chat',
|
'Show in Chat',
|
||||||
StreamSvgIcon.eye(
|
StreamSvgIcon.eye(
|
||||||
size: 24.0,
|
size: 24.0,
|
||||||
color: Colors.black,
|
color:
|
||||||
), () {
|
StreamChatTheme.of(context).colorTheme.black,
|
||||||
Navigator.pop(context);
|
),
|
||||||
Navigator.pop(context);
|
onShowMessage),
|
||||||
}),
|
|
||||||
_buildButton(
|
_buildButton(
|
||||||
context,
|
context,
|
||||||
'Save ${urls[currentIndex].type == 'video' ? 'Video' : 'Image'}',
|
'Save ${urls[currentIndex].type == 'video' ? 'Video' : 'Image'}',
|
||||||
StreamSvgIcon.Icon_save(
|
StreamSvgIcon.Icon_save(
|
||||||
size: 24.0,
|
size: 24.0,
|
||||||
color: Colors.black.withOpacity(0.5),
|
color:
|
||||||
|
StreamChatTheme.of(context).colorTheme.grey,
|
||||||
), () async {
|
), () async {
|
||||||
var url = urls[currentIndex].imageUrl ??
|
var url = urls[currentIndex].imageUrl ??
|
||||||
urls[currentIndex].assetUrl ??
|
urls[currentIndex].assetUrl ??
|
||||||
@@ -153,7 +108,9 @@ class ImageActionsModal extends StatelessWidget {
|
|||||||
'Delete',
|
'Delete',
|
||||||
StreamSvgIcon.delete(
|
StreamSvgIcon.delete(
|
||||||
size: 24.0,
|
size: 24.0,
|
||||||
color: Color(0xffFF3742),
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.accentRed,
|
||||||
),
|
),
|
||||||
() {
|
() {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
@@ -163,7 +120,9 @@ class ImageActionsModal extends StatelessWidget {
|
|||||||
StreamChannel.of(context).channel.cid,
|
StreamChannel.of(context).channel.cid,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
color: Color(0xffFF3742),
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.accentRed,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
).toList(),
|
).toList(),
|
||||||
@@ -182,10 +141,11 @@ class ImageActionsModal extends StatelessWidget {
|
|||||||
{Color color}) {
|
{Color color}) {
|
||||||
var titleStyle = TextStyle(
|
var titleStyle = TextStyle(
|
||||||
fontSize: 14.5,
|
fontSize: 14.5,
|
||||||
color: Colors.black,
|
color: StreamChatTheme.of(context).colorTheme.black,
|
||||||
);
|
);
|
||||||
|
|
||||||
return Material(
|
return Material(
|
||||||
|
color: StreamChatTheme.of(context).colorTheme.white,
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ class ImageAttachment extends StatelessWidget {
|
|||||||
final Message message;
|
final Message message;
|
||||||
final MessageTheme messageTheme;
|
final MessageTheme messageTheme;
|
||||||
final Size size;
|
final Size size;
|
||||||
|
final bool showTitle;
|
||||||
|
final ShowMessageCallback onShowMessage;
|
||||||
|
|
||||||
const ImageAttachment({
|
const ImageAttachment({
|
||||||
Key key,
|
Key key,
|
||||||
@@ -19,6 +21,8 @@ class ImageAttachment extends StatelessWidget {
|
|||||||
@required this.message,
|
@required this.message,
|
||||||
@required this.size,
|
@required this.size,
|
||||||
this.messageTheme,
|
this.messageTheme,
|
||||||
|
this.showTitle = true,
|
||||||
|
this.onShowMessage,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -54,6 +58,7 @@ class ImageAttachment extends StatelessWidget {
|
|||||||
userName: message.user.name,
|
userName: message.user.name,
|
||||||
sentAt: message.createdAt,
|
sentAt: message.createdAt,
|
||||||
message: message,
|
message: message,
|
||||||
|
onShowMessage: onShowMessage,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -83,7 +88,7 @@ class ImageAttachment extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (attachment.title != null)
|
if (showTitle && attachment.title != null)
|
||||||
Material(
|
Material(
|
||||||
color: messageTheme.messageBackgroundColor,
|
color: messageTheme.messageBackgroundColor,
|
||||||
child: AttachmentTitle(
|
child: AttachmentTitle(
|
||||||
@@ -93,7 +98,8 @@ class ImageAttachment extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (attachment.titleLink != null || attachment.ogScrapeUrl != null)
|
if (showTitle &&
|
||||||
|
(attachment.titleLink != null || attachment.ogScrapeUrl != null))
|
||||||
Positioned.fill(
|
Positioned.fill(
|
||||||
child: Material(
|
child: Material(
|
||||||
color: Colors.transparent,
|
color: Colors.transparent,
|
||||||
|
|||||||
@@ -5,20 +5,17 @@ import 'dart:typed_data';
|
|||||||
|
|
||||||
import 'package:cached_network_image/cached_network_image.dart';
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
import 'package:chewie/chewie.dart';
|
import 'package:chewie/chewie.dart';
|
||||||
|
import 'package:dio/dio.dart';
|
||||||
import 'package:esys_flutter_share/esys_flutter_share.dart';
|
import 'package:esys_flutter_share/esys_flutter_share.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:dio/dio.dart';
|
|
||||||
import 'package:image_gallery_saver/image_gallery_saver.dart';
|
import 'package:image_gallery_saver/image_gallery_saver.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:stream_chat/stream_chat.dart';
|
import 'package:stream_chat/stream_chat.dart';
|
||||||
import 'package:stream_chat_flutter/src/stream_chat.dart';
|
|
||||||
import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
|
import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
|
||||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||||
|
|
||||||
import 'stream_channel.dart';
|
class ImageFooter extends StatefulWidget implements PreferredSizeWidget {
|
||||||
|
|
||||||
class ImageFooter extends StatefulWidget {
|
|
||||||
/// Callback to call when pressing the back button.
|
/// Callback to call when pressing the back button.
|
||||||
/// By default it calls [Navigator.pop]
|
/// By default it calls [Navigator.pop]
|
||||||
final VoidCallback onBackPressed;
|
final VoidCallback onBackPressed;
|
||||||
@@ -63,12 +60,12 @@ class ImageFooter extends StatefulWidget {
|
|||||||
class _ImageFooterState extends State<ImageFooter> {
|
class _ImageFooterState extends State<ImageFooter> {
|
||||||
bool _userSearchMode = false;
|
bool _userSearchMode = false;
|
||||||
TextEditingController _searchController;
|
TextEditingController _searchController;
|
||||||
TextEditingController _messageController = TextEditingController();
|
final TextEditingController _messageController = TextEditingController();
|
||||||
|
final FocusNode _messageFocusNode = FocusNode();
|
||||||
|
|
||||||
String _userNameQuery;
|
String _channelNameQuery;
|
||||||
bool _isSearchActive = false;
|
|
||||||
|
|
||||||
Set<User> _selectedUsers = {};
|
final List<Channel> _selectedChannels = [];
|
||||||
bool _loading = false;
|
bool _loading = false;
|
||||||
|
|
||||||
Timer _debounce;
|
Timer _debounce;
|
||||||
@@ -80,8 +77,7 @@ class _ImageFooterState extends State<ImageFooter> {
|
|||||||
_debounce = Timer(const Duration(milliseconds: 350), () {
|
_debounce = Timer(const Duration(milliseconds: 350), () {
|
||||||
if (mounted && modalSetStateCallback != null) {
|
if (mounted && modalSetStateCallback != null) {
|
||||||
modalSetStateCallback(() {
|
modalSetStateCallback(() {
|
||||||
_userNameQuery = _searchController.text;
|
_channelNameQuery = _searchController.text;
|
||||||
_isSearchActive = _userNameQuery.isNotEmpty;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -91,6 +87,9 @@ class _ImageFooterState extends State<ImageFooter> {
|
|||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_searchController = TextEditingController()..addListener(_userNameListener);
|
_searchController = TextEditingController()..addListener(_userNameListener);
|
||||||
|
_messageFocusNode.addListener(() {
|
||||||
|
setState(() {});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -103,21 +102,38 @@ class _ImageFooterState extends State<ImageFooter> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SafeArea(
|
return SizedBox.fromSize(
|
||||||
child: Container(
|
size: Size(
|
||||||
color:
|
MediaQuery.of(context).size.width,
|
||||||
StreamChatTheme.of(context).channelTheme.channelHeaderTheme.color,
|
MediaQuery.of(context).padding.bottom + widget.preferredSize.height,
|
||||||
padding: EdgeInsets.symmetric(vertical: 8.0),
|
),
|
||||||
|
child: MediaQuery.removePadding(
|
||||||
|
context: context,
|
||||||
|
removeTop: true,
|
||||||
|
child: BottomAppBar(
|
||||||
|
color: StreamChatTheme.of(context).colorTheme.white,
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: StreamSvgIcon.icon_SHARE(
|
icon: StreamSvgIcon.icon_SHARE(
|
||||||
size: 24.0,
|
size: 24.0,
|
||||||
color: Colors.black,
|
color: StreamChatTheme.of(context).colorTheme.black,
|
||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () async {
|
||||||
_buildShareModal(context);
|
final attachment =
|
||||||
|
widget.mediaAttachments[widget.currentPage];
|
||||||
|
var url = attachment.imageUrl ??
|
||||||
|
attachment.assetUrl ??
|
||||||
|
attachment.thumbUrl;
|
||||||
|
var type = attachment.type == 'image'
|
||||||
|
? 'jpg'
|
||||||
|
: url?.split('?')?.first?.split('.')?.last ?? 'jpg';
|
||||||
|
var request = await HttpClient().getUrl(Uri.parse(url));
|
||||||
|
var response = await request.close();
|
||||||
|
var bytes =
|
||||||
|
await consolidateHttpClientResponseBytes(response);
|
||||||
|
await Share.file('File', 'image.$type', bytes, 'image/$type');
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
InkWell(
|
InkWell(
|
||||||
@@ -130,11 +146,8 @@ class _ImageFooterState extends State<ImageFooter> {
|
|||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Text(
|
Text(
|
||||||
'${widget.currentPage + 1} of ${widget.totalPages}',
|
'${widget.currentPage + 1} of ${widget.totalPages}',
|
||||||
style: TextStyle(
|
style:
|
||||||
fontSize: 16.0,
|
StreamChatTheme.of(context).textTheme.headlineBold,
|
||||||
fontWeight: FontWeight.w700,
|
|
||||||
color: Colors.black,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -142,25 +155,25 @@ class _ImageFooterState extends State<ImageFooter> {
|
|||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: StreamSvgIcon.Icon_grid(
|
icon: StreamSvgIcon.Icon_grid(
|
||||||
color: Colors.black,
|
color: StreamChatTheme.of(context).colorTheme.black,
|
||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () => _showPhotosModal(context),
|
||||||
_buildPhotosModal(context);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildPhotosModal(context) {
|
void _showPhotosModal(context) {
|
||||||
var videoAttachments = widget.mediaAttachments
|
var videoAttachments = widget.mediaAttachments
|
||||||
.where((element) => element.type == 'video')
|
.where((element) => element.type == 'video')
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
|
barrierColor: StreamChatTheme.of(context).colorTheme.overlay,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(16.0),
|
borderRadius: BorderRadius.circular(16.0),
|
||||||
),
|
),
|
||||||
@@ -170,7 +183,7 @@ class _ImageFooterState extends State<ImageFooter> {
|
|||||||
children: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.white,
|
color: StreamChatTheme.of(context).colorTheme.white,
|
||||||
borderRadius: BorderRadius.only(
|
borderRadius: BorderRadius.only(
|
||||||
topRight: Radius.circular(16.0),
|
topRight: Radius.circular(16.0),
|
||||||
topLeft: Radius.circular(16.0),
|
topLeft: Radius.circular(16.0),
|
||||||
@@ -182,11 +195,8 @@ class _ImageFooterState extends State<ImageFooter> {
|
|||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
child: Text(
|
child: Text(
|
||||||
'Photos',
|
'Photos',
|
||||||
style: TextStyle(
|
style:
|
||||||
fontSize: 16.0,
|
StreamChatTheme.of(context).textTheme.headlineBold,
|
||||||
fontWeight: FontWeight.w700,
|
|
||||||
color: Colors.black,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -194,18 +204,16 @@ class _ImageFooterState extends State<ImageFooter> {
|
|||||||
alignment: Alignment.centerRight,
|
alignment: Alignment.centerRight,
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
icon: StreamSvgIcon.close(
|
icon: StreamSvgIcon.close(
|
||||||
color: Colors.black,
|
color: StreamChatTheme.of(context).colorTheme.black,
|
||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () => Navigator.maybePop(context),
|
||||||
Navigator.pop(context);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
color: Colors.white,
|
color: StreamChatTheme.of(context).colorTheme.white,
|
||||||
child: GridView.builder(
|
child: GridView.builder(
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
physics: NeverScrollableScrollPhysics(),
|
physics: NeverScrollableScrollPhysics(),
|
||||||
@@ -258,278 +266,16 @@ class _ImageFooterState extends State<ImageFooter> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildShareModal(context) {
|
|
||||||
showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (context) {
|
|
||||||
return StatefulBuilder(builder: (context, modalSetState) {
|
|
||||||
modalSetStateCallback = modalSetState;
|
|
||||||
return Padding(
|
|
||||||
padding: EdgeInsets.only(
|
|
||||||
top: _userSearchMode
|
|
||||||
? 16.0
|
|
||||||
: MediaQuery.of(context).size.height / 2,
|
|
||||||
left: 8.0,
|
|
||||||
right: 8.0),
|
|
||||||
child: Material(
|
|
||||||
borderRadius: BorderRadius.only(
|
|
||||||
topLeft: Radius.circular(16.0),
|
|
||||||
topRight: Radius.circular(16.0),
|
|
||||||
),
|
|
||||||
clipBehavior: Clip.antiAlias,
|
|
||||||
child: Scaffold(
|
|
||||||
body: UsersBloc(
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
_buildTextInputSection(modalSetState),
|
|
||||||
Expanded(
|
|
||||||
child: UserListView(
|
|
||||||
selectedUsers: _selectedUsers,
|
|
||||||
onUserTap: (user, _) {
|
|
||||||
_searchController.clear();
|
|
||||||
if (!_selectedUsers.contains(user)) {
|
|
||||||
modalSetState(() {
|
|
||||||
_selectedUsers.add(user);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
modalSetState(() {
|
|
||||||
_selectedUsers.remove(user);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
crossAxisCount: 4,
|
|
||||||
pagination: PaginationParams(
|
|
||||||
limit: 25,
|
|
||||||
),
|
|
||||||
filter: {
|
|
||||||
if (_searchController.text.isNotEmpty)
|
|
||||||
'name': {
|
|
||||||
r'$autocomplete': _userNameQuery,
|
|
||||||
},
|
|
||||||
'id': {
|
|
||||||
r'$ne': StreamChat.of(context).user.id,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
sort: [
|
|
||||||
SortOption(
|
|
||||||
'name',
|
|
||||||
direction: 1,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
emptyBuilder: (_) {
|
|
||||||
return LayoutBuilder(
|
|
||||||
builder: (context, viewportConstraints) {
|
|
||||||
return SingleChildScrollView(
|
|
||||||
physics: AlwaysScrollableScrollPhysics(),
|
|
||||||
child: ConstrainedBox(
|
|
||||||
constraints: BoxConstraints(
|
|
||||||
minHeight: viewportConstraints.maxHeight,
|
|
||||||
),
|
|
||||||
child: Center(
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(24),
|
|
||||||
child: StreamSvgIcon.search(
|
|
||||||
size: 96,
|
|
||||||
color: Colors.grey,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
'No user matches these keywords...'),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (_selectedUsers.isNotEmpty)
|
|
||||||
_buildShareTextInputSection(modalSetState),
|
|
||||||
if (!_userSearchMode && _selectedUsers.isEmpty)
|
|
||||||
Align(
|
|
||||||
alignment: Alignment.bottomCenter,
|
|
||||||
child: Container(
|
|
||||||
color: Colors.white,
|
|
||||||
height: 48.0,
|
|
||||||
child: Material(
|
|
||||||
child: InkWell(
|
|
||||||
onTap: () async {
|
|
||||||
var url = widget
|
|
||||||
.mediaAttachments[widget.currentPage]
|
|
||||||
.imageUrl ??
|
|
||||||
widget
|
|
||||||
.mediaAttachments[widget.currentPage]
|
|
||||||
.assetUrl ??
|
|
||||||
widget
|
|
||||||
.mediaAttachments[widget.currentPage]
|
|
||||||
.thumbUrl;
|
|
||||||
|
|
||||||
if (widget
|
|
||||||
.mediaAttachments[widget.currentPage]
|
|
||||||
.type ==
|
|
||||||
'video') {
|
|
||||||
await _saveVideo(url);
|
|
||||||
Navigator.pop(context);
|
|
||||||
} else {
|
|
||||||
await _saveImage(url);
|
|
||||||
Navigator.pop(context);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: SizedBox.expand(
|
|
||||||
child: Center(
|
|
||||||
child: Text(
|
|
||||||
'Save to Photos',
|
|
||||||
style: TextStyle(
|
|
||||||
color: StreamChatTheme.of(context)
|
|
||||||
.accentColor,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildTextInputSection(modalSetState) {
|
|
||||||
if (_userSearchMode) {
|
|
||||||
return Column(
|
|
||||||
children: [
|
|
||||||
SizedBox(
|
|
||||||
height: 16.0,
|
|
||||||
),
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
SizedBox(
|
|
||||||
width: 16.0,
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: TextField(
|
|
||||||
controller: _searchController,
|
|
||||||
cursorColor: Colors.black,
|
|
||||||
autofocus: true,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
isDense: true,
|
|
||||||
prefixIconConstraints:
|
|
||||||
BoxConstraints.tight(Size(36.0, 44.0)),
|
|
||||||
prefixIcon: Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
vertical: 2.0, horizontal: 6.0),
|
|
||||||
child: StreamSvgIcon.search(
|
|
||||||
color: Colors.black,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
hintText: 'Search',
|
|
||||||
border: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.circular(32.0),
|
|
||||||
borderSide:
|
|
||||||
BorderSide(color: Colors.black.withOpacity(0.08)),
|
|
||||||
),
|
|
||||||
focusedBorder: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.circular(32.0),
|
|
||||||
borderSide:
|
|
||||||
BorderSide(color: Colors.black.withOpacity(0.08)),
|
|
||||||
),
|
|
||||||
contentPadding: EdgeInsets.zero,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(
|
|
||||||
width: 8.0,
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
icon: StreamSvgIcon.close_small(
|
|
||||||
color: Colors.black.withOpacity(0.5),
|
|
||||||
),
|
|
||||||
onPressed: () {
|
|
||||||
modalSetState(() {
|
|
||||||
_userSearchMode = false;
|
|
||||||
});
|
|
||||||
setState(() {});
|
|
||||||
},
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 6.0),
|
|
||||||
child: IconButton(
|
|
||||||
icon: StreamSvgIcon.search(
|
|
||||||
color: Colors.black,
|
|
||||||
),
|
|
||||||
iconSize: 24.0,
|
|
||||||
onPressed: () {
|
|
||||||
modalSetState(() {
|
|
||||||
_userSearchMode = true;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(16.0),
|
|
||||||
child: Text(
|
|
||||||
'Select a Chat to Share',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 16.0,
|
|
||||||
fontWeight: FontWeight.w700,
|
|
||||||
color: Colors.black,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 6.0),
|
|
||||||
child: IconButton(
|
|
||||||
icon: StreamSvgIcon.share_arrow(
|
|
||||||
color: Colors.black,
|
|
||||||
),
|
|
||||||
onPressed: () async {
|
|
||||||
var url =
|
|
||||||
widget.mediaAttachments[widget.currentPage].imageUrl ??
|
|
||||||
widget.mediaAttachments[widget.currentPage].assetUrl ??
|
|
||||||
widget.mediaAttachments[widget.currentPage].thumbUrl;
|
|
||||||
var type =
|
|
||||||
widget.mediaAttachments[widget.currentPage].type == 'image'
|
|
||||||
? 'jpg'
|
|
||||||
: url?.split('?')?.first?.split('.')?.last ?? 'jpg';
|
|
||||||
var request = await HttpClient().getUrl(Uri.parse(url));
|
|
||||||
var response = await request.close();
|
|
||||||
var bytes = await consolidateHttpClientResponseBytes(response);
|
|
||||||
await Share.file('File', 'image.$type', bytes, 'image/$type');
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildShareTextInputSection(modalSetState) {
|
Widget _buildShareTextInputSection(modalSetState) {
|
||||||
return Align(
|
return Align(
|
||||||
alignment: Alignment.bottomCenter,
|
alignment: Alignment.bottomCenter,
|
||||||
|
child: BottomAppBar(
|
||||||
child: Container(
|
child: Container(
|
||||||
color: Colors.white,
|
height: 40.0,
|
||||||
height: 56.0,
|
margin: const EdgeInsets.symmetric(
|
||||||
|
vertical: 8,
|
||||||
|
horizontal: 8,
|
||||||
|
),
|
||||||
child: _loading
|
child: _loading
|
||||||
? Center(
|
? Center(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
@@ -540,10 +286,9 @@ class _ImageFooterState extends State<ImageFooter> {
|
|||||||
: Row(
|
: Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.only(left: 8.0),
|
|
||||||
child: TextField(
|
child: TextField(
|
||||||
controller: _messageController,
|
controller: _messageController,
|
||||||
|
focusNode: _messageFocusNode,
|
||||||
onChanged: (val) {
|
onChanged: (val) {
|
||||||
modalSetState(() {});
|
modalSetState(() {});
|
||||||
},
|
},
|
||||||
@@ -552,48 +297,53 @@ class _ImageFooterState extends State<ImageFooter> {
|
|||||||
setState(() {});
|
setState(() {});
|
||||||
},
|
},
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
isDense: true,
|
|
||||||
prefixText: ' ',
|
prefixText: ' ',
|
||||||
hintText: 'Add a comment',
|
hintText: 'Add a comment',
|
||||||
border: OutlineInputBorder(
|
enabledBorder: OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(32.0),
|
borderRadius: BorderRadius.circular(24.0),
|
||||||
borderSide: BorderSide(
|
borderSide: BorderSide(
|
||||||
color: Colors.black.withOpacity(0.16),
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.black
|
||||||
|
.withOpacity(0.16),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
focusedBorder: OutlineInputBorder(
|
focusedBorder: OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(32.0),
|
borderRadius: BorderRadius.circular(24.0),
|
||||||
borderSide: BorderSide(
|
borderSide: BorderSide(
|
||||||
color: Colors.black.withOpacity(0.16),
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.black
|
||||||
|
.withOpacity(0.16),
|
||||||
)),
|
)),
|
||||||
contentPadding: EdgeInsets.symmetric(vertical: 12.0),
|
contentPadding: const EdgeInsets.all(0),
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
SizedBox(width: 8),
|
||||||
IconTheme(
|
IconTheme(
|
||||||
data: StreamChatTheme.of(context)
|
data: StreamChatTheme.of(context)
|
||||||
.channelTheme
|
.channelTheme
|
||||||
.messageInputButtonIconTheme,
|
.messageInputButtonIconTheme,
|
||||||
child: Center(
|
child: IconButton(
|
||||||
child: Padding(
|
onPressed: () async {
|
||||||
padding: const EdgeInsets.all(8.0),
|
modalSetState(() => _loading = true);
|
||||||
child: InkWell(
|
|
||||||
onTap: () async {
|
|
||||||
modalSetState(() {
|
|
||||||
_loading = true;
|
|
||||||
});
|
|
||||||
await sendMessage();
|
await sendMessage();
|
||||||
modalSetState(() {
|
modalSetState(() => _loading = false);
|
||||||
_loading = false;
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
child: Transform.rotate(
|
splashRadius: 24,
|
||||||
|
visualDensity: VisualDensity.compact,
|
||||||
|
constraints: BoxConstraints.tightFor(
|
||||||
|
height: 24,
|
||||||
|
width: 24,
|
||||||
|
),
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
icon: Transform.rotate(
|
||||||
angle: -pi / 2,
|
angle: -pi / 2,
|
||||||
child: StreamSvgIcon.Icon_send_message(
|
child: StreamSvgIcon.Icon_send_message(
|
||||||
color: StreamChatTheme.of(context).accentColor,
|
color: StreamChatTheme.of(context)
|
||||||
),
|
.colorTheme
|
||||||
),
|
.accentBlue,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -601,6 +351,7 @@ class _ImageFooterState extends State<ImageFooter> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -612,27 +363,16 @@ class _ImageFooterState extends State<ImageFooter> {
|
|||||||
|
|
||||||
_messageController.clear();
|
_messageController.clear();
|
||||||
|
|
||||||
final client = StreamChat.of(context).client;
|
for (var channel in _selectedChannels) {
|
||||||
|
|
||||||
for (var user in _selectedUsers) {
|
|
||||||
var c = client.channel('messaging', extraData: {
|
|
||||||
'members': [
|
|
||||||
user.id,
|
|
||||||
StreamChat.of(context).user.id,
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
await c.watch();
|
|
||||||
|
|
||||||
final message = Message(
|
final message = Message(
|
||||||
text: text,
|
text: text,
|
||||||
attachments: [attachments[widget.currentPage]],
|
attachments: [attachments[widget.currentPage]],
|
||||||
);
|
);
|
||||||
|
|
||||||
await c.sendMessage(message);
|
await channel.sendMessage(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
_selectedUsers.clear();
|
_selectedChannels.clear();
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
import 'package:cached_network_image/cached_network_image.dart';
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
import 'package:carousel_slider/carousel_options.dart';
|
|
||||||
import 'package:carousel_slider/carousel_slider.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:stream_chat/stream_chat.dart';
|
import 'package:stream_chat/stream_chat.dart';
|
||||||
import 'package:stream_chat_flutter/src/full_screen_media.dart';
|
import 'package:stream_chat_flutter/src/full_screen_media.dart';
|
||||||
@@ -12,11 +10,13 @@ class ImageGroup extends StatelessWidget {
|
|||||||
@required this.images,
|
@required this.images,
|
||||||
@required this.message,
|
@required this.message,
|
||||||
@required this.size,
|
@required this.size,
|
||||||
|
this.onShowMessage,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
final List<Attachment> images;
|
final List<Attachment> images;
|
||||||
final Message message;
|
final Message message;
|
||||||
final Size size;
|
final Size size;
|
||||||
|
final ShowMessageCallback onShowMessage;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -81,10 +81,10 @@ class ImageGroup extends StatelessWidget {
|
|||||||
color: Colors.black38,
|
color: Colors.black38,
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
'${images.length - 4} +',
|
'+ ${images.length - 4}',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
fontSize: 28,
|
fontSize: 26,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -121,6 +121,7 @@ class ImageGroup extends StatelessWidget {
|
|||||||
userName: message.user.name,
|
userName: message.user.name,
|
||||||
sentAt: message.createdAt,
|
sentAt: message.createdAt,
|
||||||
message: message,
|
message: message,
|
||||||
|
onShowMessage: onShowMessage,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -13,6 +13,9 @@ class ImageHeader extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
/// By default it calls [Navigator.pop]
|
/// By default it calls [Navigator.pop]
|
||||||
final VoidCallback onBackPressed;
|
final VoidCallback onBackPressed;
|
||||||
|
|
||||||
|
/// Callback to call when pressing the show message button.
|
||||||
|
final VoidCallback onShowMessage;
|
||||||
|
|
||||||
/// Callback to call when the header is tapped.
|
/// Callback to call when the header is tapped.
|
||||||
final VoidCallback onTitleTap;
|
final VoidCallback onTitleTap;
|
||||||
|
|
||||||
@@ -35,6 +38,7 @@ class ImageHeader extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
this.currentIndex,
|
this.currentIndex,
|
||||||
this.showBackButton = true,
|
this.showBackButton = true,
|
||||||
this.onBackPressed,
|
this.onBackPressed,
|
||||||
|
this.onShowMessage,
|
||||||
this.onTitleTap,
|
this.onTitleTap,
|
||||||
this.onImageTap,
|
this.onImageTap,
|
||||||
this.userName = '',
|
this.userName = '',
|
||||||
@@ -50,7 +54,7 @@ class ImageHeader extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
leading: showBackButton
|
leading: showBackButton
|
||||||
? IconButton(
|
? IconButton(
|
||||||
icon: StreamSvgIcon.close(
|
icon: StreamSvgIcon.close(
|
||||||
color: Colors.black,
|
color: StreamChatTheme.of(context).colorTheme.black,
|
||||||
size: 24.0,
|
size: 24.0,
|
||||||
),
|
),
|
||||||
onPressed: onBackPressed,
|
onPressed: onBackPressed,
|
||||||
@@ -61,7 +65,7 @@ class ImageHeader extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
actions: <Widget>[
|
actions: <Widget>[
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: StreamSvgIcon.Icon_menu_point_v(
|
icon: StreamSvgIcon.Icon_menu_point_v(
|
||||||
color: Colors.black,
|
color: StreamChatTheme.of(context).colorTheme.black,
|
||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
_showMessageActionModalBottomSheet(context);
|
_showMessageActionModalBottomSheet(context);
|
||||||
@@ -81,11 +85,7 @@ class ImageHeader extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Text(
|
Text(
|
||||||
userName,
|
userName,
|
||||||
style: TextStyle(
|
style: StreamChatTheme.of(context).textTheme.headlineBold,
|
||||||
fontSize: 16.0,
|
|
||||||
fontWeight: FontWeight.w700,
|
|
||||||
color: Colors.black,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
sentAt,
|
sentAt,
|
||||||
@@ -106,6 +106,7 @@ class ImageHeader extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
|
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
|
barrierColor: StreamChatTheme.of(context).colorTheme.overlay,
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return StreamChannel(
|
return StreamChannel(
|
||||||
channel: channel,
|
channel: channel,
|
||||||
@@ -115,6 +116,7 @@ class ImageHeader extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
message: message,
|
message: message,
|
||||||
urls: urls,
|
urls: urls,
|
||||||
currentIndex: currentIndex,
|
currentIndex: currentIndex,
|
||||||
|
onShowMessage: onShowMessage,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ class LazyLoadScrollView extends StatefulWidget {
|
|||||||
final bool isLoading;
|
final bool isLoading;
|
||||||
|
|
||||||
/// Initiates a LazyLoadScrollView widget
|
/// Initiates a LazyLoadScrollView widget
|
||||||
LazyLoadScrollView({
|
const LazyLoadScrollView({
|
||||||
Key key,
|
Key key,
|
||||||
@required this.child,
|
@required this.child,
|
||||||
this.onStartOfPage,
|
this.onStartOfPage,
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
|
import 'dart:ui' as ui;
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_svg/flutter_svg.dart';
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
import 'package:photo_manager/photo_manager.dart';
|
import 'package:photo_manager/photo_manager.dart';
|
||||||
import 'package:stream_chat_flutter/src/lazy_load_scroll_view.dart';
|
import 'package:stream_chat_flutter/src/lazy_load_scroll_view.dart';
|
||||||
import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
|
import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
|
||||||
import 'dart:ui' as ui;
|
|
||||||
|
import '../stream_chat_flutter.dart';
|
||||||
|
|
||||||
extension on Duration {
|
extension on Duration {
|
||||||
String format() {
|
String format() {
|
||||||
@@ -26,6 +29,7 @@ class MediaListView extends StatefulWidget {
|
|||||||
this.selectedIds = const [],
|
this.selectedIds = const [],
|
||||||
this.onSelect,
|
this.onSelect,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_MediaListViewState createState() => _MediaListViewState();
|
_MediaListViewState createState() => _MediaListViewState();
|
||||||
}
|
}
|
||||||
@@ -34,14 +38,11 @@ class _MediaListViewState extends State<MediaListView> {
|
|||||||
final _media = <AssetEntity>[];
|
final _media = <AssetEntity>[];
|
||||||
final ScrollController _scrollController = ScrollController();
|
final ScrollController _scrollController = ScrollController();
|
||||||
int _currentPage = 0;
|
int _currentPage = 0;
|
||||||
bool _endPagination = false;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return LazyLoadScrollView(
|
return LazyLoadScrollView(
|
||||||
onEndOfPage: () {
|
onEndOfPage: () async => _getMedia(),
|
||||||
_getMedia();
|
|
||||||
},
|
|
||||||
child: GridView.builder(
|
child: GridView.builder(
|
||||||
itemCount: _media.length,
|
itemCount: _media.length,
|
||||||
controller: _scrollController,
|
controller: _scrollController,
|
||||||
@@ -80,7 +81,10 @@ class _MediaListViewState extends State<MediaListView> {
|
|||||||
? 1.0
|
? 1.0
|
||||||
: 0.0,
|
: 0.0,
|
||||||
child: Container(
|
child: Container(
|
||||||
color: Colors.black.withOpacity(0.5),
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.black
|
||||||
|
.withOpacity(0.5),
|
||||||
alignment: Alignment.topRight,
|
alignment: Alignment.topRight,
|
||||||
padding: const EdgeInsets.only(
|
padding: const EdgeInsets.only(
|
||||||
top: 8,
|
top: 8,
|
||||||
@@ -88,10 +92,12 @@ class _MediaListViewState extends State<MediaListView> {
|
|||||||
),
|
),
|
||||||
child: CircleAvatar(
|
child: CircleAvatar(
|
||||||
radius: 12,
|
radius: 12,
|
||||||
backgroundColor: Colors.white,
|
backgroundColor:
|
||||||
|
StreamChatTheme.of(context).colorTheme.white,
|
||||||
child: StreamSvgIcon.check(
|
child: StreamSvgIcon.check(
|
||||||
size: 24,
|
size: 24,
|
||||||
color: Colors.black,
|
color:
|
||||||
|
StreamChatTheme.of(context).colorTheme.black,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -113,7 +119,7 @@ class _MediaListViewState extends State<MediaListView> {
|
|||||||
child: Text(
|
child: Text(
|
||||||
media.videoDuration.format(),
|
media.videoDuration.format(),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.white,
|
color: StreamChatTheme.of(context).colorTheme.white,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -141,15 +147,19 @@ class _MediaListViewState extends State<MediaListView> {
|
|||||||
void _getMedia() async {
|
void _getMedia() async {
|
||||||
final assetList = await PhotoManager.getAssetPathList(
|
final assetList = await PhotoManager.getAssetPathList(
|
||||||
hasAll: true,
|
hasAll: true,
|
||||||
).then((value) => value.singleWhere((element) => element.isAll));
|
).then((value) {
|
||||||
|
if (value?.isNotEmpty == true) {
|
||||||
|
return value.singleWhere((element) => element.isAll);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (assetList == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
final media = await assetList.getAssetListPaged(_currentPage, 50);
|
final media = await assetList.getAssetListPaged(_currentPage, 50);
|
||||||
|
|
||||||
if (media.isEmpty) {
|
if (!media.isEmpty) {
|
||||||
setState(() {
|
|
||||||
_endPagination = true;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_media.addAll(media);
|
_media.addAll(media);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -15,14 +15,17 @@ import 'stream_chat_theme.dart';
|
|||||||
|
|
||||||
class MessageActionsModal extends StatelessWidget {
|
class MessageActionsModal extends StatelessWidget {
|
||||||
final Widget Function(BuildContext, Message) editMessageInputBuilder;
|
final Widget Function(BuildContext, Message) editMessageInputBuilder;
|
||||||
final void Function(Message) onThreadTap;
|
final void Function(Message) onThreadReplyTap;
|
||||||
|
final void Function(Message) onReplyTap;
|
||||||
final Message message;
|
final Message message;
|
||||||
final MessageTheme messageTheme;
|
final MessageTheme messageTheme;
|
||||||
final bool showReactions;
|
final bool showReactions;
|
||||||
final bool showDeleteMessage;
|
final bool showDeleteMessage;
|
||||||
final bool showCopyMessage;
|
final bool showCopyMessage;
|
||||||
final bool showEditMessage;
|
final bool showEditMessage;
|
||||||
|
final bool showResendMessage;
|
||||||
final bool showReply;
|
final bool showReply;
|
||||||
|
final bool showThreadReply;
|
||||||
final bool reverse;
|
final bool reverse;
|
||||||
final ShapeBorder messageShape;
|
final ShapeBorder messageShape;
|
||||||
final DisplayWidget showUserAvatar;
|
final DisplayWidget showUserAvatar;
|
||||||
@@ -34,9 +37,12 @@ class MessageActionsModal extends StatelessWidget {
|
|||||||
this.showReactions = true,
|
this.showReactions = true,
|
||||||
this.showDeleteMessage = true,
|
this.showDeleteMessage = true,
|
||||||
this.showEditMessage = true,
|
this.showEditMessage = true,
|
||||||
this.onThreadTap,
|
this.onReplyTap,
|
||||||
|
this.onThreadReplyTap,
|
||||||
this.showCopyMessage = true,
|
this.showCopyMessage = true,
|
||||||
this.showReply = true,
|
this.showReply = true,
|
||||||
|
this.showResendMessage = true,
|
||||||
|
this.showThreadReply = true,
|
||||||
this.showUserAvatar = DisplayWidget.show,
|
this.showUserAvatar = DisplayWidget.show,
|
||||||
this.editMessageInputBuilder,
|
this.editMessageInputBuilder,
|
||||||
this.messageShape,
|
this.messageShape,
|
||||||
@@ -49,27 +55,35 @@ class MessageActionsModal extends StatelessWidget {
|
|||||||
final user = StreamChat.of(context).user;
|
final user = StreamChat.of(context).user;
|
||||||
|
|
||||||
final roughMaxSize = 2 * size.width / 3;
|
final roughMaxSize = 2 * size.width / 3;
|
||||||
|
var messageTextLength = message.text.length;
|
||||||
|
if (message.quotedMessage != null) {
|
||||||
|
var quotedMessageLength = message.quotedMessage.text.length + 40;
|
||||||
|
if (message.quotedMessage.attachments?.isNotEmpty == true) {
|
||||||
|
quotedMessageLength += 40;
|
||||||
|
}
|
||||||
|
if (quotedMessageLength > messageTextLength) {
|
||||||
|
messageTextLength = quotedMessageLength;
|
||||||
|
}
|
||||||
|
}
|
||||||
final roughSentenceSize =
|
final roughSentenceSize =
|
||||||
message.text.length * messageTheme.messageText.fontSize * 1.2;
|
messageTextLength * messageTheme.messageText.fontSize * 1.2;
|
||||||
final divFactor = message.attachments?.isNotEmpty == true
|
final divFactor = message.attachments?.isNotEmpty == true
|
||||||
? 1
|
? 1
|
||||||
: (roughSentenceSize == 0 ? 1 : (roughSentenceSize / roughMaxSize));
|
: (roughSentenceSize == 0 ? 1 : (roughSentenceSize / roughMaxSize));
|
||||||
|
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
behavior: HitTestBehavior.translucent,
|
behavior: HitTestBehavior.translucent,
|
||||||
onTap: () {
|
onTap: () => Navigator.maybePop(context),
|
||||||
Navigator.pop(context);
|
|
||||||
},
|
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
Positioned.fill(
|
Positioned.fill(
|
||||||
child: BackdropFilter(
|
child: BackdropFilter(
|
||||||
filter: ImageFilter.blur(
|
filter: ImageFilter.blur(
|
||||||
sigmaX: 10.8731,
|
sigmaX: 10,
|
||||||
sigmaY: 10.8731,
|
sigmaY: 10,
|
||||||
),
|
),
|
||||||
child: Container(
|
child: Container(
|
||||||
color: Colors.black.withOpacity(0.1),
|
color: StreamChatTheme.of(context).colorTheme.overlay,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -114,10 +128,14 @@ class MessageActionsModal extends StatelessWidget {
|
|||||||
showReactions: false,
|
showReactions: false,
|
||||||
showUsername: false,
|
showUsername: false,
|
||||||
showThreadReplyIndicator: false,
|
showThreadReplyIndicator: false,
|
||||||
|
showReplyIndicator: false,
|
||||||
showUserAvatar: showUserAvatar,
|
showUserAvatar: showUserAvatar,
|
||||||
showTimestamp: false,
|
showTimestamp: false,
|
||||||
translateUserAvatar: false,
|
translateUserAvatar: false,
|
||||||
showReactionPickerIndicator: true,
|
showReactionPickerIndicator: showReactions &&
|
||||||
|
(message.status ==
|
||||||
|
MessageSendingStatus.SENT ||
|
||||||
|
message.status == null),
|
||||||
showInChannelIndicator: false,
|
showInChannelIndicator: false,
|
||||||
showSendingIndicator: DisplayWidget.gone,
|
showSendingIndicator: DisplayWidget.gone,
|
||||||
shape: messageShape,
|
shape: messageShape,
|
||||||
@@ -125,9 +143,7 @@ class MessageActionsModal extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
SizedBox(
|
SizedBox(height: 8),
|
||||||
height: 8,
|
|
||||||
),
|
|
||||||
TweenAnimationBuilder<double>(
|
TweenAnimationBuilder<double>(
|
||||||
tween: Tween(begin: 0.0, end: 1.0),
|
tween: Tween(begin: 0.0, end: 1.0),
|
||||||
duration: Duration(milliseconds: 300),
|
duration: Duration(milliseconds: 300),
|
||||||
@@ -143,6 +159,9 @@ class MessageActionsModal extends StatelessWidget {
|
|||||||
horizontal: 48.0,
|
horizontal: 48.0,
|
||||||
),
|
),
|
||||||
child: Material(
|
child: Material(
|
||||||
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.whiteSnow,
|
||||||
clipBehavior: Clip.hardEdge,
|
clipBehavior: Clip.hardEdge,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
@@ -159,6 +178,14 @@ class MessageActionsModal extends StatelessWidget {
|
|||||||
message.status == null) &&
|
message.status == null) &&
|
||||||
message.parentId == null)
|
message.parentId == null)
|
||||||
_buildReplyButton(context),
|
_buildReplyButton(context),
|
||||||
|
if (showThreadReply &&
|
||||||
|
(message.status ==
|
||||||
|
MessageSendingStatus.SENT ||
|
||||||
|
message.status == null) &&
|
||||||
|
message.parentId == null)
|
||||||
|
_buildThreadReplyButton(context),
|
||||||
|
if (showResendMessage)
|
||||||
|
_buildResendMessage(context),
|
||||||
if (showEditMessage)
|
if (showEditMessage)
|
||||||
_buildEditMessage(context),
|
_buildEditMessage(context),
|
||||||
if (showDeleteMessage)
|
if (showDeleteMessage)
|
||||||
@@ -182,10 +209,29 @@ class MessageActionsModal extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildDeleteButton(BuildContext context) {
|
Widget _buildReplyButton(BuildContext context) {
|
||||||
return ListTile(
|
return ListTile(
|
||||||
title: Text(
|
title: Text(
|
||||||
'Delete message',
|
'Reply',
|
||||||
|
style: Theme.of(context).textTheme.headline6,
|
||||||
|
),
|
||||||
|
leading: StreamSvgIcon.reply(
|
||||||
|
color: StreamChatTheme.of(context).primaryIconTheme.color,
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
if (onReplyTap != null) {
|
||||||
|
onReplyTap(message);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildDeleteButton(BuildContext context) {
|
||||||
|
final isDeleteFailed = message.status == MessageSendingStatus.FAILED_DELETE;
|
||||||
|
return ListTile(
|
||||||
|
title: Text(
|
||||||
|
isDeleteFailed ? 'Retry deleting message' : 'Delete message',
|
||||||
style:
|
style:
|
||||||
Theme.of(context).textTheme.headline6.copyWith(color: Colors.red),
|
Theme.of(context).textTheme.headline6.copyWith(color: Colors.red),
|
||||||
),
|
),
|
||||||
@@ -234,6 +280,29 @@ class MessageActionsModal extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildResendMessage(BuildContext context) {
|
||||||
|
final isUpdateFailed = message.status == MessageSendingStatus.FAILED_UPDATE;
|
||||||
|
return ListTile(
|
||||||
|
title: Text(
|
||||||
|
isUpdateFailed ? 'Resend edited message' : 'Resend',
|
||||||
|
style: Theme.of(context).textTheme.headline6,
|
||||||
|
),
|
||||||
|
leading: StreamSvgIcon.circle_up(
|
||||||
|
color: StreamChatTheme.of(context).colorTheme.accentBlue,
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
final client = StreamChat.of(context).client;
|
||||||
|
final channel = StreamChannel.of(context).channel;
|
||||||
|
if (isUpdateFailed) {
|
||||||
|
client.updateMessage(message, channel.cid);
|
||||||
|
} else {
|
||||||
|
channel.sendMessage(message);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
void _showEditBottomSheet(BuildContext context) {
|
void _showEditBottomSheet(BuildContext context) {
|
||||||
final channel = StreamChannel.of(context).channel;
|
final channel = StreamChannel.of(context).channel;
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
@@ -241,10 +310,11 @@ class MessageActionsModal extends StatelessWidget {
|
|||||||
elevation: 2,
|
elevation: 2,
|
||||||
clipBehavior: Clip.hardEdge,
|
clipBehavior: Clip.hardEdge,
|
||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
|
backgroundColor: StreamChatTheme.of(context).colorTheme.white,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.only(
|
borderRadius: BorderRadius.only(
|
||||||
topLeft: Radius.circular(32),
|
topLeft: Radius.circular(16),
|
||||||
topRight: Radius.circular(32),
|
topRight: Radius.circular(16),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
@@ -256,39 +326,26 @@ class MessageActionsModal extends StatelessWidget {
|
|||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(
|
padding: const EdgeInsets.fromLTRB(8, 8, 8, 0),
|
||||||
top: 16.0,
|
|
||||||
left: 16.0,
|
|
||||||
right: 16.0,
|
|
||||||
),
|
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
IconButton(
|
Padding(
|
||||||
icon: StreamSvgIcon.edit(
|
padding: const EdgeInsets.all(8.0),
|
||||||
size: 22,
|
child: StreamSvgIcon.edit(
|
||||||
color:
|
color: StreamChatTheme.of(context)
|
||||||
StreamChatTheme.of(context).primaryIconTheme.color,
|
.colorTheme
|
||||||
|
.greyGainsboro,
|
||||||
),
|
),
|
||||||
onPressed: () {},
|
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
'Edit message',
|
'Edit Message',
|
||||||
style: Theme.of(context)
|
style: TextStyle(fontWeight: FontWeight.bold),
|
||||||
.textTheme
|
|
||||||
.headline6
|
|
||||||
.copyWith(fontWeight: FontWeight.bold),
|
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: Icon(
|
visualDensity: VisualDensity.compact,
|
||||||
Icons.cancel_outlined,
|
icon: StreamSvgIcon.close_small(),
|
||||||
size: 22,
|
onPressed: Navigator.of(context).pop,
|
||||||
color:
|
|
||||||
StreamChatTheme.of(context).primaryIconTheme.color,
|
|
||||||
),
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -301,9 +358,10 @@ class MessageActionsModal extends StatelessWidget {
|
|||||||
? editMessageInputBuilder(context, message)
|
? editMessageInputBuilder(context, message)
|
||||||
: MessageInput(
|
: MessageInput(
|
||||||
editMessage: message,
|
editMessage: message,
|
||||||
onMessageSent: (_) {
|
preMessageSending: (m) {
|
||||||
FocusScope.of(context).unfocus();
|
FocusScope.of(context).unfocus();
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
|
return m;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -314,7 +372,7 @@ class MessageActionsModal extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildReplyButton(BuildContext context) {
|
Widget _buildThreadReplyButton(BuildContext context) {
|
||||||
return ListTile(
|
return ListTile(
|
||||||
title: Text(
|
title: Text(
|
||||||
'Thread reply',
|
'Thread reply',
|
||||||
@@ -325,8 +383,8 @@ class MessageActionsModal extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
if (onThreadTap != null) {
|
if (onThreadReplyTap != null) {
|
||||||
onThreadTap(message);
|
onThreadReplyTap(message);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:emojis/emoji.dart';
|
import 'package:emojis/emoji.dart';
|
||||||
import 'package:file_picker/file_picker.dart';
|
import 'package:file_picker/file_picker.dart';
|
||||||
@@ -21,8 +22,10 @@ import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
|
|||||||
import 'package:stream_chat_flutter/src/user_avatar.dart';
|
import 'package:stream_chat_flutter/src/user_avatar.dart';
|
||||||
import 'package:substring_highlight/substring_highlight.dart';
|
import 'package:substring_highlight/substring_highlight.dart';
|
||||||
import 'package:video_compress/video_compress.dart';
|
import 'package:video_compress/video_compress.dart';
|
||||||
|
import 'extension.dart';
|
||||||
|
|
||||||
import '../stream_chat_flutter.dart';
|
import '../stream_chat_flutter.dart';
|
||||||
|
import 'quoted_message_widget.dart';
|
||||||
import 'stream_channel.dart';
|
import 'stream_channel.dart';
|
||||||
|
|
||||||
typedef FileUploader = Future<String> Function(PlatformFile, Channel);
|
typedef FileUploader = Future<String> Function(PlatformFile, Channel);
|
||||||
@@ -107,6 +110,8 @@ class MessageInput extends StatefulWidget {
|
|||||||
this.actionsLocation = ActionsLocation.left,
|
this.actionsLocation = ActionsLocation.left,
|
||||||
this.attachmentThumbnailBuilders,
|
this.attachmentThumbnailBuilders,
|
||||||
this.focusNode,
|
this.focusNode,
|
||||||
|
this.quotedMessage,
|
||||||
|
this.onQuotedMessageCleared,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
/// Message to edit
|
/// Message to edit
|
||||||
@@ -155,6 +160,12 @@ class MessageInput extends StatefulWidget {
|
|||||||
/// The focus node associated to the TextField
|
/// The focus node associated to the TextField
|
||||||
final FocusNode focusNode;
|
final FocusNode focusNode;
|
||||||
|
|
||||||
|
///
|
||||||
|
final Message quotedMessage;
|
||||||
|
|
||||||
|
///
|
||||||
|
final VoidCallback onQuotedMessageCleared;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
MessageInputState createState() => MessageInputState();
|
MessageInputState createState() => MessageInputState();
|
||||||
|
|
||||||
@@ -166,7 +177,7 @@ class MessageInput extends StatefulWidget {
|
|||||||
|
|
||||||
if (messageInputState == null) {
|
if (messageInputState == null) {
|
||||||
throw Exception(
|
throw Exception(
|
||||||
'You must have a MessageInput widget as anchestor of your widget tree');
|
'You must have a MessageInput widget as ancestor of your widget tree');
|
||||||
}
|
}
|
||||||
|
|
||||||
return messageInputState;
|
return messageInputState;
|
||||||
@@ -196,9 +207,11 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
/// The editing controller passed to the input TextField
|
/// The editing controller passed to the input TextField
|
||||||
TextEditingController textEditingController;
|
TextEditingController textEditingController;
|
||||||
|
|
||||||
|
bool get _hasQuotedMessage => widget.quotedMessage != null;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SafeArea(
|
Widget child = SafeArea(
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onPanUpdate: (details) {
|
onPanUpdate: (details) {
|
||||||
if (details.delta.dy > 0) {
|
if (details.delta.dy > 0) {
|
||||||
@@ -213,6 +226,32 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
|
if (_hasQuotedMessage)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(8, 8, 8, 0),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: StreamSvgIcon.reply(
|
||||||
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.greyGainsboro,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'Reply to Message',
|
||||||
|
style: TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
visualDensity: VisualDensity.compact,
|
||||||
|
icon: StreamSvgIcon.close_small(),
|
||||||
|
onPressed: widget.onQuotedMessageCleared,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8.0),
|
||||||
child: _buildTextField(context),
|
child: _buildTextField(context),
|
||||||
@@ -227,6 +266,14 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
if (widget.editMessage == null) {
|
||||||
|
child = Material(
|
||||||
|
color: StreamChatTheme.of(context).colorTheme.white,
|
||||||
|
elevation: 8,
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return child;
|
||||||
}
|
}
|
||||||
|
|
||||||
Flex _buildTextField(BuildContext context) {
|
Flex _buildTextField(BuildContext context) {
|
||||||
@@ -263,7 +310,10 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
border: _sendAsDm
|
border: _sendAsDm
|
||||||
? null
|
? null
|
||||||
: Border.all(
|
: Border.all(
|
||||||
color: Colors.black.withOpacity(.5),
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.black
|
||||||
|
.withOpacity(.5),
|
||||||
width: 2,
|
width: 2,
|
||||||
),
|
),
|
||||||
borderRadius: BorderRadius.circular(3),
|
borderRadius: BorderRadius.circular(3),
|
||||||
@@ -272,8 +322,8 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
child: Material(
|
child: Material(
|
||||||
borderRadius: BorderRadius.circular(3),
|
borderRadius: BorderRadius.circular(3),
|
||||||
color: _sendAsDm
|
color: _sendAsDm
|
||||||
? StreamChatTheme.of(context).accentColor
|
? StreamChatTheme.of(context).colorTheme.accentBlue
|
||||||
: Colors.white,
|
: StreamChatTheme.of(context).colorTheme.white,
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
setState(() {
|
setState(() {
|
||||||
@@ -288,7 +338,7 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
: CrossFadeState.showSecond,
|
: CrossFadeState.showSecond,
|
||||||
firstChild: StreamSvgIcon.check(
|
firstChild: StreamSvgIcon.check(
|
||||||
size: 16.0,
|
size: 16.0,
|
||||||
color: Colors.white,
|
color: StreamChatTheme.of(context).colorTheme.white,
|
||||||
),
|
),
|
||||||
secondChild: SizedBox(
|
secondChild: SizedBox(
|
||||||
height: 16,
|
height: 16,
|
||||||
@@ -325,17 +375,23 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
return AnimatedCrossFade(
|
return AnimatedCrossFade(
|
||||||
crossFadeState:
|
crossFadeState:
|
||||||
_actionsShrunk ? CrossFadeState.showFirst : CrossFadeState.showSecond,
|
_actionsShrunk ? CrossFadeState.showFirst : CrossFadeState.showSecond,
|
||||||
firstChild: InkWell(
|
firstChild: Padding(
|
||||||
onTap: () {
|
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 8),
|
||||||
|
child: IconButton(
|
||||||
|
onPressed: () {
|
||||||
setState(() {
|
setState(() {
|
||||||
_actionsShrunk = false;
|
_actionsShrunk = false;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
child: Padding(
|
icon: StreamSvgIcon.emptyCircleLeft(
|
||||||
padding: const EdgeInsets.all(8.0) + EdgeInsets.only(bottom: 3.0),
|
color: StreamChatTheme.of(context).colorTheme.accentBlue,
|
||||||
child: StreamSvgIcon.emptyCircleLeft(
|
|
||||||
color: StreamChatTheme.of(context).accentColor,
|
|
||||||
),
|
),
|
||||||
|
padding: const EdgeInsets.all(0),
|
||||||
|
constraints: BoxConstraints.tightFor(
|
||||||
|
height: 24,
|
||||||
|
width: 24,
|
||||||
|
),
|
||||||
|
splashRadius: 24,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
secondChild: Row(
|
secondChild: Row(
|
||||||
@@ -361,13 +417,15 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(24.0),
|
borderRadius: BorderRadius.circular(24.0),
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: Colors.grey,
|
color: StreamChatTheme.of(context).colorTheme.greyGainsboro,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
padding: _attachments.isEmpty ? null : EdgeInsets.all(6.0),
|
padding: _attachments.isEmpty ? null : EdgeInsets.all(6.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
|
_buildReplyToMessage(),
|
||||||
_buildAttachments(),
|
_buildAttachments(),
|
||||||
LimitedBox(
|
LimitedBox(
|
||||||
maxHeight: widget.maxHeight,
|
maxHeight: widget.maxHeight,
|
||||||
@@ -408,23 +466,31 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
padding:
|
padding:
|
||||||
const EdgeInsets.symmetric(horizontal: 8.0),
|
const EdgeInsets.symmetric(horizontal: 8.0),
|
||||||
child: Chip(
|
child: Chip(
|
||||||
backgroundColor:
|
backgroundColor: StreamChatTheme.of(context)
|
||||||
StreamChatTheme.of(context).accentColor,
|
.colorTheme
|
||||||
|
.accentBlue,
|
||||||
padding: EdgeInsets.zero,
|
padding: EdgeInsets.zero,
|
||||||
labelPadding:
|
labelPadding:
|
||||||
EdgeInsets.symmetric(horizontal: 9.0),
|
EdgeInsets.symmetric(horizontal: 8.0),
|
||||||
label: Row(
|
label: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
StreamSvgIcon.lightning(
|
StreamSvgIcon.lightning(
|
||||||
color: Colors.white,
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.white,
|
||||||
size: 16.0,
|
size: 16.0,
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
_chosenCommand?.name?.toUpperCase() ?? "",
|
_chosenCommand?.name?.toUpperCase() ?? '',
|
||||||
style: TextStyle(
|
style: StreamChatTheme.of(context)
|
||||||
color: Colors.white, fontSize: 12.0),
|
.textTheme
|
||||||
|
.footnote
|
||||||
|
.copyWith(
|
||||||
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.white),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -432,23 +498,14 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
suffixIcon: _commandEnabled
|
suffixIcon: _commandEnabled
|
||||||
? InkWell(
|
? IconButton(
|
||||||
child: Padding(
|
icon: StreamSvgIcon.close_small(),
|
||||||
padding:
|
splashRadius: 24,
|
||||||
const EdgeInsets.symmetric(horizontal: 8.0),
|
onPressed: () {
|
||||||
child: StreamSvgIcon.close_small(),
|
setState(() => _commandEnabled = false);
|
||||||
),
|
|
||||||
onTap: () {
|
|
||||||
setState(() {
|
|
||||||
_commandEnabled = false;
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
suffixIconConstraints: BoxConstraints(
|
|
||||||
maxHeight: 24.0,
|
|
||||||
maxWidth: 40.0,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
textCapitalization: TextCapitalization.sentences,
|
textCapitalization: TextCapitalization.sentences,
|
||||||
),
|
),
|
||||||
@@ -602,12 +659,12 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(8.0),
|
borderRadius: BorderRadius.circular(8.0),
|
||||||
),
|
),
|
||||||
color: StreamChatTheme.of(context).primaryColor,
|
color: StreamChatTheme.of(context).colorTheme.white,
|
||||||
clipBehavior: Clip.antiAlias,
|
clipBehavior: Clip.antiAlias,
|
||||||
child: Container(
|
child: Container(
|
||||||
constraints: BoxConstraints.loose(Size.fromHeight(400)),
|
constraints: BoxConstraints.loose(Size.fromHeight(400)),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: StreamChatTheme.of(context).primaryColor,
|
color: StreamChatTheme.of(context).colorTheme.white,
|
||||||
borderRadius: BorderRadius.circular(8.0)),
|
borderRadius: BorderRadius.circular(8.0)),
|
||||||
child: ListView(
|
child: ListView(
|
||||||
padding: const EdgeInsets.all(0),
|
padding: const EdgeInsets.all(0),
|
||||||
@@ -625,13 +682,17 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
),
|
),
|
||||||
child: StreamSvgIcon.lightning(
|
child: StreamSvgIcon.lightning(
|
||||||
color: StreamChatTheme.of(context)
|
color: StreamChatTheme.of(context)
|
||||||
.accentColor,
|
.colorTheme
|
||||||
|
.accentBlue,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
'Instant Commands',
|
'Instant Commands',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.black.withOpacity(.5),
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.black
|
||||||
|
.withOpacity(.5),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
@@ -658,15 +719,6 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
trailing: CircleAvatar(
|
|
||||||
backgroundColor:
|
|
||||||
StreamChatTheme.of(context).accentColor,
|
|
||||||
child: StreamSvgIcon.lightning(
|
|
||||||
color: Colors.white,
|
|
||||||
size: 12.5,
|
|
||||||
),
|
|
||||||
maxRadius: 12,
|
|
||||||
),
|
|
||||||
//subtitle: Text(c.description),
|
//subtitle: Text(c.description),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
_setCommand(c);
|
_setCommand(c);
|
||||||
@@ -693,27 +745,36 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
switch (index) {
|
switch (index) {
|
||||||
case 0:
|
case 0:
|
||||||
return _attachments.isEmpty
|
return _attachments.isEmpty
|
||||||
? StreamChatTheme.of(context).accentColor
|
? StreamChatTheme.of(context).colorTheme.accentBlue
|
||||||
: (!_attachmentContainsFile
|
: (!_attachmentContainsFile
|
||||||
? StreamChatTheme.of(context).accentColor
|
? StreamChatTheme.of(context).colorTheme.accentBlue
|
||||||
: Colors.black.withOpacity(0.2));
|
: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.black
|
||||||
|
.withOpacity(0.2));
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
return _attachmentContainsFile
|
return _attachmentContainsFile
|
||||||
? StreamChatTheme.of(context).accentColor
|
? StreamChatTheme.of(context).colorTheme.accentBlue
|
||||||
: (_attachments.isEmpty
|
: (_attachments.isEmpty
|
||||||
? Colors.black.withOpacity(0.5)
|
? StreamChatTheme.of(context)
|
||||||
: Colors.black.withOpacity(0.2));
|
.colorTheme
|
||||||
|
.black
|
||||||
|
.withOpacity(0.5)
|
||||||
|
: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.black
|
||||||
|
.withOpacity(0.2));
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
return _attachmentContainsFile && _attachments.isNotEmpty
|
return _attachmentContainsFile && _attachments.isNotEmpty
|
||||||
? Colors.black.withOpacity(0.2)
|
? StreamChatTheme.of(context).colorTheme.black.withOpacity(0.2)
|
||||||
: Colors.black.withOpacity(0.5);
|
: StreamChatTheme.of(context).colorTheme.black.withOpacity(0.5);
|
||||||
break;
|
break;
|
||||||
case 3:
|
case 3:
|
||||||
return _attachmentContainsFile && _attachments.isNotEmpty
|
return _attachmentContainsFile && _attachments.isNotEmpty
|
||||||
? Colors.black.withOpacity(0.2)
|
? StreamChatTheme.of(context).colorTheme.black.withOpacity(0.2)
|
||||||
: Colors.black.withOpacity(0.5);
|
: StreamChatTheme.of(context).colorTheme.black.withOpacity(0.5);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -722,7 +783,7 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
duration: _animateContainer ? Duration(milliseconds: 300) : Duration.zero,
|
duration: _animateContainer ? Duration(milliseconds: 300) : Duration.zero,
|
||||||
height: _openFilePickerSection ? _filePickerSize : 0,
|
height: _openFilePickerSection ? _filePickerSize : 0,
|
||||||
child: Material(
|
child: Material(
|
||||||
color: Color(0xFFF2F2F2),
|
color: StreamChatTheme.of(context).colorTheme.whiteSmoke,
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
@@ -789,7 +850,7 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
},
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.white,
|
color: StreamChatTheme.of(context).colorTheme.white,
|
||||||
borderRadius: BorderRadius.only(
|
borderRadius: BorderRadius.only(
|
||||||
topLeft: Radius.circular(16.0),
|
topLeft: Radius.circular(16.0),
|
||||||
topRight: Radius.circular(16.0),
|
topRight: Radius.circular(16.0),
|
||||||
@@ -804,7 +865,8 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
width: 40.0,
|
width: 40.0,
|
||||||
height: 4.0,
|
height: 4.0,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Color(0xFFF2F2F2),
|
color:
|
||||||
|
StreamChatTheme.of(context).colorTheme.whiteSmoke,
|
||||||
borderRadius: BorderRadius.circular(4.0),
|
borderRadius: BorderRadius.circular(4.0),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -817,7 +879,7 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.white,
|
color: StreamChatTheme.of(context).colorTheme.white,
|
||||||
borderRadius: BorderRadius.circular(8.0),
|
borderRadius: BorderRadius.circular(8.0),
|
||||||
),
|
),
|
||||||
child: _buildPickerSection(),
|
child: _buildPickerSection(),
|
||||||
@@ -852,11 +914,12 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
},
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
constraints: BoxConstraints.expand(),
|
constraints: BoxConstraints.expand(),
|
||||||
color: Color(0xfff2f2f2),
|
color: StreamChatTheme.of(context).colorTheme.whiteSmoke,
|
||||||
child: Text(
|
child: Text(
|
||||||
'Add more files',
|
'Add more files',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: StreamChatTheme.of(context).accentColor,
|
color:
|
||||||
|
StreamChatTheme.of(context).colorTheme.accentBlue,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -885,7 +948,7 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
PhotoManager.openSetting();
|
PhotoManager.openSetting();
|
||||||
},
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
color: Color(0xFFF2F2F2),
|
color: StreamChatTheme.of(context).colorTheme.whiteSmoke,
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
@@ -894,15 +957,19 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
'svgs/icon_picture_empty_state.svg',
|
'svgs/icon_picture_empty_state.svg',
|
||||||
package: 'stream_chat_flutter',
|
package: 'stream_chat_flutter',
|
||||||
height: 140,
|
height: 140,
|
||||||
color: StreamChatTheme.of(context).accentColor,
|
color:
|
||||||
|
StreamChatTheme.of(context).colorTheme.accentBlue,
|
||||||
),
|
),
|
||||||
Center(
|
Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
'Allow access to your gallery',
|
'Allow access to your gallery',
|
||||||
style: TextStyle(
|
style: StreamChatTheme.of(context)
|
||||||
fontSize: 14,
|
.textTheme
|
||||||
fontWeight: FontWeight.bold,
|
.bodyBold
|
||||||
color: StreamChatTheme.of(context).accentColor,
|
.copyWith(
|
||||||
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.accentBlue,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -1019,7 +1086,7 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
CircleAvatar _buildGiphyIcon() {
|
CircleAvatar _buildGiphyIcon() {
|
||||||
if (kIsWeb) {
|
if (kIsWeb) {
|
||||||
return CircleAvatar(
|
return CircleAvatar(
|
||||||
backgroundColor: Colors.black,
|
backgroundColor: StreamChatTheme.of(context).colorTheme.black,
|
||||||
child: Image.asset(
|
child: Image.asset(
|
||||||
'images/giphy_icon.png',
|
'images/giphy_icon.png',
|
||||||
package: 'stream_chat_flutter',
|
package: 'stream_chat_flutter',
|
||||||
@@ -1080,7 +1147,7 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
child: Card(
|
child: Card(
|
||||||
margin: EdgeInsets.all(8.0),
|
margin: EdgeInsets.all(8.0),
|
||||||
elevation: 2.0,
|
elevation: 2.0,
|
||||||
color: StreamChatTheme.of(context).primaryColor,
|
color: StreamChatTheme.of(context).colorTheme.white,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(8.0),
|
borderRadius: BorderRadius.circular(8.0),
|
||||||
),
|
),
|
||||||
@@ -1088,7 +1155,7 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
child: Container(
|
child: Container(
|
||||||
constraints: BoxConstraints.loose(Size.fromHeight(400)),
|
constraints: BoxConstraints.loose(Size.fromHeight(400)),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: StreamChatTheme.of(context).primaryColor,
|
color: StreamChatTheme.of(context).colorTheme.white,
|
||||||
),
|
),
|
||||||
child: FutureBuilder<List<Member>>(
|
child: FutureBuilder<List<Member>>(
|
||||||
future: queryMembers ?? Future.value(members),
|
future: queryMembers ?? Future.value(members),
|
||||||
@@ -1116,7 +1183,8 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
subtitle: Text('@${m.userId}'),
|
subtitle: Text('@${m.userId}'),
|
||||||
trailing: StreamSvgIcon.mentions(
|
trailing: StreamSvgIcon.mentions(
|
||||||
color: StreamChatTheme.of(context)
|
color: StreamChatTheme.of(context)
|
||||||
.accentColor,
|
.colorTheme
|
||||||
|
.accentBlue,
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
_mentionedUsers.add(m.user);
|
_mentionedUsers.add(m.user);
|
||||||
@@ -1181,7 +1249,7 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
child: Card(
|
child: Card(
|
||||||
margin: EdgeInsets.all(8.0),
|
margin: EdgeInsets.all(8.0),
|
||||||
elevation: 2.0,
|
elevation: 2.0,
|
||||||
color: StreamChatTheme.of(context).primaryColor,
|
color: StreamChatTheme.of(context).colorTheme.white,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(8.0),
|
borderRadius: BorderRadius.circular(8.0),
|
||||||
),
|
),
|
||||||
@@ -1196,7 +1264,7 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
offset: Offset(0, -4),
|
offset: Offset(0, -4),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
color: StreamChatTheme.of(context).primaryColor,
|
color: StreamChatTheme.of(context).colorTheme.white,
|
||||||
),
|
),
|
||||||
child: ListView.builder(
|
child: ListView.builder(
|
||||||
padding: const EdgeInsets.all(0),
|
padding: const EdgeInsets.all(0),
|
||||||
@@ -1212,14 +1280,19 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
padding:
|
padding:
|
||||||
const EdgeInsets.symmetric(horizontal: 8.0),
|
const EdgeInsets.symmetric(horizontal: 8.0),
|
||||||
child: StreamSvgIcon.smile(
|
child: StreamSvgIcon.smile(
|
||||||
color: StreamChatTheme.of(context).accentColor,
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.accentBlue,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Flexible(
|
Flexible(
|
||||||
child: Text(
|
child: Text(
|
||||||
'Emoji matching "$query"',
|
'Emoji matching "$query"',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.black.withOpacity(.5),
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.black
|
||||||
|
.withOpacity(.5),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -1280,6 +1353,25 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
_commandsOverlay = null;
|
_commandsOverlay = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildReplyToMessage() {
|
||||||
|
if (!_hasQuotedMessage) {
|
||||||
|
return Offstage();
|
||||||
|
}
|
||||||
|
final containsUrl = widget.quotedMessage.attachments
|
||||||
|
?.any((element) => element.ogScrapeUrl != null) ==
|
||||||
|
true;
|
||||||
|
return Transform(
|
||||||
|
transform: Matrix4.rotationY(pi),
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: QuotedMessageWidget(
|
||||||
|
reverse: true,
|
||||||
|
showBorder: !containsUrl,
|
||||||
|
message: widget.quotedMessage,
|
||||||
|
messageTheme: StreamChatTheme.of(context).otherMessageTheme,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildAttachments() {
|
Widget _buildAttachments() {
|
||||||
return _attachments.isEmpty
|
return _attachments.isEmpty
|
||||||
? Container()
|
? Container()
|
||||||
@@ -1313,10 +1405,15 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
child: InkWell(
|
child: InkWell(
|
||||||
child: CircleAvatar(
|
child: CircleAvatar(
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
Colors.black.withOpacity(0.2),
|
StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.black
|
||||||
|
.withOpacity(0.6),
|
||||||
maxRadius: 12.0,
|
maxRadius: 12.0,
|
||||||
child: StreamSvgIcon.close(
|
child: StreamSvgIcon.close(
|
||||||
color: Colors.white,
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.white,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
@@ -1401,11 +1498,11 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
_attachments.remove(attachment);
|
_attachments.remove(attachment);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
fillColor: Colors.black.withOpacity(.5),
|
fillColor: StreamChatTheme.of(context).colorTheme.black.withOpacity(.5),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: StreamSvgIcon.close(
|
child: StreamSvgIcon.close(
|
||||||
size: 24,
|
size: 24,
|
||||||
color: Colors.white,
|
color: StreamChatTheme.of(context).colorTheme.white,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -1488,17 +1585,21 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildCommandButton() {
|
Widget _buildCommandButton() {
|
||||||
return InkWell(
|
return Padding(
|
||||||
child: Padding(
|
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 8),
|
||||||
padding:
|
child: IconButton(
|
||||||
const EdgeInsets.only(left: 4.0, right: 8.0, top: 8.0, bottom: 8.0),
|
icon: StreamSvgIcon.lightning(
|
||||||
child: StreamSvgIcon.lightning(
|
|
||||||
color: _commandsOverlay != null
|
color: _commandsOverlay != null
|
||||||
? StreamChatTheme.of(context).accentColor
|
? StreamChatTheme.of(context).colorTheme.accentBlue
|
||||||
: Color(0xFF000000).withAlpha(128),
|
: StreamChatTheme.of(context).colorTheme.grey,
|
||||||
),
|
),
|
||||||
|
padding: const EdgeInsets.all(0),
|
||||||
|
constraints: BoxConstraints.tightFor(
|
||||||
|
height: 24,
|
||||||
|
width: 24,
|
||||||
),
|
),
|
||||||
onTap: () async {
|
splashRadius: 24,
|
||||||
|
onPressed: () async {
|
||||||
if (_openFilePickerSection) {
|
if (_openFilePickerSection) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_animateContainer = false;
|
_animateContainer = false;
|
||||||
@@ -1520,23 +1621,26 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildAttachmentButton() {
|
Widget _buildAttachmentButton() {
|
||||||
var padding = widget.editMessage == null ? 4.0 : 8.0;
|
return Padding(
|
||||||
return Center(
|
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 8),
|
||||||
child: InkWell(
|
child: IconButton(
|
||||||
child: Padding(
|
icon: StreamSvgIcon.attach(
|
||||||
padding:
|
|
||||||
EdgeInsets.only(left: 8.0, right: padding, top: 8.0, bottom: 8.0),
|
|
||||||
child: StreamSvgIcon.attach(
|
|
||||||
color: _openFilePickerSection
|
color: _openFilePickerSection
|
||||||
? StreamChatTheme.of(context).accentColor
|
? StreamChatTheme.of(context).colorTheme.accentBlue
|
||||||
: Color(0xFF000000).withAlpha(128),
|
: StreamChatTheme.of(context).colorTheme.grey,
|
||||||
),
|
),
|
||||||
|
padding: const EdgeInsets.all(0),
|
||||||
|
constraints: BoxConstraints.tightFor(
|
||||||
|
height: 24,
|
||||||
|
width: 24,
|
||||||
),
|
),
|
||||||
onTap: () async {
|
splashRadius: 24,
|
||||||
|
onPressed: () async {
|
||||||
_emojiOverlay?.remove();
|
_emojiOverlay?.remove();
|
||||||
_emojiOverlay = null;
|
_emojiOverlay = null;
|
||||||
_commandsOverlay?.remove();
|
_commandsOverlay?.remove();
|
||||||
@@ -1854,36 +1958,29 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
|
|
||||||
Widget _buildIdleSendButton(BuildContext context) {
|
Widget _buildIdleSendButton(BuildContext context) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.all(8.0) + EdgeInsets.only(bottom: 3.0),
|
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 8),
|
||||||
child: Center(
|
|
||||||
child: InkWell(
|
|
||||||
onTap: () {
|
|
||||||
sendMessage();
|
|
||||||
},
|
|
||||||
child: StreamSvgIcon(
|
child: StreamSvgIcon(
|
||||||
assetName: _getIdleSendIcon(),
|
assetName: _getIdleSendIcon(),
|
||||||
color: Colors.grey,
|
color: StreamChatTheme.of(context).colorTheme.greyGainsboro,
|
||||||
height: 24.0,
|
|
||||||
width: 24.0,
|
|
||||||
),
|
),
|
||||||
)),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildSendButton(BuildContext context) {
|
Widget _buildSendButton(BuildContext context) {
|
||||||
return Center(
|
return Padding(
|
||||||
child: Padding(
|
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 8),
|
||||||
padding: const EdgeInsets.all(8.0) + EdgeInsets.only(bottom: 3.0),
|
child: IconButton(
|
||||||
child: InkWell(
|
onPressed: sendMessage,
|
||||||
onTap: () {
|
visualDensity: VisualDensity.compact,
|
||||||
sendMessage();
|
padding: const EdgeInsets.all(0),
|
||||||
},
|
splashRadius: 24,
|
||||||
child: StreamSvgIcon(
|
constraints: BoxConstraints.tightFor(
|
||||||
assetName: _getSendIcon(),
|
height: 24,
|
||||||
color: StreamChatTheme.of(context).accentColor,
|
width: 24,
|
||||||
height: 24.0,
|
|
||||||
width: 24.0,
|
|
||||||
),
|
),
|
||||||
|
icon: StreamSvgIcon(
|
||||||
|
assetName: _getSendIcon(),
|
||||||
|
color: StreamChatTheme.of(context).colorTheme.accentBlue,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -1922,6 +2019,9 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
|
|
||||||
textEditingController.clear();
|
textEditingController.clear();
|
||||||
_attachments.clear();
|
_attachments.clear();
|
||||||
|
if (widget.onQuotedMessageCleared != null) {
|
||||||
|
widget.onQuotedMessageCleared();
|
||||||
|
}
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_messageIsPresent = false;
|
_messageIsPresent = false;
|
||||||
@@ -1953,6 +2053,12 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (widget.quotedMessage != null) {
|
||||||
|
message = message.copyWith(
|
||||||
|
quotedMessageId: widget.quotedMessage.id,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (widget.preMessageSending != null) {
|
if (widget.preMessageSending != null) {
|
||||||
message = await widget.preMessageSending(message);
|
message = await widget.preMessageSending(message);
|
||||||
}
|
}
|
||||||
@@ -1976,10 +2082,6 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
return sendingFuture.then((resp) {
|
return sendingFuture.then((resp) {
|
||||||
if (widget.onMessageSent != null) {
|
if (widget.onMessageSent != null) {
|
||||||
widget.onMessageSent(resp.message);
|
widget.onMessageSent(resp.message);
|
||||||
} else {
|
|
||||||
if (widget.editMessage != null) {
|
|
||||||
Navigator.pop(context);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1995,7 +2097,6 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
|
||||||
_focusNode = widget.focusNode ?? FocusNode();
|
_focusNode = widget.focusNode ?? FocusNode();
|
||||||
|
|
||||||
_emojiNames = Emoji.all().map((e) => e.name);
|
_emojiNames = Emoji.all().map((e) => e.name);
|
||||||
@@ -2073,12 +2174,6 @@ class _SendingAttachment {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
extension StringExtension on String {
|
|
||||||
String capitalize() {
|
|
||||||
return "${this[0].toUpperCase()}${this.substring(1)}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents a 2-tuple, or pair.
|
/// Represents a 2-tuple, or pair.
|
||||||
class Tuple2<T1, T2> {
|
class Tuple2<T1, T2> {
|
||||||
/// Returns the first item of the tuple
|
/// Returns the first item of the tuple
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import 'package:visibility_detector/visibility_detector.dart';
|
|||||||
import '../stream_chat_flutter.dart';
|
import '../stream_chat_flutter.dart';
|
||||||
import 'date_divider.dart';
|
import 'date_divider.dart';
|
||||||
import 'stream_channel.dart';
|
import 'stream_channel.dart';
|
||||||
|
import 'swipeable.dart';
|
||||||
|
|
||||||
typedef MessageBuilder = Widget Function(
|
typedef MessageBuilder = Widget Function(
|
||||||
BuildContext,
|
BuildContext,
|
||||||
@@ -29,6 +30,9 @@ typedef ParentMessageBuilder = Widget Function(
|
|||||||
typedef ThreadBuilder = Widget Function(BuildContext context, Message parent);
|
typedef ThreadBuilder = Widget Function(BuildContext context, Message parent);
|
||||||
typedef ThreadTapCallback = void Function(Message, Widget);
|
typedef ThreadTapCallback = void Function(Message, Widget);
|
||||||
|
|
||||||
|
typedef OnMessageSwiped = void Function(Message);
|
||||||
|
typedef ReplyTapCallback = void Function(Message);
|
||||||
|
|
||||||
class MessageDetails {
|
class MessageDetails {
|
||||||
/// True if the message belongs to the current user
|
/// True if the message belongs to the current user
|
||||||
bool isMyMessage;
|
bool isMyMessage;
|
||||||
@@ -109,14 +113,17 @@ class MessageListView extends StatefulWidget {
|
|||||||
this.parentMessage,
|
this.parentMessage,
|
||||||
this.threadBuilder,
|
this.threadBuilder,
|
||||||
this.onThreadTap,
|
this.onThreadTap,
|
||||||
|
this.onReplyTap,
|
||||||
this.dateDividerBuilder,
|
this.dateDividerBuilder,
|
||||||
this.scrollPhysics = const AlwaysScrollableScrollPhysics(),
|
this.scrollPhysics = const ClampingScrollPhysics(),
|
||||||
this.initialScrollIndex,
|
this.initialScrollIndex,
|
||||||
this.initialAlignment,
|
this.initialAlignment,
|
||||||
this.scrollController,
|
this.scrollController,
|
||||||
this.itemPositionListener,
|
this.itemPositionListener,
|
||||||
|
this.onMessageSwiped,
|
||||||
this.highlightInitialMessage = false,
|
this.highlightInitialMessage = false,
|
||||||
this.highlightColor = const Color(0XFFFBF4DD),
|
this.messageHighlightColor,
|
||||||
|
this.onShowMessage,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
/// Function used to build a custom message widget
|
/// Function used to build a custom message widget
|
||||||
@@ -158,15 +165,21 @@ class MessageListView extends StatefulWidget {
|
|||||||
/// The ScrollPhysics used by the ListView
|
/// The ScrollPhysics used by the ListView
|
||||||
final ScrollPhysics scrollPhysics;
|
final ScrollPhysics scrollPhysics;
|
||||||
|
|
||||||
|
/// Called when message item gets swiped
|
||||||
|
final OnMessageSwiped onMessageSwiped;
|
||||||
|
|
||||||
|
///
|
||||||
|
final ReplyTapCallback onReplyTap;
|
||||||
|
|
||||||
/// If true the list will highlight the initialMessage if there is any.
|
/// If true the list will highlight the initialMessage if there is any.
|
||||||
///
|
///
|
||||||
/// Also See [StreamChannel]
|
/// Also See [StreamChannel]
|
||||||
final bool highlightInitialMessage;
|
final bool highlightInitialMessage;
|
||||||
|
|
||||||
/// The color used for highlighting the initialMessage if there is any.
|
/// Color used while highlighting initial message
|
||||||
///
|
final Color messageHighlightColor;
|
||||||
/// Also See [highlightInitialMessage]
|
|
||||||
final Color highlightColor;
|
final ShowMessageCallback onShowMessage;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_MessageListViewState createState() => _MessageListViewState();
|
_MessageListViewState createState() => _MessageListViewState();
|
||||||
@@ -179,10 +192,10 @@ class _MessageListViewState extends State<MessageListView> {
|
|||||||
bool _showScrollToBottom = false;
|
bool _showScrollToBottom = false;
|
||||||
ItemPositionsListener _itemPositionListener;
|
ItemPositionsListener _itemPositionListener;
|
||||||
int _messageListLength;
|
int _messageListLength;
|
||||||
|
StreamChannelState streamChannel;
|
||||||
|
|
||||||
int get _initialIndex {
|
int get _initialIndex {
|
||||||
if (widget.initialScrollIndex != null) return widget.initialScrollIndex;
|
if (widget.initialScrollIndex != null) return widget.initialScrollIndex;
|
||||||
final streamChannel = StreamChannel.of(context);
|
|
||||||
if (streamChannel.initialMessageId != null) {
|
if (streamChannel.initialMessageId != null) {
|
||||||
final messages = streamChannel.channel.state.messages;
|
final messages = streamChannel.channel.state.messages;
|
||||||
final totalMessages = messages.length;
|
final totalMessages = messages.length;
|
||||||
@@ -202,11 +215,10 @@ class _MessageListViewState extends State<MessageListView> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool _isInitialMessage(String id) {
|
bool _isInitialMessage(String id) {
|
||||||
final streamChannel = StreamChannel.of(context);
|
|
||||||
return streamChannel.initialMessageId == id;
|
return streamChannel.initialMessageId == id;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool get _upToDate => StreamChannel.of(context).channel.state.isUpToDate;
|
bool get _upToDate => streamChannel.channel.state.isUpToDate;
|
||||||
|
|
||||||
bool _topPaginationActive = false;
|
bool _topPaginationActive = false;
|
||||||
bool _bottomPaginationActive = false;
|
bool _bottomPaginationActive = false;
|
||||||
@@ -222,22 +234,13 @@ class _MessageListViewState extends State<MessageListView> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final streamChannel = StreamChannel.of(context);
|
|
||||||
|
|
||||||
final messagesStream = widget.parentMessage != null
|
final messagesStream = widget.parentMessage != null
|
||||||
? streamChannel.channel.state.threadsStream
|
? streamChannel.channel.state.threadsStream
|
||||||
.where((threads) => threads.containsKey(widget.parentMessage.id))
|
.where((threads) => threads.containsKey(widget.parentMessage.id))
|
||||||
.map((threads) => threads[widget.parentMessage.id])
|
.map((threads) => threads[widget.parentMessage.id])
|
||||||
: streamChannel.channel.state?.messagesStream;
|
: streamChannel.channel.state?.messagesStream;
|
||||||
|
|
||||||
return WillPopScope(
|
return StreamBuilder<List<Message>>(
|
||||||
onWillPop: () async {
|
|
||||||
if (!_upToDate) {
|
|
||||||
await streamChannel.reloadChannel();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
child: StreamBuilder<List<Message>>(
|
|
||||||
stream: messagesStream?.map((messages) => messages
|
stream: messagesStream?.map((messages) => messages
|
||||||
?.where((e) =>
|
?.where((e) =>
|
||||||
!e.isDeleted ||
|
!e.isDeleted ||
|
||||||
@@ -252,16 +255,19 @@ class _MessageListViewState extends State<MessageListView> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final messageList = snapshot.data?.reversed?.toList() ?? [];
|
final messageList = snapshot.data?.reversed?.toList() ?? [];
|
||||||
|
|
||||||
if (messageList.isEmpty) {
|
if (messageList.isEmpty) {
|
||||||
if (_upToDate) {
|
if (_upToDate) {
|
||||||
return Center(
|
return Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
'No chats here yet...',
|
'No chats here yet...',
|
||||||
style: TextStyle(
|
style: StreamChatTheme.of(context)
|
||||||
fontSize: 12,
|
.textTheme
|
||||||
color: Colors.black.withOpacity(.5),
|
.footnote
|
||||||
),
|
.copyWith(
|
||||||
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.black
|
||||||
|
.withOpacity(.5)),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -344,14 +350,9 @@ class _MessageListViewState extends State<MessageListView> {
|
|||||||
buildParentMessage(widget.parentMessage),
|
buildParentMessage(widget.parentMessage),
|
||||||
Container(
|
Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
gradient: LinearGradient(
|
gradient: StreamChatTheme.of(context)
|
||||||
begin: Alignment.topCenter,
|
.colorTheme
|
||||||
end: Alignment.bottomCenter,
|
.bgGradient,
|
||||||
colors: [
|
|
||||||
Color(0XFFF7F7F7),
|
|
||||||
Color(0XFFFCFCFC),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8.0),
|
||||||
@@ -404,7 +405,7 @@ class _MessageListViewState extends State<MessageListView> {
|
|||||||
if (widget.messageBuilder != null) {
|
if (widget.messageBuilder != null) {
|
||||||
messageWidget = Builder(
|
messageWidget = Builder(
|
||||||
key: ValueKey<String>('MESSAGE-${message.id}'),
|
key: ValueKey<String>('MESSAGE-${message.id}'),
|
||||||
builder: (_) => widget.messageBuilder(
|
builder: (context) => widget.messageBuilder(
|
||||||
context,
|
context,
|
||||||
MessageDetails(
|
MessageDetails(
|
||||||
context,
|
context,
|
||||||
@@ -427,8 +428,7 @@ class _MessageListViewState extends State<MessageListView> {
|
|||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
messageWidget,
|
messageWidget,
|
||||||
Padding(
|
Padding(
|
||||||
padding:
|
padding: const EdgeInsets.symmetric(vertical: 12.0),
|
||||||
const EdgeInsets.symmetric(vertical: 12.0),
|
|
||||||
child: widget.dateDividerBuilder != null
|
child: widget.dateDividerBuilder != null
|
||||||
? widget.dateDividerBuilder(
|
? widget.dateDividerBuilder(
|
||||||
nextMessage.createdAt.toLocal())
|
nextMessage.createdAt.toLocal())
|
||||||
@@ -477,8 +477,7 @@ class _MessageListViewState extends State<MessageListView> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}),
|
});
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _paginateData(
|
Future<void> _paginateData(
|
||||||
@@ -498,7 +497,6 @@ class _MessageListViewState extends State<MessageListView> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildScrollToBottom() {
|
Widget _buildScrollToBottom() {
|
||||||
final streamChannel = StreamChannel.of(context);
|
|
||||||
return StreamBuilder<Tuple2<bool, int>>(
|
return StreamBuilder<Tuple2<bool, int>>(
|
||||||
stream: Rx.combineLatest2(
|
stream: Rx.combineLatest2(
|
||||||
streamChannel.channel.state.isUpToDateStream,
|
streamChannel.channel.state.isUpToDateStream,
|
||||||
@@ -529,9 +527,9 @@ class _MessageListViewState extends State<MessageListView> {
|
|||||||
clipBehavior: Clip.none,
|
clipBehavior: Clip.none,
|
||||||
children: [
|
children: [
|
||||||
FloatingActionButton(
|
FloatingActionButton(
|
||||||
backgroundColor: Colors.white,
|
backgroundColor: StreamChatTheme.of(context).colorTheme.white,
|
||||||
child: StreamSvgIcon.down(
|
child: StreamSvgIcon.down(
|
||||||
color: Colors.black,
|
color: StreamChatTheme.of(context).colorTheme.black,
|
||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
if (unreadCount > 0) {
|
if (unreadCount > 0) {
|
||||||
@@ -591,7 +589,10 @@ class _MessageListViewState extends State<MessageListView> {
|
|||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (snapshot.hasError) {
|
if (snapshot.hasError) {
|
||||||
return Container(
|
return Container(
|
||||||
color: Color(0xffd0021B).withAlpha(26),
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.accentRed
|
||||||
|
.withOpacity(.2),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Text('Error loading messages'),
|
child: Text('Error loading messages'),
|
||||||
),
|
),
|
||||||
@@ -696,6 +697,7 @@ class _MessageListViewState extends State<MessageListView> {
|
|||||||
return MessageWidget(
|
return MessageWidget(
|
||||||
showThreadReplyIndicator: false,
|
showThreadReplyIndicator: false,
|
||||||
showInChannelIndicator: false,
|
showInChannelIndicator: false,
|
||||||
|
showReplyIndicator: false,
|
||||||
message: message,
|
message: message,
|
||||||
reverse: isMyMessage,
|
reverse: isMyMessage,
|
||||||
showUsername: !isMyMessage,
|
showUsername: !isMyMessage,
|
||||||
@@ -720,6 +722,7 @@ class _MessageListViewState extends State<MessageListView> {
|
|||||||
messageTheme: isMyMessage
|
messageTheme: isMyMessage
|
||||||
? StreamChatTheme.of(context).ownMessageTheme
|
? StreamChatTheme.of(context).ownMessageTheme
|
||||||
: StreamChatTheme.of(context).otherMessageTheme,
|
: StreamChatTheme.of(context).otherMessageTheme,
|
||||||
|
onShowMessage: widget.onShowMessage,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -740,7 +743,7 @@ class _MessageListViewState extends State<MessageListView> {
|
|||||||
final isNextUser =
|
final isNextUser =
|
||||||
index - 2 >= 0 && message.user.id == messages[index - 2]?.user?.id;
|
index - 2 >= 0 && message.user.id == messages[index - 2]?.user?.id;
|
||||||
|
|
||||||
final channel = StreamChannel.of(context).channel;
|
final channel = streamChannel.channel;
|
||||||
final readList = channel.state?.read
|
final readList = channel.state?.read
|
||||||
?.where((element) => element.user.id != userId)
|
?.where((element) => element.user.id != userId)
|
||||||
?.where((read) =>
|
?.where((read) =>
|
||||||
@@ -753,6 +756,9 @@ class _MessageListViewState extends State<MessageListView> {
|
|||||||
|
|
||||||
final allRead = readList.length >= (channel.memberCount ?? 0) - 1;
|
final allRead = readList.length >= (channel.memberCount ?? 0) - 1;
|
||||||
|
|
||||||
|
final isThreadMessage =
|
||||||
|
widget.parentMessage != null || message?.showInChannel == true;
|
||||||
|
|
||||||
Widget child = MessageWidget(
|
Widget child = MessageWidget(
|
||||||
key: ValueKey<String>('MESSAGE-${message.id}'),
|
key: ValueKey<String>('MESSAGE-${message.id}'),
|
||||||
message: message,
|
message: message,
|
||||||
@@ -764,6 +770,26 @@ class _MessageListViewState extends State<MessageListView> {
|
|||||||
bottom: index == 0 ? 30 : (isNextUser ? 2 : 7),
|
bottom: index == 0 ? 30 : (isNextUser ? 2 : 7),
|
||||||
top: 3,
|
top: 3,
|
||||||
),
|
),
|
||||||
|
onQuotedMessageTap: (quotedMessageId) async {
|
||||||
|
final scrollToIndex = () {
|
||||||
|
final index = messages.indexWhere((m) => m.id == quotedMessageId);
|
||||||
|
_scrollController?.scrollTo(
|
||||||
|
index: index,
|
||||||
|
duration: const Duration(milliseconds: 350),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
if (messages.map((e) => e.id).contains(quotedMessageId)) {
|
||||||
|
scrollToIndex();
|
||||||
|
} else {
|
||||||
|
streamChannel.loadChannelAtMessage(quotedMessageId).then((_) {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
if (messages.map((e) => e.id).contains(quotedMessageId)) {
|
||||||
|
scrollToIndex();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
showInChannelIndicator: widget.parentMessage == null,
|
showInChannelIndicator: widget.parentMessage == null,
|
||||||
showThreadReplyIndicator: widget.parentMessage == null,
|
showThreadReplyIndicator: widget.parentMessage == null,
|
||||||
showUsername: !isMyMessage && !isNextUser,
|
showUsername: !isMyMessage && !isNextUser,
|
||||||
@@ -776,6 +802,7 @@ class _MessageListViewState extends State<MessageListView> {
|
|||||||
showDeleteMessage: isMyMessage,
|
showDeleteMessage: isMyMessage,
|
||||||
borderSide: isMyMessage ? BorderSide.none : null,
|
borderSide: isMyMessage ? BorderSide.none : null,
|
||||||
onThreadTap: _onThreadTap,
|
onThreadTap: _onThreadTap,
|
||||||
|
onReplyTap: widget.onReplyTap,
|
||||||
attachmentBorderRadiusGeometry: BorderRadius.only(
|
attachmentBorderRadiusGeometry: BorderRadius.only(
|
||||||
topLeft: Radius.circular(16),
|
topLeft: Radius.circular(16),
|
||||||
bottomLeft: Radius.circular(!isNextUser ? 0 : 16),
|
bottomLeft: Radius.circular(!isNextUser ? 0 : 16),
|
||||||
@@ -797,21 +824,33 @@ class _MessageListViewState extends State<MessageListView> {
|
|||||||
: StreamChatTheme.of(context).otherMessageTheme,
|
: StreamChatTheme.of(context).otherMessageTheme,
|
||||||
readList: readList,
|
readList: readList,
|
||||||
allRead: allRead,
|
allRead: allRead,
|
||||||
|
onShowMessage: widget.onShowMessage,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (!isThreadMessage) {
|
||||||
|
child = Swipeable(
|
||||||
|
onSwipeEnd: () => widget.onMessageSwiped(message),
|
||||||
|
backgroundIcon: StreamSvgIcon.reply(
|
||||||
|
color: StreamChatTheme.of(context).colorTheme.accentBlue,
|
||||||
|
),
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (!initialMessageHighlightComplete &&
|
if (!initialMessageHighlightComplete &&
|
||||||
widget.highlightInitialMessage &&
|
widget.highlightInitialMessage &&
|
||||||
_isInitialMessage(message.id)) {
|
_isInitialMessage(message.id)) {
|
||||||
|
final colorTheme = StreamChatTheme.of(context).colorTheme;
|
||||||
|
final highlightColor =
|
||||||
|
widget.messageHighlightColor ?? colorTheme.highlight;
|
||||||
child = TweenAnimationBuilder<Color>(
|
child = TweenAnimationBuilder<Color>(
|
||||||
tween: ColorTween(
|
tween: ColorTween(
|
||||||
begin: widget.highlightColor,
|
begin: highlightColor,
|
||||||
end: Colors.white10,
|
end: colorTheme.white.withOpacity(0),
|
||||||
),
|
),
|
||||||
duration: const Duration(seconds: 3),
|
duration: const Duration(seconds: 3),
|
||||||
child: child,
|
child: child,
|
||||||
onEnd: () {
|
onEnd: () => initialMessageHighlightComplete = true,
|
||||||
initialMessageHighlightComplete = true;
|
|
||||||
},
|
|
||||||
builder: (_, color, child) {
|
builder: (_, color, child) {
|
||||||
return Container(
|
return Container(
|
||||||
color: color,
|
color: color,
|
||||||
@@ -831,7 +870,7 @@ class _MessageListViewState extends State<MessageListView> {
|
|||||||
_itemPositionListener =
|
_itemPositionListener =
|
||||||
widget.itemPositionListener ?? ItemPositionsListener.create();
|
widget.itemPositionListener ?? ItemPositionsListener.create();
|
||||||
|
|
||||||
final streamChannel = StreamChannel.of(context);
|
streamChannel = StreamChannel.of(context);
|
||||||
|
|
||||||
initialIndex = _initialIndex;
|
initialIndex = _initialIndex;
|
||||||
initialAlignment = _initialAlignment;
|
initialAlignment = _initialAlignment;
|
||||||
@@ -844,7 +883,7 @@ class _MessageListViewState extends State<MessageListView> {
|
|||||||
}
|
}
|
||||||
if (event.message.user.id == streamChannel.channel.client.state.user.id) {
|
if (event.message.user.id == streamChannel.channel.client.state.user.id) {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
_scrollController.jumpTo(
|
_scrollController?.jumpTo(
|
||||||
index: 0,
|
index: 0,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -874,16 +913,13 @@ class _MessageListViewState extends State<MessageListView> {
|
|||||||
context,
|
context,
|
||||||
MaterialPageRoute(builder: (_) {
|
MaterialPageRoute(builder: (_) {
|
||||||
return StreamBuilder<Message>(
|
return StreamBuilder<Message>(
|
||||||
stream: StreamChannel.of(context)
|
stream: streamChannel.channel.state.messagesStream.map(
|
||||||
.channel
|
(messages) =>
|
||||||
.state
|
|
||||||
.messagesStream
|
|
||||||
.map((messages) =>
|
|
||||||
messages.firstWhere((m) => m.id == message.id)),
|
messages.firstWhere((m) => m.id == message.id)),
|
||||||
initialData: message,
|
initialData: message,
|
||||||
builder: (_, snapshot) {
|
builder: (_, snapshot) {
|
||||||
return StreamChannel(
|
return StreamChannel(
|
||||||
channel: StreamChannel.of(context).channel,
|
channel: streamChannel.channel,
|
||||||
child: widget.threadBuilder(context, snapshot.data),
|
child: widget.threadBuilder(context, snapshot.data),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -895,6 +931,9 @@ class _MessageListViewState extends State<MessageListView> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
if (!_upToDate) {
|
||||||
|
streamChannel.reloadChannel();
|
||||||
|
}
|
||||||
_messageNewListener?.cancel();
|
_messageNewListener?.cancel();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,27 +40,35 @@ class MessageReactionsModal extends StatelessWidget {
|
|||||||
final user = StreamChat.of(context).user;
|
final user = StreamChat.of(context).user;
|
||||||
|
|
||||||
final roughMaxSize = 2 * size.width / 3;
|
final roughMaxSize = 2 * size.width / 3;
|
||||||
|
var messageTextLength = message.text.length;
|
||||||
|
if (message.quotedMessage != null) {
|
||||||
|
var quotedMessageLength = message.quotedMessage.text.length + 40;
|
||||||
|
if (message.quotedMessage.attachments?.isNotEmpty == true) {
|
||||||
|
quotedMessageLength += 40;
|
||||||
|
}
|
||||||
|
if (quotedMessageLength > messageTextLength) {
|
||||||
|
messageTextLength = quotedMessageLength;
|
||||||
|
}
|
||||||
|
}
|
||||||
final roughSentenceSize =
|
final roughSentenceSize =
|
||||||
message.text.length * messageTheme.messageText.fontSize * 1.2;
|
messageTextLength * messageTheme.messageText.fontSize * 1.2;
|
||||||
final divFactor = message.attachments?.isNotEmpty == true
|
final divFactor = message.attachments?.isNotEmpty == true
|
||||||
? 1
|
? 1
|
||||||
: (roughSentenceSize == 0 ? 1 : (roughSentenceSize / roughMaxSize));
|
: (roughSentenceSize == 0 ? 1 : (roughSentenceSize / roughMaxSize));
|
||||||
|
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
behavior: HitTestBehavior.translucent,
|
behavior: HitTestBehavior.translucent,
|
||||||
onTap: () {
|
onTap: () => Navigator.maybePop(context),
|
||||||
Navigator.pop(context);
|
|
||||||
},
|
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
Positioned.fill(
|
Positioned.fill(
|
||||||
child: BackdropFilter(
|
child: BackdropFilter(
|
||||||
filter: ImageFilter.blur(
|
filter: ImageFilter.blur(
|
||||||
sigmaX: 10.8731,
|
sigmaX: 10,
|
||||||
sigmaY: 10.8731,
|
sigmaY: 10,
|
||||||
),
|
),
|
||||||
child: Container(
|
child: Container(
|
||||||
color: Colors.black.withOpacity(0.1),
|
color: StreamChatTheme.of(context).colorTheme.overlay,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -110,14 +118,16 @@ class MessageReactionsModal extends StatelessWidget {
|
|||||||
translateUserAvatar: false,
|
translateUserAvatar: false,
|
||||||
showSendingIndicator: DisplayWidget.gone,
|
showSendingIndicator: DisplayWidget.gone,
|
||||||
shape: messageShape,
|
shape: messageShape,
|
||||||
showReactionPickerIndicator: true,
|
showInChannelIndicator: false,
|
||||||
|
showReactionPickerIndicator: showReactions &&
|
||||||
|
(message.status ==
|
||||||
|
MessageSendingStatus.SENT ||
|
||||||
|
message.status == null),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
SizedBox(
|
SizedBox(height: 8),
|
||||||
height: 16,
|
|
||||||
),
|
|
||||||
if (message.latestReactions?.isNotEmpty == true)
|
if (message.latestReactions?.isNotEmpty == true)
|
||||||
_buildReactionCard(context),
|
_buildReactionCard(context),
|
||||||
],
|
],
|
||||||
@@ -137,6 +147,7 @@ class MessageReactionsModal extends StatelessWidget {
|
|||||||
horizontal: 8.0,
|
horizontal: 8.0,
|
||||||
),
|
),
|
||||||
child: Card(
|
child: Card(
|
||||||
|
color: StreamChatTheme.of(context).colorTheme.white,
|
||||||
clipBehavior: Clip.hardEdge,
|
clipBehavior: Clip.hardEdge,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ class MessageSearchBlocState extends State<MessageSearchBloc>
|
|||||||
/// Calls [Client.search] updating [queryMessagesLoading] stream
|
/// Calls [Client.search] updating [queryMessagesLoading] stream
|
||||||
Future<void> search({
|
Future<void> search({
|
||||||
Map<String, dynamic> filter,
|
Map<String, dynamic> filter,
|
||||||
|
Map<String, dynamic> messageFilter,
|
||||||
List<SortOption> sort,
|
List<SortOption> sort,
|
||||||
String query,
|
String query,
|
||||||
PaginationParams pagination,
|
PaginationParams pagination,
|
||||||
@@ -78,6 +79,7 @@ class MessageSearchBlocState extends State<MessageSearchBloc>
|
|||||||
sort,
|
sort,
|
||||||
query,
|
query,
|
||||||
pagination,
|
pagination,
|
||||||
|
messageFilters: messageFilter,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (clear) {
|
if (clear) {
|
||||||
|
|||||||
@@ -113,7 +113,9 @@ class MessageSearchItem extends StatelessWidget {
|
|||||||
} else if (e.type == 'giphy') {
|
} else if (e.type == 'giphy') {
|
||||||
return '[GIF]';
|
return '[GIF]';
|
||||||
}
|
}
|
||||||
return null;
|
return e == message.attachments.last
|
||||||
|
? (e.title ?? 'File')
|
||||||
|
: '${e.title ?? 'File'} , ';
|
||||||
}).where((e) => e != null),
|
}).where((e) => e != null),
|
||||||
message.text ?? '',
|
message.text ?? '',
|
||||||
];
|
];
|
||||||
@@ -121,17 +123,61 @@ class MessageSearchItem extends StatelessWidget {
|
|||||||
text = parts.join(' ');
|
text = parts.join(' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
return Text(
|
return Text.rich(
|
||||||
|
_getDisplayText(
|
||||||
text,
|
text,
|
||||||
maxLines: 1,
|
message.mentionedUsers,
|
||||||
overflow: TextOverflow.ellipsis,
|
message.attachments,
|
||||||
style: StreamChatTheme.of(context).channelPreviewTheme.subtitle.copyWith(
|
StreamChatTheme.of(context).channelPreviewTheme.subtitle.copyWith(
|
||||||
color:
|
|
||||||
StreamChatTheme.of(context).channelPreviewTheme.subtitle.color,
|
|
||||||
fontStyle: (message.isSystem || message.isDeleted)
|
fontStyle: (message.isSystem || message.isDeleted)
|
||||||
? FontStyle.italic
|
? FontStyle.italic
|
||||||
: FontStyle.normal,
|
: FontStyle.normal,
|
||||||
),
|
),
|
||||||
|
StreamChatTheme.of(context).channelPreviewTheme.subtitle.copyWith(
|
||||||
|
fontStyle: (message.isSystem || message.isDeleted)
|
||||||
|
? FontStyle.italic
|
||||||
|
: FontStyle.normal,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TextSpan _getDisplayText(
|
||||||
|
String text,
|
||||||
|
List<User> mentions,
|
||||||
|
List<Attachment> attachments,
|
||||||
|
TextStyle normalTextStyle,
|
||||||
|
TextStyle mentionsTextStyle) {
|
||||||
|
var textList = text.split(' ');
|
||||||
|
List<TextSpan> resList = [];
|
||||||
|
for (var e in textList) {
|
||||||
|
if (mentions != null &&
|
||||||
|
mentions.isNotEmpty &&
|
||||||
|
mentions.any((element) => '@${element.name}' == e)) {
|
||||||
|
resList.add(TextSpan(
|
||||||
|
text: '$e ',
|
||||||
|
style: mentionsTextStyle,
|
||||||
|
));
|
||||||
|
} else if (attachments != null &&
|
||||||
|
attachments.isNotEmpty &&
|
||||||
|
attachments
|
||||||
|
.where((e) => e.title != null)
|
||||||
|
.any((element) => element.title == e)) {
|
||||||
|
resList.add(TextSpan(
|
||||||
|
text: '$e ',
|
||||||
|
style: normalTextStyle.copyWith(fontStyle: FontStyle.italic),
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
resList.add(TextSpan(
|
||||||
|
text: e == textList.last ? '$e' : '$e ',
|
||||||
|
style: normalTextStyle,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return TextSpan(children: resList);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:stream_chat/stream_chat.dart';
|
import 'package:stream_chat/stream_chat.dart';
|
||||||
import 'package:stream_chat_flutter/src/message_search_item.dart';
|
import 'package:stream_chat_flutter/src/message_search_item.dart';
|
||||||
|
|
||||||
|
import '../stream_chat_flutter.dart';
|
||||||
import 'lazy_load_scroll_view.dart';
|
import 'lazy_load_scroll_view.dart';
|
||||||
import 'message_search_bloc.dart';
|
import 'message_search_bloc.dart';
|
||||||
|
|
||||||
@@ -127,8 +128,8 @@ class _MessageSearchListViewState extends State<MessageSearchListView> {
|
|||||||
return Container(
|
return Container(
|
||||||
height: 1,
|
height: 1,
|
||||||
color: Theme.of(context).brightness == Brightness.dark
|
color: Theme.of(context).brightness == Brightness.dark
|
||||||
? Colors.white.withOpacity(0.1)
|
? StreamChatTheme.of(context).colorTheme.white.withOpacity(0.1)
|
||||||
: Colors.black.withOpacity(0.1),
|
: StreamChatTheme.of(context).colorTheme.black.withOpacity(0.1),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,7 +152,10 @@ class _MessageSearchListViewState extends State<MessageSearchListView> {
|
|||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (snapshot.hasError) {
|
if (snapshot.hasError) {
|
||||||
return Container(
|
return Container(
|
||||||
color: Color(0xffd0021B).withAlpha(26),
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.accentRed
|
||||||
|
.withOpacity(.2),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 16.0),
|
padding: const EdgeInsets.symmetric(vertical: 16.0),
|
||||||
child: Center(
|
child: Center(
|
||||||
@@ -309,15 +313,7 @@ class _MessageSearchListViewState extends State<MessageSearchListView> {
|
|||||||
Container(
|
Container(
|
||||||
width: double.maxFinite,
|
width: double.maxFinite,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
gradient: LinearGradient(
|
gradient: StreamChatTheme.of(context).colorTheme.bgGradient,
|
||||||
begin: Alignment.centerLeft,
|
|
||||||
end: Alignment.centerRight,
|
|
||||||
colors: [
|
|
||||||
Colors.black.withOpacity(0.02),
|
|
||||||
Colors.white.withOpacity(0.05),
|
|
||||||
],
|
|
||||||
stops: [0, 1],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
@@ -327,7 +323,7 @@ class _MessageSearchListViewState extends State<MessageSearchListView> {
|
|||||||
child: Text(
|
child: Text(
|
||||||
'${items.length} results',
|
'${items.length} results',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.black.withOpacity(0.5),
|
color: StreamChatTheme.of(context).colorTheme.grey,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ class MessageText extends StatelessWidget {
|
|||||||
) {
|
) {
|
||||||
if (link.startsWith('@')) {
|
if (link.startsWith('@')) {
|
||||||
final mentionedUser = message.mentionedUsers.firstWhere(
|
final mentionedUser = message.mentionedUsers.firstWhere(
|
||||||
(u) => '@${u.name.replaceAll(' ', '')}' == link,
|
(u) => '@${u.name}' == link,
|
||||||
orElse: () => null,
|
orElse: () => null,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -10,14 +10,17 @@ import 'package:flutter_portal/flutter_portal.dart';
|
|||||||
import 'package:jiffy/jiffy.dart';
|
import 'package:jiffy/jiffy.dart';
|
||||||
import 'package:stream_chat_flutter/src/message_actions_modal.dart';
|
import 'package:stream_chat_flutter/src/message_actions_modal.dart';
|
||||||
import 'package:stream_chat_flutter/src/message_reactions_modal.dart';
|
import 'package:stream_chat_flutter/src/message_reactions_modal.dart';
|
||||||
|
import 'package:stream_chat_flutter/src/quoted_message_widget.dart';
|
||||||
import 'package:stream_chat_flutter/src/reaction_bubble.dart';
|
import 'package:stream_chat_flutter/src/reaction_bubble.dart';
|
||||||
import 'package:stream_chat_flutter/src/url_attachment.dart';
|
import 'package:stream_chat_flutter/src/url_attachment.dart';
|
||||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||||
|
|
||||||
|
import 'extension.dart';
|
||||||
import 'image_group.dart';
|
import 'image_group.dart';
|
||||||
import 'message_text.dart';
|
import 'message_text.dart';
|
||||||
|
|
||||||
typedef AttachmentBuilder = Widget Function(BuildContext, Message, Attachment);
|
typedef AttachmentBuilder = Widget Function(BuildContext, Message, Attachment);
|
||||||
|
typedef OnQuotedMessageTap = void Function(String);
|
||||||
|
|
||||||
/// The display behaviour of a widget
|
/// The display behaviour of a widget
|
||||||
enum DisplayWidget {
|
enum DisplayWidget {
|
||||||
@@ -46,6 +49,7 @@ class MessageWidget extends StatefulWidget {
|
|||||||
|
|
||||||
/// The function called when tapping on replies
|
/// The function called when tapping on replies
|
||||||
final void Function(Message) onThreadTap;
|
final void Function(Message) onThreadTap;
|
||||||
|
final void Function(Message) onReplyTap;
|
||||||
final Widget Function(BuildContext, Message) editMessageInputBuilder;
|
final Widget Function(BuildContext, Message) editMessageInputBuilder;
|
||||||
final Widget Function(BuildContext, Message) textBuilder;
|
final Widget Function(BuildContext, Message) textBuilder;
|
||||||
|
|
||||||
@@ -102,6 +106,9 @@ class MessageWidget extends StatefulWidget {
|
|||||||
/// If true the widget will show the thread reply indicator
|
/// If true the widget will show the thread reply indicator
|
||||||
final bool showThreadReplyIndicator;
|
final bool showThreadReplyIndicator;
|
||||||
|
|
||||||
|
/// If true the widget will show the reply indicator
|
||||||
|
final bool showReplyIndicator;
|
||||||
|
|
||||||
/// If true the widget will show the show in channel indicator
|
/// If true the widget will show the show in channel indicator
|
||||||
final bool showInChannelIndicator;
|
final bool showInChannelIndicator;
|
||||||
|
|
||||||
@@ -114,8 +121,13 @@ class MessageWidget extends StatefulWidget {
|
|||||||
/// Used in [MessageReactionsModal] and [MessageActionsModal]
|
/// Used in [MessageReactionsModal] and [MessageActionsModal]
|
||||||
final bool showReactionPickerIndicator;
|
final bool showReactionPickerIndicator;
|
||||||
|
|
||||||
|
/// If true the widget will show the resendMessage indicator
|
||||||
|
final bool showResendMessage;
|
||||||
|
|
||||||
final List<Read> readList;
|
final List<Read> readList;
|
||||||
|
|
||||||
|
final ShowMessageCallback onShowMessage;
|
||||||
|
|
||||||
/// If true show the users username next to the timestamp of the message
|
/// If true show the users username next to the timestamp of the message
|
||||||
final bool showUsername;
|
final bool showUsername;
|
||||||
final bool showTimestamp;
|
final bool showTimestamp;
|
||||||
@@ -126,6 +138,9 @@ class MessageWidget extends StatefulWidget {
|
|||||||
/// Center user avatar with bottom of the message
|
/// Center user avatar with bottom of the message
|
||||||
final bool translateUserAvatar;
|
final bool translateUserAvatar;
|
||||||
|
|
||||||
|
/// Function called when quotedMessage is tapped
|
||||||
|
final OnQuotedMessageTap onQuotedMessageTap;
|
||||||
|
|
||||||
///
|
///
|
||||||
MessageWidget({
|
MessageWidget({
|
||||||
Key key,
|
Key key,
|
||||||
@@ -145,6 +160,8 @@ class MessageWidget extends StatefulWidget {
|
|||||||
this.showSendingIndicator = DisplayWidget.show,
|
this.showSendingIndicator = DisplayWidget.show,
|
||||||
this.showThreadReplyIndicator = true,
|
this.showThreadReplyIndicator = true,
|
||||||
this.showInChannelIndicator = true,
|
this.showInChannelIndicator = true,
|
||||||
|
this.showReplyIndicator = true,
|
||||||
|
this.onReplyTap,
|
||||||
this.onThreadTap,
|
this.onThreadTap,
|
||||||
this.showUsername = true,
|
this.showUsername = true,
|
||||||
this.showTimestamp = true,
|
this.showTimestamp = true,
|
||||||
@@ -154,9 +171,11 @@ class MessageWidget extends StatefulWidget {
|
|||||||
this.onUserAvatarTap,
|
this.onUserAvatarTap,
|
||||||
this.onLinkTap,
|
this.onLinkTap,
|
||||||
this.onMessageActions,
|
this.onMessageActions,
|
||||||
|
this.onShowMessage,
|
||||||
this.editMessageInputBuilder,
|
this.editMessageInputBuilder,
|
||||||
this.textBuilder,
|
this.textBuilder,
|
||||||
Map<String, AttachmentBuilder> customAttachmentBuilders,
|
Map<String, AttachmentBuilder> customAttachmentBuilders,
|
||||||
|
this.showResendMessage = true,
|
||||||
this.readList,
|
this.readList,
|
||||||
this.padding,
|
this.padding,
|
||||||
this.textPadding = const EdgeInsets.symmetric(
|
this.textPadding = const EdgeInsets.symmetric(
|
||||||
@@ -165,6 +184,7 @@ class MessageWidget extends StatefulWidget {
|
|||||||
),
|
),
|
||||||
this.attachmentPadding = EdgeInsets.zero,
|
this.attachmentPadding = EdgeInsets.zero,
|
||||||
this.allRead = false,
|
this.allRead = false,
|
||||||
|
this.onQuotedMessageTap,
|
||||||
}) : attachmentBuilders = {
|
}) : attachmentBuilders = {
|
||||||
'image': (context, message, attachment) {
|
'image': (context, message, attachment) {
|
||||||
return ImageAttachment(
|
return ImageAttachment(
|
||||||
@@ -175,6 +195,7 @@ class MessageWidget extends StatefulWidget {
|
|||||||
MediaQuery.of(context).size.width * 0.8,
|
MediaQuery.of(context).size.width * 0.8,
|
||||||
MediaQuery.of(context).size.height * 0.3,
|
MediaQuery.of(context).size.height * 0.3,
|
||||||
),
|
),
|
||||||
|
onShowMessage: onShowMessage,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
'video': (context, message, attachment) {
|
'video': (context, message, attachment) {
|
||||||
@@ -186,6 +207,7 @@ class MessageWidget extends StatefulWidget {
|
|||||||
MediaQuery.of(context).size.height * 0.3,
|
MediaQuery.of(context).size.height * 0.3,
|
||||||
),
|
),
|
||||||
message: message,
|
message: message,
|
||||||
|
onShowMessage: onShowMessage,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
'giphy': (context, message, attachment) {
|
'giphy': (context, message, attachment) {
|
||||||
@@ -197,6 +219,7 @@ class MessageWidget extends StatefulWidget {
|
|||||||
MediaQuery.of(context).size.width * 0.8,
|
MediaQuery.of(context).size.width * 0.8,
|
||||||
MediaQuery.of(context).size.height * 0.3,
|
MediaQuery.of(context).size.height * 0.3,
|
||||||
),
|
),
|
||||||
|
onShowMessage: onShowMessage,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
'file': (context, message, attachment) {
|
'file': (context, message, attachment) {
|
||||||
@@ -224,22 +247,53 @@ class _MessageWidgetState extends State<MessageWidget> {
|
|||||||
bool get showTimeStamp =>
|
bool get showTimeStamp =>
|
||||||
widget.message.createdAt != null && widget.showTimestamp;
|
widget.message.createdAt != null && widget.showTimestamp;
|
||||||
|
|
||||||
bool get showReadList => widget.readList?.isNotEmpty == true;
|
bool get isMessageRead => widget.readList?.isNotEmpty == true;
|
||||||
|
|
||||||
bool get showInChannel =>
|
bool get showInChannel =>
|
||||||
widget.showInChannelIndicator && widget.message?.showInChannel == true;
|
widget.showInChannelIndicator && widget.message?.showInChannel == true;
|
||||||
|
|
||||||
|
bool get hasQuotedMessage => widget.message?.quotedMessage != null;
|
||||||
|
|
||||||
|
bool get isSendFailed => widget.message.status == MessageSendingStatus.FAILED;
|
||||||
|
|
||||||
|
bool get isUpdateFailed =>
|
||||||
|
widget.message.status == MessageSendingStatus.FAILED_UPDATE;
|
||||||
|
|
||||||
|
bool get isDeleteFailed =>
|
||||||
|
widget.message.status == MessageSendingStatus.FAILED_DELETE;
|
||||||
|
|
||||||
|
bool get isFailedState => isSendFailed || isUpdateFailed || isDeleteFailed;
|
||||||
|
|
||||||
|
bool get isGiphy =>
|
||||||
|
widget.message.attachments?.any((element) => element.type == 'giphy') ==
|
||||||
|
true;
|
||||||
|
|
||||||
|
bool get showBottomRow =>
|
||||||
|
showThreadReplyIndicator ||
|
||||||
|
showUsername ||
|
||||||
|
showTimeStamp ||
|
||||||
|
showInChannel;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var leftPadding = widget.showUserAvatar != DisplayWidget.gone
|
final avatarWidth = widget.messageTheme.avatarTheme.constraints.maxWidth;
|
||||||
? widget.messageTheme.avatarTheme.constraints.maxWidth + 14.5
|
var leftPadding =
|
||||||
: 6.0;
|
widget.showUserAvatar != DisplayWidget.gone ? avatarWidth + 8.5 : 4.5;
|
||||||
|
|
||||||
|
final isOnlyEmoji =
|
||||||
|
widget.message.text.characters.every((c) => Emoji.byChar(c) != null);
|
||||||
|
|
||||||
final hasFiles =
|
final hasFiles =
|
||||||
widget.message.attachments?.any((element) => element.type == 'file') ==
|
widget.message.attachments?.any((element) => element.type == 'file') ==
|
||||||
true;
|
true;
|
||||||
|
|
||||||
return Portal(
|
return Material(
|
||||||
|
type: MaterialType.transparency,
|
||||||
|
child: Portal(
|
||||||
|
child: InkWell(
|
||||||
|
onLongPress: widget.message.isDeleted && !isFailedState
|
||||||
|
? null
|
||||||
|
: () => onLongPress(context),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: widget.padding ?? EdgeInsets.all(8),
|
padding: widget.padding ?? EdgeInsets.all(8),
|
||||||
child: Transform(
|
child: Transform(
|
||||||
@@ -253,6 +307,7 @@ class _MessageWidgetState extends State<MessageWidget> {
|
|||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Stack(
|
Stack(
|
||||||
|
clipBehavior: Clip.none,
|
||||||
alignment: AlignmentDirectional.bottomStart,
|
alignment: AlignmentDirectional.bottomStart,
|
||||||
children: [
|
children: [
|
||||||
Column(
|
Column(
|
||||||
@@ -264,15 +319,13 @@ class _MessageWidgetState extends State<MessageWidget> {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
if (widget.showUserAvatar == DisplayWidget.show)
|
if (widget.showUserAvatar ==
|
||||||
|
DisplayWidget.show) ...[
|
||||||
_buildUserAvatar(),
|
_buildUserAvatar(),
|
||||||
SizedBox(width: 6),
|
SizedBox(width: 4),
|
||||||
|
],
|
||||||
if (widget.showUserAvatar == DisplayWidget.hide)
|
if (widget.showUserAvatar == DisplayWidget.hide)
|
||||||
SizedBox(
|
SizedBox(width: avatarWidth + 4),
|
||||||
width: widget.messageTheme.avatarTheme
|
|
||||||
.constraints.maxWidth +
|
|
||||||
8,
|
|
||||||
),
|
|
||||||
Flexible(
|
Flexible(
|
||||||
child: PortalEntry(
|
child: PortalEntry(
|
||||||
portal: Container(
|
portal: Container(
|
||||||
@@ -290,7 +343,9 @@ class _MessageWidgetState extends State<MessageWidget> {
|
|||||||
Padding(
|
Padding(
|
||||||
padding: widget.showReactions
|
padding: widget.showReactions
|
||||||
? EdgeInsets.only(
|
? EdgeInsets.only(
|
||||||
top: widget.message.reactionCounts
|
top: widget
|
||||||
|
.message
|
||||||
|
.reactionCounts
|
||||||
?.isNotEmpty ==
|
?.isNotEmpty ==
|
||||||
true
|
true
|
||||||
? 18
|
? 18
|
||||||
@@ -298,41 +353,36 @@ class _MessageWidgetState extends State<MessageWidget> {
|
|||||||
)
|
)
|
||||||
: EdgeInsets.zero,
|
: EdgeInsets.zero,
|
||||||
child: (widget.message.isDeleted &&
|
child: (widget.message.isDeleted &&
|
||||||
widget.message.status !=
|
!isFailedState)
|
||||||
MessageSendingStatus
|
|
||||||
.FAILED_DELETE)
|
|
||||||
? Transform(
|
? Transform(
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
transform: Matrix4.rotationY(
|
transform: Matrix4.rotationY(
|
||||||
widget.reverse ? pi : 0),
|
widget.reverse ? pi : 0),
|
||||||
child: DeletedMessage(
|
child: DeletedMessage(
|
||||||
reverse: widget.reverse,
|
reverse: widget.reverse,
|
||||||
borderRadiusGeometry:
|
borderRadiusGeometry: widget
|
||||||
widget.borderRadiusGeometry,
|
.borderRadiusGeometry,
|
||||||
borderSide: widget.borderSide,
|
borderSide:
|
||||||
|
widget.borderSide,
|
||||||
shape: widget.shape,
|
shape: widget.shape,
|
||||||
messageTheme:
|
messageTheme:
|
||||||
widget.messageTheme,
|
widget.messageTheme,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: Material(
|
: Card(
|
||||||
clipBehavior: Clip.antiAlias,
|
clipBehavior: Clip.antiAlias,
|
||||||
|
elevation: 0.0,
|
||||||
shape: widget.shape ??
|
shape: widget.shape ??
|
||||||
RoundedRectangleBorder(
|
RoundedRectangleBorder(
|
||||||
side: isOnlyEmoji
|
side: isOnlyEmoji &&
|
||||||
|
!(showThreadReplyIndicator ||
|
||||||
|
showInChannel)
|
||||||
? BorderSide.none
|
? BorderSide.none
|
||||||
: widget.borderSide ??
|
: widget.borderSide ??
|
||||||
BorderSide(
|
BorderSide(
|
||||||
color: Theme.of(context)
|
color: widget
|
||||||
.brightness ==
|
.messageTheme
|
||||||
Brightness
|
.messageBorderColor,
|
||||||
.dark
|
|
||||||
? Colors.white
|
|
||||||
.withAlpha(
|
|
||||||
24)
|
|
||||||
: Colors.black
|
|
||||||
.withAlpha(
|
|
||||||
24),
|
|
||||||
),
|
),
|
||||||
borderRadius: widget
|
borderRadius: widget
|
||||||
.borderRadiusGeometry ??
|
.borderRadiusGeometry ??
|
||||||
@@ -344,17 +394,21 @@ class _MessageWidgetState extends State<MessageWidget> {
|
|||||||
hasFiles ? 2.0 : 0.0),
|
hasFiles ? 2.0 : 0.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment:
|
crossAxisAlignment:
|
||||||
CrossAxisAlignment.start,
|
CrossAxisAlignment
|
||||||
|
.start,
|
||||||
mainAxisSize:
|
mainAxisSize:
|
||||||
MainAxisSize.min,
|
MainAxisSize.min,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
|
if (hasQuotedMessage)
|
||||||
|
_buildQuotedMessage(),
|
||||||
..._parseAttachments(
|
..._parseAttachments(
|
||||||
context),
|
context),
|
||||||
if (widget.message.text
|
if (widget.message.text
|
||||||
.trim()
|
.trim()
|
||||||
.isNotEmpty &&
|
.isNotEmpty &&
|
||||||
!isGiphy)
|
!isGiphy)
|
||||||
_buildTextBubble(context),
|
_buildTextBubble(
|
||||||
|
context),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -383,18 +437,23 @@ class _MessageWidgetState extends State<MessageWidget> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (showThreadReplyIndicator ||
|
if (showBottomRow) SizedBox(height: 20.0),
|
||||||
showUsername ||
|
|
||||||
showTimeStamp ||
|
|
||||||
showInChannel)
|
|
||||||
SizedBox(height: 20.0),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (showThreadReplyIndicator ||
|
if (showBottomRow) _buildBottomRow(leftPadding),
|
||||||
showUsername ||
|
if (isFailedState)
|
||||||
showTimeStamp ||
|
Positioned(
|
||||||
showInChannel)
|
left: widget.reverse ? -3 : null,
|
||||||
_buildBottomRow(leftPadding)
|
right: widget.reverse ? null : -9,
|
||||||
|
bottom: showBottomRow ? 20 : 0,
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
child: StreamSvgIcon.error(size: 20),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -402,6 +461,25 @@ class _MessageWidgetState extends State<MessageWidget> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildQuotedMessage() {
|
||||||
|
final isMyMessage =
|
||||||
|
widget.message.user.id == StreamChat.of(context).user.id;
|
||||||
|
return QuotedMessageWidget(
|
||||||
|
onTap: () {
|
||||||
|
if (widget.onQuotedMessageTap != null) {
|
||||||
|
widget.onQuotedMessageTap(widget.message.quotedMessageId);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
message: widget.message.quotedMessage,
|
||||||
|
messageTheme: isMyMessage
|
||||||
|
? StreamChatTheme.of(context).otherMessageTheme
|
||||||
|
: StreamChatTheme.of(context).ownMessageTheme,
|
||||||
|
reverse: widget.reverse,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -414,16 +492,18 @@ class _MessageWidgetState extends State<MessageWidget> {
|
|||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
StreamSvgIcon.eye(
|
StreamSvgIcon.eye(
|
||||||
color: Colors.black.withOpacity(0.5),
|
color:
|
||||||
|
StreamChatTheme.of(context).colorTheme.black.withOpacity(0.5),
|
||||||
size: 16.0,
|
size: 16.0,
|
||||||
),
|
),
|
||||||
SizedBox(width: 8.0),
|
SizedBox(width: 8.0),
|
||||||
Text(
|
Text(
|
||||||
'Only visible to you',
|
'Only visible to you',
|
||||||
style: TextStyle(
|
style: StreamChatTheme.of(context).textTheme.footnote.copyWith(
|
||||||
color: Colors.black.withOpacity(0.5),
|
color: StreamChatTheme.of(context)
|
||||||
fontSize: 12.0,
|
.colorTheme
|
||||||
),
|
.black
|
||||||
|
.withOpacity(0.5)),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -449,6 +529,8 @@ class _MessageWidgetState extends State<MessageWidget> {
|
|||||||
} else {
|
} else {
|
||||||
final showSendingIndicator =
|
final showSendingIndicator =
|
||||||
widget.showSendingIndicator == DisplayWidget.show;
|
widget.showSendingIndicator == DisplayWidget.show;
|
||||||
|
final threadParticipants = widget.message.threadParticipants;
|
||||||
|
final showThreadParticipants = threadParticipants?.isNotEmpty == true;
|
||||||
final replyCount = widget.message.replyCount;
|
final replyCount = widget.message.replyCount;
|
||||||
final msg = replyCount != 0
|
final msg = replyCount != 0
|
||||||
? '$replyCount ${replyCount > 1 ? 'Thread Replies' : 'Thread Reply'}'
|
? '$replyCount ${replyCount > 1 ? 'Thread Replies' : 'Thread Reply'}'
|
||||||
@@ -461,19 +543,17 @@ class _MessageWidgetState extends State<MessageWidget> {
|
|||||||
|
|
||||||
children.addAll([
|
children.addAll([
|
||||||
if (showSendingIndicator) _buildSendingIndicator(),
|
if (showSendingIndicator) _buildSendingIndicator(),
|
||||||
if (showReadList)
|
if (showThreadReplyIndicator) ...[
|
||||||
|
if (showThreadParticipants)
|
||||||
SizedBox.fromSize(
|
SizedBox.fromSize(
|
||||||
size: Size((widget.readList.length * 10.0) + 10, 17),
|
size: Size((threadParticipants.length * 8.0) + 10, 16),
|
||||||
child: Padding(
|
child: _buildThreadParticipantsIndicator(),
|
||||||
padding: const EdgeInsets.only(left: 4.0),
|
|
||||||
child: _buildReadIndicator(),
|
|
||||||
),
|
),
|
||||||
),
|
|
||||||
if (showThreadReplyIndicator)
|
|
||||||
InkWell(
|
InkWell(
|
||||||
onTap: widget.onThreadTap != null ? onThreadTap : null,
|
onTap: widget.onThreadTap != null ? onThreadTap : null,
|
||||||
child: Text(msg, style: widget.messageTheme?.replies),
|
child: Text(msg, style: widget.messageTheme?.replies),
|
||||||
),
|
),
|
||||||
|
],
|
||||||
if (showUsername)
|
if (showUsername)
|
||||||
Text(
|
Text(
|
||||||
widget.message.user.name,
|
widget.message.user.name,
|
||||||
@@ -502,7 +582,8 @@ class _MessageWidgetState extends State<MessageWidget> {
|
|||||||
child: CustomPaint(
|
child: CustomPaint(
|
||||||
size: const Size(16, 32),
|
size: const Size(16, 32),
|
||||||
painter: _ThreadReplyPainter(
|
painter: _ThreadReplyPainter(
|
||||||
color: widget.messageTheme.replyThreadColor,
|
context: context,
|
||||||
|
color: widget.messageTheme.messageBorderColor,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -510,7 +591,7 @@ class _MessageWidgetState extends State<MessageWidget> {
|
|||||||
(child) => Transform(
|
(child) => Transform(
|
||||||
transform: Matrix4.rotationY(widget.reverse ? pi : 0),
|
transform: Matrix4.rotationY(widget.reverse ? pi : 0),
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
child: child,
|
child: Container(height: 16, child: Center(child: child)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
].insertBetween(const SizedBox(width: 8.0)),
|
].insertBetween(const SizedBox(width: 8.0)),
|
||||||
@@ -526,7 +607,7 @@ class _MessageWidgetState extends State<MessageWidget> {
|
|||||||
var splitList = host.split('.');
|
var splitList = host.split('.');
|
||||||
var hostName = splitList.length == 3 ? splitList[1] : splitList[0];
|
var hostName = splitList.length == 3 ? splitList[1] : splitList[0];
|
||||||
var hostDisplayName = urlAttachment.authorName?.capitalize() ??
|
var hostDisplayName = urlAttachment.authorName?.capitalize() ??
|
||||||
_getWebsiteName(hostName.toLowerCase()) ??
|
getWebsiteName(hostName.toLowerCase()) ??
|
||||||
hostName.capitalize();
|
hostName.capitalize();
|
||||||
|
|
||||||
return UrlAttachment(
|
return UrlAttachment(
|
||||||
@@ -536,14 +617,10 @@ class _MessageWidgetState extends State<MessageWidget> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool get isGiphy =>
|
Widget _buildThreadParticipantsIndicator() {
|
||||||
widget.message.attachments?.any((element) => element.type == 'giphy') ==
|
|
||||||
true;
|
|
||||||
|
|
||||||
Widget _buildReadIndicator() {
|
|
||||||
var padding = 0.0;
|
var padding = 0.0;
|
||||||
return Stack(
|
return Stack(
|
||||||
children: widget.readList.map((e) {
|
children: widget.message.threadParticipants.map((user) {
|
||||||
padding += 10.0;
|
padding += 10.0;
|
||||||
return Positioned(
|
return Positioned(
|
||||||
left: padding - 10,
|
left: padding - 10,
|
||||||
@@ -553,15 +630,12 @@ class _MessageWidgetState extends State<MessageWidget> {
|
|||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
clipBehavior: Clip.antiAlias,
|
clipBehavior: Clip.antiAlias,
|
||||||
shape: CircleBorder(),
|
shape: CircleBorder(),
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(1.0),
|
|
||||||
child: UserAvatar(
|
child: UserAvatar(
|
||||||
user: e.user,
|
user: user,
|
||||||
constraints: BoxConstraints.loose(Size.fromRadius(8)),
|
constraints: BoxConstraints.loose(Size.fromRadius(8)),
|
||||||
showOnlineStatus: false,
|
showOnlineStatus: false,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}).toList(),
|
}).toList(),
|
||||||
);
|
);
|
||||||
@@ -604,6 +678,7 @@ class _MessageWidgetState extends State<MessageWidget> {
|
|||||||
final channel = StreamChannel.of(context).channel;
|
final channel = StreamChannel.of(context).channel;
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
|
barrierColor: StreamChatTheme.of(context).colorTheme.overlay,
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return StreamChannel(
|
return StreamChannel(
|
||||||
channel: channel,
|
channel: channel,
|
||||||
@@ -618,14 +693,22 @@ class _MessageWidgetState extends State<MessageWidget> {
|
|||||||
showDeleteMessage: widget.showDeleteMessage,
|
showDeleteMessage: widget.showDeleteMessage,
|
||||||
message: widget.message,
|
message: widget.message,
|
||||||
editMessageInputBuilder: widget.editMessageInputBuilder,
|
editMessageInputBuilder: widget.editMessageInputBuilder,
|
||||||
onThreadTap: widget.onThreadTap,
|
onReplyTap: widget.onReplyTap,
|
||||||
showCopyMessage: widget.message.text?.trim()?.isNotEmpty == true,
|
onThreadReplyTap: widget.onThreadTap,
|
||||||
|
showResendMessage:
|
||||||
|
widget.showResendMessage && (isSendFailed || isUpdateFailed),
|
||||||
|
showCopyMessage: !isFailedState &&
|
||||||
|
widget.message.text?.trim()?.isNotEmpty == true,
|
||||||
showEditMessage: widget.showEditMessage &&
|
showEditMessage: widget.showEditMessage &&
|
||||||
|
!isDeleteFailed &&
|
||||||
widget.message.attachments
|
widget.message.attachments
|
||||||
?.any((element) => element.type == 'giphy') !=
|
?.any((element) => element.type == 'giphy') !=
|
||||||
true,
|
true,
|
||||||
showReactions: widget.showReactions,
|
showReactions: widget.showReactions,
|
||||||
showReply:
|
showReply: widget.showReplyIndicator &&
|
||||||
|
!isFailedState &&
|
||||||
|
widget.onReplyTap != null,
|
||||||
|
showThreadReply:
|
||||||
widget.showThreadReplyIndicator && widget.onThreadTap != null,
|
widget.showThreadReplyIndicator && widget.onThreadTap != null,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -636,6 +719,7 @@ class _MessageWidgetState extends State<MessageWidget> {
|
|||||||
final channel = StreamChannel.of(context).channel;
|
final channel = StreamChannel.of(context).channel;
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
|
barrierColor: StreamChatTheme.of(context).colorTheme.overlay,
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return StreamChannel(
|
return StreamChannel(
|
||||||
channel: channel,
|
channel: channel,
|
||||||
@@ -663,8 +747,8 @@ class _MessageWidgetState extends State<MessageWidget> {
|
|||||||
widget.borderSide ??
|
widget.borderSide ??
|
||||||
BorderSide(
|
BorderSide(
|
||||||
color: Theme.of(context).brightness == Brightness.dark
|
color: Theme.of(context).brightness == Brightness.dark
|
||||||
? Colors.white.withAlpha(24)
|
? StreamChatTheme.of(context).colorTheme.white.withAlpha(24)
|
||||||
: Colors.black.withAlpha(24),
|
: StreamChatTheme.of(context).colorTheme.black.withAlpha(24),
|
||||||
),
|
),
|
||||||
borderRadius: widget.attachmentBorderRadiusGeometry ??
|
borderRadius: widget.attachmentBorderRadiusGeometry ??
|
||||||
widget.borderRadiusGeometry ??
|
widget.borderRadiusGeometry ??
|
||||||
@@ -692,6 +776,7 @@ class _MessageWidgetState extends State<MessageWidget> {
|
|||||||
),
|
),
|
||||||
images: images,
|
images: images,
|
||||||
message: widget.message,
|
message: widget.message,
|
||||||
|
onShowMessage: widget.onShowMessage,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -728,10 +813,7 @@ class _MessageWidgetState extends State<MessageWidget> {
|
|||||||
}) {
|
}) {
|
||||||
final attachmentShape =
|
final attachmentShape =
|
||||||
widget.attachmentShape ?? widget.shape ?? _getDefaultShape(context);
|
widget.attachmentShape ?? widget.shape ?? _getDefaultShape(context);
|
||||||
return GestureDetector(
|
return Material(
|
||||||
onTap: () => retryMessage(context),
|
|
||||||
onLongPress: () => onLongPress(context),
|
|
||||||
child: Material(
|
|
||||||
color: _getBackgroundColor(),
|
color: _getBackgroundColor(),
|
||||||
clipBehavior: Clip.antiAlias,
|
clipBehavior: Clip.antiAlias,
|
||||||
shape: attachmentShape,
|
shape: attachmentShape,
|
||||||
@@ -744,18 +826,7 @@ class _MessageWidgetState extends State<MessageWidget> {
|
|||||||
child: Transform(
|
child: Transform(
|
||||||
transform: Matrix4.rotationY(widget.reverse ? pi : 0),
|
transform: Matrix4.rotationY(widget.reverse ? pi : 0),
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
child: Column(
|
child: attachmentWidget,
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
|
||||||
children: <Widget>[
|
|
||||||
getFailedMessageWidget(
|
|
||||||
context,
|
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
),
|
|
||||||
attachmentWidget,
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -777,21 +848,33 @@ class _MessageWidgetState extends State<MessageWidget> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildSendingIndicator() {
|
Widget _buildSendingIndicator() {
|
||||||
return Container(
|
final style = widget.messageTheme.createdAt;
|
||||||
height: widget.messageTheme.createdAt.fontSize + 2,
|
Widget child = SendingIndicator(
|
||||||
width: widget.messageTheme.createdAt.fontSize + 2,
|
|
||||||
child: SendingIndicator(
|
|
||||||
message: widget.message,
|
message: widget.message,
|
||||||
allRead: widget.allRead,
|
isMessageRead: isMessageRead,
|
||||||
),
|
size: style.fontSize,
|
||||||
);
|
);
|
||||||
|
if (isMessageRead) {
|
||||||
|
child = Row(
|
||||||
|
children: [
|
||||||
|
if (StreamChannel.of(context).channel.memberCount > 2)
|
||||||
|
Text(
|
||||||
|
widget.readList.length.toString(),
|
||||||
|
style: style.copyWith(
|
||||||
|
color: StreamChatTheme.of(context).colorTheme.accentBlue,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 2),
|
||||||
|
child,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return child;
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildUserAvatar() => Transform(
|
Widget _buildUserAvatar() => Transform(
|
||||||
transform: Matrix4.rotationY(widget.reverse ? pi : 0),
|
transform: Matrix4.rotationY(widget.reverse ? pi : 0),
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 4.0),
|
|
||||||
child: Transform.translate(
|
child: Transform.translate(
|
||||||
offset: Offset(
|
offset: Offset(
|
||||||
0,
|
0,
|
||||||
@@ -806,60 +889,10 @@ class _MessageWidgetState extends State<MessageWidget> {
|
|||||||
showOnlineStatus: false,
|
showOnlineStatus: false,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget getFailedMessageWidget(
|
|
||||||
BuildContext context, {
|
|
||||||
EdgeInsetsGeometry padding,
|
|
||||||
}) {
|
|
||||||
Widget failedWidget;
|
|
||||||
if (widget.message.status == MessageSendingStatus.FAILED) {
|
|
||||||
failedWidget = Text(
|
|
||||||
'MESSAGE FAILED · CLICK TO TRY AGAIN',
|
|
||||||
style: widget.messageTheme.messageText.copyWith(
|
|
||||||
color: Theme.of(context).brightness == Brightness.dark
|
|
||||||
? Colors.white.withOpacity(.5)
|
|
||||||
: Colors.black.withOpacity(.5),
|
|
||||||
fontSize: 11,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (widget.message.status == MessageSendingStatus.FAILED_UPDATE) {
|
|
||||||
failedWidget = Text(
|
|
||||||
'MESSAGE UPDATE FAILED · CLICK TO TRY AGAIN',
|
|
||||||
style: widget.messageTheme.messageText.copyWith(
|
|
||||||
color: Theme.of(context).brightness == Brightness.dark
|
|
||||||
? Colors.white.withOpacity(.5)
|
|
||||||
: Colors.black.withOpacity(.5),
|
|
||||||
fontSize: 11,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (widget.message.status == MessageSendingStatus.FAILED_DELETE) {
|
|
||||||
failedWidget = Text(
|
|
||||||
'MESSAGE DELETE FAILED · CLICK TO TRY AGAIN',
|
|
||||||
style: widget.messageTheme.messageText.copyWith(
|
|
||||||
color: Theme.of(context).brightness == Brightness.dark
|
|
||||||
? Colors.white.withOpacity(.5)
|
|
||||||
: Colors.black.withOpacity(.5),
|
|
||||||
fontSize: 11,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (failedWidget != null) {
|
|
||||||
return Padding(
|
|
||||||
padding: padding ?? EdgeInsets.zero,
|
|
||||||
child: failedWidget,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return SizedBox();
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildTextBubble(BuildContext context) {
|
Widget _buildTextBubble(BuildContext context) {
|
||||||
Widget child = Transform(
|
return Transform(
|
||||||
transform: Matrix4.rotationY(widget.reverse ? pi : 0),
|
transform: Matrix4.rotationY(widget.reverse ? pi : 0),
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
child: Column(
|
child: Column(
|
||||||
@@ -867,11 +900,7 @@ class _MessageWidgetState extends State<MessageWidget> {
|
|||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: widget.textPadding,
|
padding: widget.textPadding,
|
||||||
child: Column(
|
child: widget.textBuilder != null
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
|
||||||
children: [
|
|
||||||
getFailedMessageWidget(context),
|
|
||||||
widget.textBuilder != null
|
|
||||||
? widget.textBuilder(context, widget.message)
|
? widget.textBuilder(context, widget.message)
|
||||||
: MessageText(
|
: MessageText(
|
||||||
onLinkTap: widget.onLinkTap,
|
onLinkTap: widget.onLinkTap,
|
||||||
@@ -885,21 +914,15 @@ class _MessageWidgetState extends State<MessageWidget> {
|
|||||||
))
|
))
|
||||||
: widget.messageTheme,
|
: widget.messageTheme,
|
||||||
),
|
),
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
if (widget.message.attachments
|
if (widget.message.attachments
|
||||||
?.any((element) => element.ogScrapeUrl != null) ==
|
?.any((element) => element.ogScrapeUrl != null) ==
|
||||||
true)
|
true &&
|
||||||
|
!hasQuotedMessage)
|
||||||
_buildUrlAttachment(),
|
_buildUrlAttachment(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return GestureDetector(
|
|
||||||
onTap: () => retryMessage(context),
|
|
||||||
onLongPress: () => onLongPress(context),
|
|
||||||
child: child,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool get isOnlyEmoji =>
|
bool get isOnlyEmoji =>
|
||||||
@@ -907,16 +930,14 @@ class _MessageWidgetState extends State<MessageWidget> {
|
|||||||
widget.message.text.characters.every((c) => Emoji.byChar(c) != null);
|
widget.message.text.characters.every((c) => Emoji.byChar(c) != null);
|
||||||
|
|
||||||
Color _getBackgroundColor() {
|
Color _getBackgroundColor() {
|
||||||
if ((widget.message.status == MessageSendingStatus.FAILED ||
|
if (hasQuotedMessage) {
|
||||||
widget.message.status == MessageSendingStatus.FAILED_UPDATE ||
|
return widget.messageTheme.messageBackgroundColor;
|
||||||
widget.message.status == MessageSendingStatus.FAILED_DELETE)) {
|
|
||||||
return Color(0xffd0021B).withOpacity(.1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (widget.message.attachments
|
if (widget.message.attachments
|
||||||
?.any((element) => element.ogScrapeUrl != null) ==
|
?.any((element) => element.ogScrapeUrl != null) ==
|
||||||
true) {
|
true) {
|
||||||
return Color(0xFFE9F2FF);
|
return StreamChatTheme.of(context).colorTheme.blueAlice;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isOnlyEmoji) {
|
if (isOnlyEmoji) {
|
||||||
@@ -952,58 +973,18 @@ class _MessageWidgetState extends State<MessageWidget> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String _getWebsiteName(String hostName) {
|
|
||||||
switch (hostName) {
|
|
||||||
case 'reddit':
|
|
||||||
return 'Reddit';
|
|
||||||
case 'youtube':
|
|
||||||
return 'Youtube';
|
|
||||||
case 'wikipedia':
|
|
||||||
return 'Wikipedia';
|
|
||||||
case 'twitter':
|
|
||||||
return 'Twitter';
|
|
||||||
case 'facebook':
|
|
||||||
return 'Facebook';
|
|
||||||
case 'amazon':
|
|
||||||
return 'Amazon';
|
|
||||||
case 'yelp':
|
|
||||||
return 'Yelp';
|
|
||||||
case 'imdb':
|
|
||||||
return 'IMDB';
|
|
||||||
case 'pinterest':
|
|
||||||
return 'Pinterest';
|
|
||||||
case 'tripadvisor':
|
|
||||||
return 'TripAdvisor';
|
|
||||||
case 'instagram':
|
|
||||||
return 'Instagram';
|
|
||||||
case 'walmart':
|
|
||||||
return 'Walmart';
|
|
||||||
case 'craigslist':
|
|
||||||
return 'Craigslist';
|
|
||||||
case 'ebay':
|
|
||||||
return 'eBay';
|
|
||||||
case 'linkedin':
|
|
||||||
return 'LinkedIn';
|
|
||||||
case 'google':
|
|
||||||
return 'Google';
|
|
||||||
case 'apple':
|
|
||||||
return 'Apple';
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ThreadReplyPainter extends CustomPainter {
|
class _ThreadReplyPainter extends CustomPainter {
|
||||||
final Color color;
|
final Color color;
|
||||||
|
final BuildContext context;
|
||||||
|
|
||||||
const _ThreadReplyPainter({@required this.color});
|
const _ThreadReplyPainter({this.context, @required this.color});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void paint(Canvas canvas, Size size) {
|
void paint(Canvas canvas, Size size) {
|
||||||
final paint = Paint()
|
final paint = Paint()
|
||||||
..color = color ?? Color(0XFFDBDBDB)
|
..color = color ?? StreamChatTheme.of(context).colorTheme.greyGainsboro
|
||||||
..style = PaintingStyle.stroke
|
..style = PaintingStyle.stroke
|
||||||
..strokeWidth = 1
|
..strokeWidth = 1
|
||||||
..strokeCap = StrokeCap.round;
|
..strokeCap = StrokeCap.round;
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
|
||||||
|
import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
|
||||||
|
|
||||||
|
class OptionListTile extends StatelessWidget {
|
||||||
|
final String title;
|
||||||
|
final Widget leading;
|
||||||
|
final Widget trailing;
|
||||||
|
final VoidCallback onTap;
|
||||||
|
final Color titleColor;
|
||||||
|
final Color tileColor;
|
||||||
|
final Color separatorColor;
|
||||||
|
final TextStyle titleTextStyle;
|
||||||
|
|
||||||
|
OptionListTile({
|
||||||
|
this.title,
|
||||||
|
this.leading,
|
||||||
|
this.trailing,
|
||||||
|
this.onTap,
|
||||||
|
this.titleColor,
|
||||||
|
this.tileColor,
|
||||||
|
this.separatorColor,
|
||||||
|
this.titleTextStyle,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
color: separatorColor ??
|
||||||
|
StreamChatTheme.of(context).colorTheme.greyGainsboro,
|
||||||
|
height: 1.0,
|
||||||
|
),
|
||||||
|
Material(
|
||||||
|
color: tileColor ?? StreamChatTheme.of(context).colorTheme.white,
|
||||||
|
child: Container(
|
||||||
|
height: 63.0,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: onTap,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
if (leading != null) Center(child: leading),
|
||||||
|
if (leading == null)
|
||||||
|
SizedBox(
|
||||||
|
width: 16.0,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
flex: 4,
|
||||||
|
child: Text(
|
||||||
|
title,
|
||||||
|
style: titleTextStyle ??
|
||||||
|
(titleColor == null
|
||||||
|
? StreamChatTheme.of(context).textTheme.bodyBold
|
||||||
|
: StreamChatTheme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.bodyBold
|
||||||
|
.copyWith(
|
||||||
|
color: titleColor,
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
flex: 2,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 16.0),
|
||||||
|
child: Align(
|
||||||
|
alignment: Alignment.centerRight,
|
||||||
|
child: trailing ?? Container(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,321 @@
|
|||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
|
import 'package:emojis/emoji.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:stream_chat/stream_chat.dart';
|
||||||
|
import 'package:video_player/video_player.dart';
|
||||||
|
|
||||||
|
import 'attachment_error.dart';
|
||||||
|
import 'image_attachment.dart';
|
||||||
|
import 'message_text.dart';
|
||||||
|
import 'stream_chat_theme.dart';
|
||||||
|
import 'user_avatar.dart';
|
||||||
|
import 'utils.dart';
|
||||||
|
import 'extension.dart';
|
||||||
|
|
||||||
|
typedef QuotedMessageAttachmentThumbnailBuilder = Widget Function(
|
||||||
|
BuildContext,
|
||||||
|
Attachment,
|
||||||
|
);
|
||||||
|
|
||||||
|
class _VideoAttachmentThumbnail extends StatefulWidget {
|
||||||
|
final Size size;
|
||||||
|
final Attachment attachment;
|
||||||
|
|
||||||
|
const _VideoAttachmentThumbnail({
|
||||||
|
Key key,
|
||||||
|
@required this.attachment,
|
||||||
|
this.size = const Size(32, 32),
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_VideoAttachmentThumbnailState createState() =>
|
||||||
|
_VideoAttachmentThumbnailState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _VideoAttachmentThumbnailState extends State<_VideoAttachmentThumbnail> {
|
||||||
|
VideoPlayerController _controller;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_controller = VideoPlayerController.network(widget.attachment.assetUrl)
|
||||||
|
..initialize().then((_) {
|
||||||
|
setState(() {}); //when your thumbnail will show.
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
super.dispose();
|
||||||
|
_controller.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
height: widget.size.height,
|
||||||
|
width: widget.size.width,
|
||||||
|
child: _controller.value.initialized
|
||||||
|
? VideoPlayer(_controller)
|
||||||
|
: CircularProgressIndicator());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
class QuotedMessageWidget extends StatelessWidget {
|
||||||
|
/// The message
|
||||||
|
final Message message;
|
||||||
|
|
||||||
|
/// The message theme
|
||||||
|
final MessageTheme messageTheme;
|
||||||
|
|
||||||
|
/// If true the widget will be mirrored
|
||||||
|
final bool reverse;
|
||||||
|
|
||||||
|
/// If true the message will show a grey border
|
||||||
|
final bool showBorder;
|
||||||
|
|
||||||
|
/// limit of the text message shown
|
||||||
|
final int textLimit;
|
||||||
|
|
||||||
|
/// Map that defines a thumbnail builder for an attachment type
|
||||||
|
final Map<String, QuotedMessageAttachmentThumbnailBuilder>
|
||||||
|
attachmentThumbnailBuilders;
|
||||||
|
|
||||||
|
final GestureTapCallback onTap;
|
||||||
|
|
||||||
|
///
|
||||||
|
QuotedMessageWidget({
|
||||||
|
Key key,
|
||||||
|
@required this.message,
|
||||||
|
@required this.messageTheme,
|
||||||
|
this.reverse = false,
|
||||||
|
this.showBorder = false,
|
||||||
|
this.textLimit = 170,
|
||||||
|
this.attachmentThumbnailBuilders,
|
||||||
|
this.onTap,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
bool get _hasAttachments => message.attachments?.isNotEmpty == true;
|
||||||
|
|
||||||
|
bool get _containsScrapeUrl =>
|
||||||
|
message.attachments?.any((element) => element.ogScrapeUrl != null) ==
|
||||||
|
true;
|
||||||
|
|
||||||
|
bool get _containsText => message?.text?.isNotEmpty == true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return InkWell(
|
||||||
|
onTap: onTap,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 8, bottom: 6, right: 4, left: 8),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Flexible(child: _buildMessage(context)),
|
||||||
|
SizedBox(width: 4),
|
||||||
|
_buildUserAvatar(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildMessage(BuildContext context) {
|
||||||
|
final isOnlyEmoji =
|
||||||
|
message.text.characters.every((c) => Emoji.byChar(c) != null);
|
||||||
|
var msg = _hasAttachments && !_containsText
|
||||||
|
? message.copyWith(text: message.attachments.last?.title ?? '')
|
||||||
|
: message;
|
||||||
|
if (msg.text.length > textLimit) {
|
||||||
|
msg = msg.copyWith(text: '${msg.text.substring(0, textLimit - 3)}...');
|
||||||
|
}
|
||||||
|
|
||||||
|
final children = [
|
||||||
|
if (_hasAttachments) _parseAttachments(context),
|
||||||
|
if (msg.text.isNotEmpty)
|
||||||
|
Flexible(
|
||||||
|
child: Transform(
|
||||||
|
transform: Matrix4.rotationY(reverse ? pi : 0),
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: MessageText(
|
||||||
|
message: msg,
|
||||||
|
messageTheme: isOnlyEmoji && _containsText
|
||||||
|
? messageTheme.copyWith(
|
||||||
|
messageText: messageTheme.messageText.copyWith(
|
||||||
|
fontSize: 24,
|
||||||
|
))
|
||||||
|
: messageTheme,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
].insertBetween(const SizedBox(width: 8));
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: _getBackgroundColor(context),
|
||||||
|
border: showBorder
|
||||||
|
? Border.all(
|
||||||
|
color: StreamChatTheme.of(context).colorTheme.greyGainsboro,
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
borderRadius: BorderRadius.only(
|
||||||
|
topRight: Radius.circular(12),
|
||||||
|
topLeft: Radius.circular(12),
|
||||||
|
bottomLeft: Radius.circular(12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisAlignment:
|
||||||
|
reverse ? MainAxisAlignment.end : MainAxisAlignment.start,
|
||||||
|
children: reverse ? children.reversed.toList() : children,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildUrlAttachment(Attachment attachment) {
|
||||||
|
final size = Size(32, 32);
|
||||||
|
if (attachment.thumbUrl != null) {
|
||||||
|
return Container(
|
||||||
|
height: size.height,
|
||||||
|
width: size.width,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
image: DecorationImage(
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
image: CachedNetworkImageProvider(
|
||||||
|
attachment.imageUrl,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return AttachmentError(
|
||||||
|
attachment: attachment,
|
||||||
|
size: size,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _parseAttachments(BuildContext context) {
|
||||||
|
Widget child;
|
||||||
|
Attachment attachment;
|
||||||
|
if (_containsScrapeUrl) {
|
||||||
|
attachment = message.attachments.firstWhere(
|
||||||
|
(element) => element.ogScrapeUrl != null,
|
||||||
|
);
|
||||||
|
child = _buildUrlAttachment(attachment);
|
||||||
|
} else {
|
||||||
|
QuotedMessageAttachmentThumbnailBuilder attachmentBuilder;
|
||||||
|
attachment = message.attachments.last;
|
||||||
|
if (attachmentThumbnailBuilders?.containsKey(attachment?.type) == true) {
|
||||||
|
attachmentBuilder = attachmentThumbnailBuilders[attachment?.type];
|
||||||
|
}
|
||||||
|
attachmentBuilder = _defaultAttachmentBuilder[attachment?.type];
|
||||||
|
if (attachmentBuilder == null) {
|
||||||
|
child = Offstage();
|
||||||
|
}
|
||||||
|
child = attachmentBuilder(context, attachment);
|
||||||
|
}
|
||||||
|
child = AbsorbPointer(child: child);
|
||||||
|
return Transform(
|
||||||
|
transform: Matrix4.rotationY(reverse ? pi : 0),
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: Material(
|
||||||
|
clipBehavior: Clip.hardEdge,
|
||||||
|
color: Colors.transparent,
|
||||||
|
shape: attachment.type == 'file' ? null : _getDefaultShape(context),
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ShapeBorder _getDefaultShape(BuildContext context) {
|
||||||
|
return RoundedRectangleBorder(
|
||||||
|
side: BorderSide(
|
||||||
|
color: StreamChatTheme.of(context).colorTheme.greyWhisper,
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildUserAvatar() {
|
||||||
|
return Transform(
|
||||||
|
transform: Matrix4.rotationY(reverse ? pi : 0),
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 4.0),
|
||||||
|
child: UserAvatar(
|
||||||
|
user: message.user,
|
||||||
|
constraints: BoxConstraints.tightFor(
|
||||||
|
height: 24,
|
||||||
|
width: 24,
|
||||||
|
),
|
||||||
|
showOnlineStatus: false,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, QuotedMessageAttachmentThumbnailBuilder>
|
||||||
|
get _defaultAttachmentBuilder {
|
||||||
|
return {
|
||||||
|
'image': (_, attachment) {
|
||||||
|
return ImageAttachment(
|
||||||
|
attachment: attachment,
|
||||||
|
message: message,
|
||||||
|
messageTheme: messageTheme,
|
||||||
|
size: Size(32, 32),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
'video': (_, attachment) {
|
||||||
|
return _VideoAttachmentThumbnail(
|
||||||
|
key: ValueKey(attachment.assetUrl),
|
||||||
|
attachment: attachment,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
'giphy': (_, attachment) {
|
||||||
|
final size = Size(32, 32);
|
||||||
|
return CachedNetworkImage(
|
||||||
|
height: size?.height,
|
||||||
|
width: size?.width,
|
||||||
|
placeholder: (_, __) {
|
||||||
|
return Container(
|
||||||
|
width: size?.width,
|
||||||
|
height: size?.height,
|
||||||
|
child: Center(
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
imageUrl:
|
||||||
|
attachment.thumbUrl ?? attachment.imageUrl ?? attachment.assetUrl,
|
||||||
|
errorWidget: (context, url, error) => AttachmentError(
|
||||||
|
attachment: attachment,
|
||||||
|
size: size,
|
||||||
|
),
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
'file': (_, attachment) {
|
||||||
|
return Container(
|
||||||
|
height: 32,
|
||||||
|
width: 32,
|
||||||
|
child: getFileTypeImage(attachment.extraData['mime_type']),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Color _getBackgroundColor(BuildContext context) {
|
||||||
|
if (_containsScrapeUrl) {
|
||||||
|
return StreamChatTheme.of(context).colorTheme.blueAlice;
|
||||||
|
}
|
||||||
|
return messageTheme.messageBackgroundColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -107,16 +107,22 @@ class ReactionBubble extends StatelessWidget {
|
|||||||
height: 16,
|
height: 16,
|
||||||
color: (!highlightOwnReactions ||
|
color: (!highlightOwnReactions ||
|
||||||
reaction.user.id == StreamChat.of(context).user.id)
|
reaction.user.id == StreamChat.of(context).user.id)
|
||||||
? StreamChatTheme.of(context).accentColor
|
? StreamChatTheme.of(context).colorTheme.accentBlue
|
||||||
: Colors.black.withOpacity(.5),
|
: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.black
|
||||||
|
.withOpacity(.5),
|
||||||
)
|
)
|
||||||
: Icon(
|
: Icon(
|
||||||
Icons.help_outline_rounded,
|
Icons.help_outline_rounded,
|
||||||
size: 16,
|
size: 16,
|
||||||
color: (!highlightOwnReactions ||
|
color: (!highlightOwnReactions ||
|
||||||
reaction.user.id == StreamChat.of(context).user.id)
|
reaction.user.id == StreamChat.of(context).user.id)
|
||||||
? StreamChatTheme.of(context).accentColor
|
? StreamChatTheme.of(context).colorTheme.accentBlue
|
||||||
: Colors.black.withOpacity(.5),
|
: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.black
|
||||||
|
.withOpacity(.5),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,7 +89,9 @@ class _ReactionPickerState extends State<ReactionPicker>
|
|||||||
height: animations[index].value * 24.0,
|
height: animations[index].value * 24.0,
|
||||||
width: animations[index].value * 24.0,
|
width: animations[index].value * 24.0,
|
||||||
color: ownReactionIndex != -1
|
color: ownReactionIndex != -1
|
||||||
? StreamChatTheme.of(context).accentColor
|
? StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.accentBlue
|
||||||
: Theme.of(context)
|
: Theme.of(context)
|
||||||
.iconTheme
|
.iconTheme
|
||||||
.color
|
.color
|
||||||
@@ -134,11 +136,11 @@ class _ReactionPickerState extends State<ReactionPicker>
|
|||||||
|
|
||||||
/// Add a reaction to the message
|
/// Add a reaction to the message
|
||||||
void sendReaction(BuildContext context, String reactionType) {
|
void sendReaction(BuildContext context, String reactionType) {
|
||||||
StreamChannel.of(context)
|
StreamChannel.of(context).channel.sendReaction(
|
||||||
.channel
|
widget.message,
|
||||||
.sendReaction(widget.message, reactionType, extraData: {
|
reactionType,
|
||||||
'enforce_unique': true,
|
enforceUnique: true,
|
||||||
});
|
);
|
||||||
pop();
|
pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,27 +4,27 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
|||||||
/// Used to show the sending status of the message
|
/// Used to show the sending status of the message
|
||||||
class SendingIndicator extends StatelessWidget {
|
class SendingIndicator extends StatelessWidget {
|
||||||
final Message message;
|
final Message message;
|
||||||
final bool allRead;
|
final bool isMessageRead;
|
||||||
|
final double size;
|
||||||
|
|
||||||
const SendingIndicator({
|
const SendingIndicator({
|
||||||
Key key,
|
Key key,
|
||||||
this.message,
|
this.message,
|
||||||
this.allRead = false,
|
this.isMessageRead = false,
|
||||||
|
this.size = 12,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (allRead) {
|
if (isMessageRead) {
|
||||||
return Icon(
|
return StreamSvgIcon.checkAll(
|
||||||
Icons.done_all,
|
size: size,
|
||||||
size: 8,
|
color: StreamChatTheme.of(context).colorTheme.accentBlue,
|
||||||
color: StreamChatTheme.of(context).accentColor,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (message.status == MessageSendingStatus.SENT || message.status == null) {
|
if (message.status == MessageSendingStatus.SENT || message.status == null) {
|
||||||
return Icon(
|
return StreamSvgIcon.check(
|
||||||
Icons.done,
|
size: size,
|
||||||
size: 8,
|
|
||||||
color: IconTheme.of(context).color.withOpacity(0.5),
|
color: IconTheme.of(context).color.withOpacity(0.5),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -32,18 +32,9 @@ class SendingIndicator extends StatelessWidget {
|
|||||||
message.status == MessageSendingStatus.UPDATING) {
|
message.status == MessageSendingStatus.UPDATING) {
|
||||||
return Icon(
|
return Icon(
|
||||||
Icons.access_time,
|
Icons.access_time,
|
||||||
size: 8,
|
size: size,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (message.status == MessageSendingStatus.FAILED ||
|
|
||||||
message.status == MessageSendingStatus.FAILED_UPDATE ||
|
|
||||||
message.status == MessageSendingStatus.FAILED_DELETE) {
|
|
||||||
return Icon(
|
|
||||||
Icons.error_outline,
|
|
||||||
size: 8,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return SizedBox();
|
return SizedBox();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,8 +76,9 @@ class StreamChatState extends State<StreamChat> with WidgetsBindingObserver {
|
|||||||
return Theme(
|
return Theme(
|
||||||
data: materialTheme.copyWith(
|
data: materialTheme.copyWith(
|
||||||
primaryIconTheme: streamTheme.primaryIconTheme,
|
primaryIconTheme: streamTheme.primaryIconTheme,
|
||||||
accentColor: streamTheme.accentColor,
|
accentColor: streamTheme.colorTheme.accentBlue,
|
||||||
scaffoldBackgroundColor: streamTheme.backgroundColor,
|
scaffoldBackgroundColor: streamTheme.colorTheme.white,
|
||||||
|
buttonTheme: streamTheme.buttonTheme,
|
||||||
),
|
),
|
||||||
child: widget.child,
|
child: widget.child,
|
||||||
);
|
);
|
||||||
@@ -91,81 +92,7 @@ class StreamChatState extends State<StreamChat> with WidgetsBindingObserver {
|
|||||||
StreamChatThemeData themeData,
|
StreamChatThemeData themeData,
|
||||||
) {
|
) {
|
||||||
final defaultTheme = StreamChatThemeData.getDefaultTheme(Theme.of(context));
|
final defaultTheme = StreamChatThemeData.getDefaultTheme(Theme.of(context));
|
||||||
final theme = defaultTheme.copyWith(
|
return defaultTheme.merge(themeData) ?? themeData;
|
||||||
primaryColor: themeData?.primaryColor,
|
|
||||||
defaultChannelImage: themeData?.defaultChannelImage,
|
|
||||||
primaryIconTheme: themeData?.primaryIconTheme,
|
|
||||||
defaultUserImage: themeData?.defaultUserImage,
|
|
||||||
backgroundColor: themeData?.backgroundColor,
|
|
||||||
channelTheme: defaultTheme.channelTheme.copyWith(
|
|
||||||
channelHeaderTheme:
|
|
||||||
defaultTheme.channelTheme.channelHeaderTheme.copyWith(
|
|
||||||
color: themeData?.channelTheme?.channelHeaderTheme?.color,
|
|
||||||
lastMessageAt:
|
|
||||||
themeData?.channelTheme?.channelHeaderTheme?.lastMessageAt,
|
|
||||||
title: themeData?.channelTheme?.channelHeaderTheme?.title,
|
|
||||||
avatarTheme: defaultTheme.channelPreviewTheme.avatarTheme.copyWith(
|
|
||||||
constraints: themeData
|
|
||||||
?.channelTheme?.channelHeaderTheme?.avatarTheme?.constraints,
|
|
||||||
borderRadius: themeData
|
|
||||||
?.channelTheme?.channelHeaderTheme?.avatarTheme?.borderRadius,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
inputBackground: themeData?.channelTheme?.inputBackground,
|
|
||||||
messageInputButtonIconTheme:
|
|
||||||
themeData?.channelTheme?.messageInputButtonIconTheme,
|
|
||||||
inputGradient: themeData?.channelTheme?.inputGradient,
|
|
||||||
messageInputButtonTheme:
|
|
||||||
themeData?.channelTheme?.messageInputButtonTheme,
|
|
||||||
),
|
|
||||||
ownMessageTheme: defaultTheme.ownMessageTheme.copyWith(
|
|
||||||
replies: themeData?.ownMessageTheme?.replies,
|
|
||||||
createdAt: themeData?.ownMessageTheme?.createdAt,
|
|
||||||
messageText: themeData?.ownMessageTheme?.messageText,
|
|
||||||
messageBackgroundColor:
|
|
||||||
themeData?.ownMessageTheme?.messageBackgroundColor,
|
|
||||||
messageAuthor: themeData?.ownMessageTheme?.messageAuthor,
|
|
||||||
messageLinks: themeData?.ownMessageTheme?.messageLinks,
|
|
||||||
avatarTheme: defaultTheme.ownMessageTheme.avatarTheme.copyWith(
|
|
||||||
constraints: themeData?.ownMessageTheme?.avatarTheme?.constraints,
|
|
||||||
borderRadius: themeData?.ownMessageTheme?.avatarTheme?.borderRadius,
|
|
||||||
),
|
|
||||||
reactionsBorderColor: themeData?.ownMessageTheme?.reactionsBorderColor,
|
|
||||||
reactionsBackgroundColor:
|
|
||||||
themeData?.ownMessageTheme?.reactionsBackgroundColor,
|
|
||||||
),
|
|
||||||
otherMessageTheme: defaultTheme.otherMessageTheme.copyWith(
|
|
||||||
replies: themeData?.otherMessageTheme?.replies,
|
|
||||||
createdAt: themeData?.otherMessageTheme?.createdAt,
|
|
||||||
messageText: themeData?.otherMessageTheme?.messageText,
|
|
||||||
messageBackgroundColor:
|
|
||||||
themeData?.otherMessageTheme?.messageBackgroundColor,
|
|
||||||
messageAuthor: themeData?.otherMessageTheme?.messageAuthor,
|
|
||||||
messageLinks: themeData?.otherMessageTheme?.messageLinks,
|
|
||||||
avatarTheme: defaultTheme.otherMessageTheme.avatarTheme.copyWith(
|
|
||||||
constraints: themeData?.otherMessageTheme?.avatarTheme?.constraints,
|
|
||||||
borderRadius: themeData?.otherMessageTheme?.avatarTheme?.borderRadius,
|
|
||||||
),
|
|
||||||
reactionsBorderColor:
|
|
||||||
themeData?.otherMessageTheme?.reactionsBorderColor,
|
|
||||||
reactionsBackgroundColor:
|
|
||||||
themeData?.otherMessageTheme?.reactionsBackgroundColor,
|
|
||||||
),
|
|
||||||
accentColor: themeData?.accentColor,
|
|
||||||
secondaryColor: themeData?.secondaryColor,
|
|
||||||
channelPreviewTheme: defaultTheme.channelPreviewTheme.copyWith(
|
|
||||||
avatarTheme: defaultTheme.channelPreviewTheme.avatarTheme.copyWith(
|
|
||||||
constraints: themeData?.channelPreviewTheme?.avatarTheme?.constraints,
|
|
||||||
borderRadius:
|
|
||||||
themeData?.channelPreviewTheme?.avatarTheme?.borderRadius,
|
|
||||||
),
|
|
||||||
title: themeData?.channelPreviewTheme?.title,
|
|
||||||
lastMessageAt: themeData?.channelPreviewTheme?.lastMessageAt,
|
|
||||||
subtitle: themeData?.channelPreviewTheme?.subtitle,
|
|
||||||
unreadCounterColor: themeData?.channelPreviewTheme?.unreadCounterColor,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return theme;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The current user
|
/// The current user
|
||||||
|
|||||||
@@ -42,17 +42,14 @@ class StreamChatTheme extends InheritedWidget {
|
|||||||
|
|
||||||
/// Theme data
|
/// Theme data
|
||||||
class StreamChatThemeData {
|
class StreamChatThemeData {
|
||||||
/// Primary color of the chat widgets
|
/// The text themes used in the widgets
|
||||||
final Color primaryColor;
|
final TextTheme textTheme;
|
||||||
|
|
||||||
/// Secondary color of the chat widgets
|
/// The button themes used in the widgets
|
||||||
final Color secondaryColor;
|
final ButtonThemeData buttonTheme;
|
||||||
|
|
||||||
/// Accent color of the chat widgets
|
/// The text themes used in the widgets
|
||||||
final Color accentColor;
|
final ColorTheme colorTheme;
|
||||||
|
|
||||||
/// Background color of the chat widgets
|
|
||||||
final Color backgroundColor;
|
|
||||||
|
|
||||||
/// Theme of the [ChannelPreview]
|
/// Theme of the [ChannelPreview]
|
||||||
final ChannelPreviewTheme channelPreviewTheme;
|
final ChannelPreviewTheme channelPreviewTheme;
|
||||||
@@ -79,11 +76,10 @@ class StreamChatThemeData {
|
|||||||
final List<ReactionIcon> reactionIcons;
|
final List<ReactionIcon> reactionIcons;
|
||||||
|
|
||||||
/// Create a theme from scratch
|
/// Create a theme from scratch
|
||||||
StreamChatThemeData({
|
const StreamChatThemeData({
|
||||||
this.primaryColor,
|
this.textTheme,
|
||||||
this.secondaryColor,
|
this.buttonTheme,
|
||||||
this.accentColor,
|
this.colorTheme,
|
||||||
this.backgroundColor,
|
|
||||||
this.channelPreviewTheme,
|
this.channelPreviewTheme,
|
||||||
this.channelTheme,
|
this.channelTheme,
|
||||||
this.otherMessageTheme,
|
this.otherMessageTheme,
|
||||||
@@ -97,44 +93,25 @@ class StreamChatThemeData {
|
|||||||
/// Create a theme from a Material [Theme]
|
/// Create a theme from a Material [Theme]
|
||||||
factory StreamChatThemeData.fromTheme(ThemeData theme) {
|
factory StreamChatThemeData.fromTheme(ThemeData theme) {
|
||||||
final defaultTheme = getDefaultTheme(theme);
|
final defaultTheme = getDefaultTheme(theme);
|
||||||
|
final customizedTheme = StreamChatThemeData(
|
||||||
return defaultTheme.copyWith(
|
|
||||||
accentColor: theme.accentColor,
|
|
||||||
primaryIconTheme: theme.primaryIconTheme,
|
primaryIconTheme: theme.primaryIconTheme,
|
||||||
primaryColor: theme.colorScheme.primary,
|
ownMessageTheme: MessageTheme(
|
||||||
secondaryColor: theme.colorScheme.secondary,
|
replies: TextStyle(color: theme.accentColor),
|
||||||
backgroundColor: theme.scaffoldBackgroundColor,
|
messageLinks: TextStyle(color: theme.accentColor),
|
||||||
channelTheme: defaultTheme.channelTheme.copyWith(
|
|
||||||
inputGradient: LinearGradient(colors: [
|
|
||||||
theme.accentColor.withOpacity(.5),
|
|
||||||
theme.accentColor,
|
|
||||||
]),
|
|
||||||
),
|
|
||||||
ownMessageTheme: defaultTheme.ownMessageTheme.copyWith(
|
|
||||||
replies: defaultTheme.ownMessageTheme.replies.copyWith(
|
|
||||||
color: theme.accentColor,
|
|
||||||
),
|
|
||||||
messageLinks: TextStyle(
|
|
||||||
color: theme.accentColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
otherMessageTheme: defaultTheme.otherMessageTheme.copyWith(
|
|
||||||
replies: defaultTheme.otherMessageTheme.replies.copyWith(
|
|
||||||
color: theme.accentColor,
|
|
||||||
),
|
|
||||||
messageLinks: TextStyle(
|
|
||||||
color: theme.accentColor,
|
|
||||||
),
|
),
|
||||||
|
otherMessageTheme: MessageTheme(
|
||||||
|
replies: TextStyle(color: theme.accentColor),
|
||||||
|
messageLinks: TextStyle(color: theme.accentColor),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
return defaultTheme.merge(customizedTheme) ?? customizedTheme;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a copy of [StreamChatThemeData] with specified attributes overridden.
|
/// Creates a copy of [StreamChatThemeData] with specified attributes overridden.
|
||||||
StreamChatThemeData copyWith({
|
StreamChatThemeData copyWith({
|
||||||
Color primaryColor,
|
TextTheme textTheme,
|
||||||
Color secondaryColor,
|
ButtonThemeData buttonTheme,
|
||||||
Color accentColor,
|
ColorTheme colorTheme,
|
||||||
Color backgroundColor,
|
|
||||||
ChannelPreviewTheme channelPreviewTheme,
|
ChannelPreviewTheme channelPreviewTheme,
|
||||||
ChannelTheme channelTheme,
|
ChannelTheme channelTheme,
|
||||||
MessageTheme ownMessageTheme,
|
MessageTheme ownMessageTheme,
|
||||||
@@ -145,91 +122,63 @@ class StreamChatThemeData {
|
|||||||
List<ReactionIcon> reactionIcons,
|
List<ReactionIcon> reactionIcons,
|
||||||
}) =>
|
}) =>
|
||||||
StreamChatThemeData(
|
StreamChatThemeData(
|
||||||
primaryColor: primaryColor ?? this.primaryColor,
|
textTheme: textTheme ?? this.textTheme,
|
||||||
secondaryColor: secondaryColor ?? this.secondaryColor,
|
buttonTheme: buttonTheme ?? this.buttonTheme,
|
||||||
|
colorTheme: colorTheme ?? this.colorTheme,
|
||||||
primaryIconTheme: primaryIconTheme ?? this.primaryIconTheme,
|
primaryIconTheme: primaryIconTheme ?? this.primaryIconTheme,
|
||||||
accentColor: accentColor ?? this.accentColor,
|
|
||||||
defaultChannelImage: defaultChannelImage ?? this.defaultChannelImage,
|
defaultChannelImage: defaultChannelImage ?? this.defaultChannelImage,
|
||||||
defaultUserImage: defaultUserImage ?? this.defaultUserImage,
|
defaultUserImage: defaultUserImage ?? this.defaultUserImage,
|
||||||
backgroundColor: backgroundColor ?? this.backgroundColor,
|
channelPreviewTheme: channelPreviewTheme ?? this.channelPreviewTheme,
|
||||||
channelPreviewTheme: channelPreviewTheme?.copyWith(
|
channelTheme: channelTheme ?? this.channelTheme,
|
||||||
title:
|
ownMessageTheme: ownMessageTheme ?? this.ownMessageTheme,
|
||||||
channelPreviewTheme.title ?? this.channelPreviewTheme.title,
|
otherMessageTheme: otherMessageTheme ?? this.otherMessageTheme,
|
||||||
subtitle: channelPreviewTheme.subtitle ??
|
|
||||||
this.channelPreviewTheme.subtitle,
|
|
||||||
lastMessageAt: channelPreviewTheme.lastMessageAt ??
|
|
||||||
this.channelPreviewTheme.lastMessageAt,
|
|
||||||
avatarTheme: channelPreviewTheme.avatarTheme ??
|
|
||||||
this.channelPreviewTheme.avatarTheme,
|
|
||||||
) ??
|
|
||||||
this.channelPreviewTheme,
|
|
||||||
channelTheme: channelTheme?.copyWith(
|
|
||||||
channelHeaderTheme: channelTheme.channelHeaderTheme ??
|
|
||||||
this.channelTheme.channelHeaderTheme,
|
|
||||||
messageInputButtonIconTheme:
|
|
||||||
channelTheme.messageInputButtonIconTheme ??
|
|
||||||
this.channelTheme.messageInputButtonIconTheme,
|
|
||||||
messageInputButtonTheme: channelTheme.messageInputButtonTheme ??
|
|
||||||
this.channelTheme.messageInputButtonTheme,
|
|
||||||
inputGradient:
|
|
||||||
channelTheme.inputGradient ?? this.channelTheme.inputGradient,
|
|
||||||
inputBackground: channelTheme.inputBackground ??
|
|
||||||
this.channelTheme.inputBackground,
|
|
||||||
) ??
|
|
||||||
this.channelTheme,
|
|
||||||
ownMessageTheme: ownMessageTheme?.copyWith(
|
|
||||||
messageText: ownMessageTheme?.messageText ??
|
|
||||||
this.ownMessageTheme.messageText,
|
|
||||||
messageAuthor: ownMessageTheme?.messageAuthor ??
|
|
||||||
this.ownMessageTheme.messageAuthor,
|
|
||||||
messageLinks: ownMessageTheme?.messageLinks ??
|
|
||||||
this.ownMessageTheme.messageLinks,
|
|
||||||
createdAt:
|
|
||||||
ownMessageTheme?.createdAt ?? this.ownMessageTheme.createdAt,
|
|
||||||
replies: ownMessageTheme?.replies ?? this.ownMessageTheme.replies,
|
|
||||||
messageBackgroundColor: ownMessageTheme?.messageBackgroundColor ??
|
|
||||||
this.ownMessageTheme.messageBackgroundColor,
|
|
||||||
avatarTheme: ownMessageTheme?.avatarTheme ??
|
|
||||||
this.ownMessageTheme.avatarTheme,
|
|
||||||
replyThreadColor: ownMessageTheme?.replyThreadColor ??
|
|
||||||
this.ownMessageTheme.replyThreadColor,
|
|
||||||
) ??
|
|
||||||
this.ownMessageTheme,
|
|
||||||
otherMessageTheme: otherMessageTheme?.copyWith(
|
|
||||||
messageText: otherMessageTheme?.messageText ??
|
|
||||||
this.otherMessageTheme.messageText,
|
|
||||||
messageAuthor: otherMessageTheme?.messageAuthor ??
|
|
||||||
this.otherMessageTheme.messageAuthor,
|
|
||||||
messageLinks: otherMessageTheme?.messageLinks ??
|
|
||||||
this.otherMessageTheme.messageLinks,
|
|
||||||
createdAt: otherMessageTheme?.createdAt ??
|
|
||||||
this.otherMessageTheme.createdAt,
|
|
||||||
replies:
|
|
||||||
otherMessageTheme?.replies ?? this.otherMessageTheme.replies,
|
|
||||||
messageBackgroundColor:
|
|
||||||
otherMessageTheme?.messageBackgroundColor ??
|
|
||||||
this.otherMessageTheme.messageBackgroundColor,
|
|
||||||
avatarTheme: otherMessageTheme?.avatarTheme ??
|
|
||||||
this.otherMessageTheme.avatarTheme,
|
|
||||||
replyThreadColor: ownMessageTheme?.replyThreadColor ??
|
|
||||||
this.ownMessageTheme.replyThreadColor,
|
|
||||||
) ??
|
|
||||||
this.otherMessageTheme,
|
|
||||||
reactionIcons: reactionIcons ?? this.reactionIcons,
|
reactionIcons: reactionIcons ?? this.reactionIcons,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
StreamChatThemeData merge(StreamChatThemeData other) {
|
||||||
|
if (other == null) return this;
|
||||||
|
return copyWith(
|
||||||
|
textTheme: textTheme?.merge(other.textTheme) ?? other.textTheme,
|
||||||
|
buttonTheme: other.buttonTheme,
|
||||||
|
colorTheme: colorTheme?.merge(other.colorTheme) ?? other.colorTheme,
|
||||||
|
primaryIconTheme: other.primaryIconTheme,
|
||||||
|
defaultChannelImage: other.defaultChannelImage,
|
||||||
|
defaultUserImage: other.defaultUserImage,
|
||||||
|
channelPreviewTheme:
|
||||||
|
channelPreviewTheme?.merge(other.channelPreviewTheme) ??
|
||||||
|
other.channelPreviewTheme,
|
||||||
|
channelTheme:
|
||||||
|
channelTheme?.merge(other.channelTheme) ?? other.channelTheme,
|
||||||
|
ownMessageTheme: ownMessageTheme?.merge(other.ownMessageTheme) ??
|
||||||
|
other.ownMessageTheme,
|
||||||
|
otherMessageTheme: otherMessageTheme?.merge(other.otherMessageTheme) ??
|
||||||
|
other.otherMessageTheme,
|
||||||
|
reactionIcons: other.reactionIcons,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the default Stream Chat theme
|
/// Get the default Stream Chat theme
|
||||||
static StreamChatThemeData getDefaultTheme(ThemeData theme) {
|
static StreamChatThemeData getDefaultTheme(ThemeData theme) {
|
||||||
final accentColor = Color(0xff006cff);
|
final accentColor = Color(0xff006cff);
|
||||||
final isDark = theme.brightness == Brightness.dark;
|
final isDark = theme.brightness == Brightness.dark;
|
||||||
|
final textTheme = isDark ? TextTheme.dark() : TextTheme.light();
|
||||||
|
final colorTheme = isDark ? ColorTheme.dark() : ColorTheme.light();
|
||||||
return StreamChatThemeData(
|
return StreamChatThemeData(
|
||||||
secondaryColor: Color(0xffEAEAEA),
|
textTheme: textTheme,
|
||||||
accentColor: accentColor,
|
colorTheme: colorTheme,
|
||||||
primaryColor: isDark ? Colors.black : Colors.white,
|
buttonTheme: ButtonThemeData(
|
||||||
primaryIconTheme: IconThemeData(
|
height: 48.0,
|
||||||
color: isDark ? Colors.white : Colors.black.withOpacity(.5)),
|
buttonColor: isDark ? Color(0xffffffff) : Color(0xff006aff),
|
||||||
|
textTheme: ButtonTextTheme.accent,
|
||||||
|
colorScheme: theme.colorScheme.copyWith(
|
||||||
|
secondary: isDark ? Color(0xff005eff) : Color(0xffffffff),
|
||||||
|
),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(26),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
primaryIconTheme: IconThemeData(color: colorTheme.black.withOpacity(.5)),
|
||||||
defaultChannelImage: (context, channel) => SizedBox(),
|
defaultChannelImage: (context, channel) => SizedBox(),
|
||||||
backgroundColor: isDark ? Colors.black : Colors.white,
|
|
||||||
defaultUserImage: (context, user) => Center(
|
defaultUserImage: (context, user) => Center(
|
||||||
child: CachedNetworkImage(
|
child: CachedNetworkImage(
|
||||||
filterQuality: FilterQuality.high,
|
filterQuality: FilterQuality.high,
|
||||||
@@ -238,7 +187,7 @@ class StreamChatThemeData {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
channelPreviewTheme: ChannelPreviewTheme(
|
channelPreviewTheme: ChannelPreviewTheme(
|
||||||
unreadCounterColor: Color(0xffff3742),
|
unreadCounterColor: colorTheme.accentRed,
|
||||||
avatarTheme: AvatarTheme(
|
avatarTheme: AvatarTheme(
|
||||||
borderRadius: BorderRadius.circular(20),
|
borderRadius: BorderRadius.circular(20),
|
||||||
constraints: BoxConstraints.tightFor(
|
constraints: BoxConstraints.tightFor(
|
||||||
@@ -246,21 +195,14 @@ class StreamChatThemeData {
|
|||||||
width: 40,
|
width: 40,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
title: TextStyle(
|
title: textTheme.bodyBold,
|
||||||
fontSize: 14,
|
subtitle: textTheme.footnote.copyWith(
|
||||||
color: isDark ? Colors.white : Colors.black,
|
color: Color(0xff7A7A7A),
|
||||||
fontWeight: FontWeight.bold),
|
|
||||||
subtitle: TextStyle(
|
|
||||||
fontSize: 12.5,
|
|
||||||
color: (isDark ? Colors.white : Colors.black).withOpacity(0.5),
|
|
||||||
),
|
|
||||||
lastMessageAt: TextStyle(
|
|
||||||
fontSize: 11,
|
|
||||||
color: isDark
|
|
||||||
? Colors.white.withOpacity(.5)
|
|
||||||
: Colors.black.withOpacity(.5),
|
|
||||||
),
|
),
|
||||||
|
lastMessageAt: textTheme.footnote.copyWith(
|
||||||
|
color: colorTheme.black.withOpacity(.5),
|
||||||
),
|
),
|
||||||
|
indicatorIconSize: 16.0),
|
||||||
channelTheme: ChannelTheme(
|
channelTheme: ChannelTheme(
|
||||||
messageInputButtonIconTheme: theme.iconTheme.copyWith(
|
messageInputButtonIconTheme: theme.iconTheme.copyWith(
|
||||||
color: accentColor,
|
color: accentColor,
|
||||||
@@ -273,34 +215,25 @@ class StreamChatThemeData {
|
|||||||
width: 40,
|
width: 40,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
color: isDark ? Colors.black : Colors.white,
|
color: colorTheme.white,
|
||||||
title: TextStyle(
|
title: TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
color: isDark ? Colors.white : Colors.black,
|
color: colorTheme.black,
|
||||||
),
|
),
|
||||||
lastMessageAt: TextStyle(
|
lastMessageAt: TextStyle(
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
color: isDark
|
color: colorTheme.black.withOpacity(.5),
|
||||||
? Colors.white.withOpacity(.5)
|
|
||||||
: Colors.black.withOpacity(.5),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
inputBackground:
|
inputBackground: colorTheme.white.withAlpha(12),
|
||||||
isDark ? Colors.black.withAlpha(12) : Colors.white.withAlpha(12),
|
|
||||||
inputGradient: LinearGradient(colors: [
|
|
||||||
Color(0xFF00AEFF),
|
|
||||||
Color(0xFF0076FF),
|
|
||||||
]),
|
|
||||||
),
|
),
|
||||||
ownMessageTheme: MessageTheme(
|
ownMessageTheme: MessageTheme(
|
||||||
messageText: TextStyle(
|
messageText: TextStyle(
|
||||||
fontSize: 14.5,
|
fontSize: 14.5,
|
||||||
color: isDark ? Colors.white : Colors.black,
|
color: colorTheme.black,
|
||||||
),
|
),
|
||||||
createdAt: TextStyle(
|
createdAt: TextStyle(
|
||||||
color: isDark
|
color: colorTheme.black.withOpacity(.5),
|
||||||
? Colors.white.withOpacity(.5)
|
|
||||||
: Colors.black.withOpacity(.5),
|
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
),
|
),
|
||||||
replies: TextStyle(
|
replies: TextStyle(
|
||||||
@@ -308,10 +241,10 @@ class StreamChatThemeData {
|
|||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
),
|
),
|
||||||
messageBackgroundColor: isDark ? Color(0xff191919) : Color(0xffEAEAEA),
|
messageBackgroundColor: colorTheme.greyGainsboro,
|
||||||
reactionsBackgroundColor: isDark ? Colors.black : Colors.white,
|
reactionsBackgroundColor: colorTheme.white,
|
||||||
reactionsBorderColor: isDark ? Color(0xff191919) : Color(0xffEAEAEA),
|
reactionsBorderColor: colorTheme.greyWhisper,
|
||||||
replyThreadColor: isDark ? Color(0xff191919) : Color(0xffEAEAEA),
|
messageBorderColor: colorTheme.greyGainsboro,
|
||||||
avatarTheme: AvatarTheme(
|
avatarTheme: AvatarTheme(
|
||||||
borderRadius: BorderRadius.circular(20),
|
borderRadius: BorderRadius.circular(20),
|
||||||
constraints: BoxConstraints.tightFor(
|
constraints: BoxConstraints.tightFor(
|
||||||
@@ -324,17 +257,14 @@ class StreamChatThemeData {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
otherMessageTheme: MessageTheme(
|
otherMessageTheme: MessageTheme(
|
||||||
reactionsBackgroundColor:
|
reactionsBackgroundColor: colorTheme.greyGainsboro,
|
||||||
isDark ? Color(0xff191919) : Color(0xffEAEAEA),
|
reactionsBorderColor: colorTheme.white,
|
||||||
reactionsBorderColor: isDark ? Colors.black : Colors.white,
|
|
||||||
messageText: TextStyle(
|
messageText: TextStyle(
|
||||||
fontSize: 14.5,
|
fontSize: 14.5,
|
||||||
color: isDark ? Colors.white : Colors.black,
|
color: colorTheme.black,
|
||||||
),
|
),
|
||||||
createdAt: TextStyle(
|
createdAt: TextStyle(
|
||||||
color: isDark
|
color: colorTheme.black.withOpacity(.5),
|
||||||
? Colors.white.withOpacity(.5)
|
|
||||||
: Colors.black.withOpacity(.5),
|
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
),
|
),
|
||||||
replies: TextStyle(
|
replies: TextStyle(
|
||||||
@@ -345,9 +275,8 @@ class StreamChatThemeData {
|
|||||||
messageLinks: TextStyle(
|
messageLinks: TextStyle(
|
||||||
color: accentColor,
|
color: accentColor,
|
||||||
),
|
),
|
||||||
messageBackgroundColor: isDark ? Colors.black : Colors.white,
|
messageBackgroundColor: colorTheme.white,
|
||||||
replyThreadColor:
|
messageBorderColor: colorTheme.greyWhisper,
|
||||||
isDark ? Colors.white.withAlpha(24) : Colors.black.withAlpha(24),
|
|
||||||
avatarTheme: AvatarTheme(
|
avatarTheme: AvatarTheme(
|
||||||
borderRadius: BorderRadius.circular(20),
|
borderRadius: BorderRadius.circular(20),
|
||||||
constraints: BoxConstraints.tightFor(
|
constraints: BoxConstraints.tightFor(
|
||||||
@@ -362,19 +291,19 @@ class StreamChatThemeData {
|
|||||||
assetName: 'Icon_love_reaction.svg',
|
assetName: 'Icon_love_reaction.svg',
|
||||||
),
|
),
|
||||||
ReactionIcon(
|
ReactionIcon(
|
||||||
type: 'thumbs_up',
|
type: 'like',
|
||||||
assetName: 'Icon_thumbs_up_reaction.svg',
|
assetName: 'Icon_thumbs_up_reaction.svg',
|
||||||
),
|
),
|
||||||
ReactionIcon(
|
ReactionIcon(
|
||||||
type: 'thumbs_down',
|
type: 'sad',
|
||||||
assetName: 'Icon_thumbs_down_reaction.svg',
|
assetName: 'Icon_thumbs_down_reaction.svg',
|
||||||
),
|
),
|
||||||
ReactionIcon(
|
ReactionIcon(
|
||||||
type: 'lol',
|
type: 'haha',
|
||||||
assetName: 'Icon_LOL_reaction.svg',
|
assetName: 'Icon_LOL_reaction.svg',
|
||||||
),
|
),
|
||||||
ReactionIcon(
|
ReactionIcon(
|
||||||
type: 'wut',
|
type: 'wow',
|
||||||
assetName: 'Icon_wut_reaction.svg',
|
assetName: 'Icon_wut_reaction.svg',
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -382,6 +311,337 @@ class StreamChatThemeData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum TextThemeType {
|
||||||
|
light,
|
||||||
|
dark,
|
||||||
|
}
|
||||||
|
|
||||||
|
class TextTheme {
|
||||||
|
final TextStyle title;
|
||||||
|
final TextStyle headlineBold;
|
||||||
|
final TextStyle headline;
|
||||||
|
final TextStyle bodyBold;
|
||||||
|
final TextStyle body;
|
||||||
|
final TextStyle footnoteBold;
|
||||||
|
final TextStyle footnote;
|
||||||
|
final TextStyle captionBold;
|
||||||
|
|
||||||
|
TextTheme.light({
|
||||||
|
this.title = const TextStyle(
|
||||||
|
fontSize: 22,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.black,
|
||||||
|
),
|
||||||
|
this.headlineBold = const TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.black,
|
||||||
|
),
|
||||||
|
this.headline = const TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: Colors.black,
|
||||||
|
),
|
||||||
|
this.bodyBold = const TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.black,
|
||||||
|
),
|
||||||
|
this.body = const TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: Colors.black,
|
||||||
|
),
|
||||||
|
this.footnoteBold = const TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: Colors.black,
|
||||||
|
),
|
||||||
|
this.footnote = const TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: Colors.black,
|
||||||
|
),
|
||||||
|
this.captionBold = const TextStyle(
|
||||||
|
fontSize: 10,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.black,
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
TextTheme.dark({
|
||||||
|
this.title = const TextStyle(
|
||||||
|
fontSize: 22,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
this.headlineBold = const TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
this.headline = const TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
this.bodyBold = const TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
this.body = const TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
this.footnoteBold = const TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
this.footnote = const TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
this.captionBold = const TextStyle(
|
||||||
|
fontSize: 10,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
TextTheme copyWith({
|
||||||
|
TextThemeType type = TextThemeType.light,
|
||||||
|
TextStyle body,
|
||||||
|
TextStyle title,
|
||||||
|
TextStyle headlineBold,
|
||||||
|
TextStyle headline,
|
||||||
|
TextStyle bodyBold,
|
||||||
|
TextStyle footnoteBold,
|
||||||
|
TextStyle footnote,
|
||||||
|
TextStyle captionBold,
|
||||||
|
}) {
|
||||||
|
return type == TextThemeType.light
|
||||||
|
? TextTheme.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,
|
||||||
|
)
|
||||||
|
: TextTheme.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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TextTheme merge(TextTheme other) {
|
||||||
|
if (other == null) return this;
|
||||||
|
return copyWith(
|
||||||
|
body: body?.merge(other.body) ?? other.body,
|
||||||
|
title: title?.merge(other.title) ?? other.title,
|
||||||
|
headlineBold:
|
||||||
|
headlineBold?.merge(other.headlineBold) ?? other.headlineBold,
|
||||||
|
headline: headline?.merge(other.headline) ?? other.headline,
|
||||||
|
bodyBold: bodyBold?.merge(other.bodyBold) ?? other.bodyBold,
|
||||||
|
footnoteBold:
|
||||||
|
footnoteBold?.merge(other.footnoteBold) ?? other.footnoteBold,
|
||||||
|
footnote: footnote?.merge(other.footnote) ?? other.footnote,
|
||||||
|
captionBold: captionBold?.merge(other.captionBold) ?? other.captionBold,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ColorThemeType {
|
||||||
|
light,
|
||||||
|
dark,
|
||||||
|
}
|
||||||
|
|
||||||
|
class ColorTheme {
|
||||||
|
final Color black;
|
||||||
|
final Color grey;
|
||||||
|
final Color greyGainsboro;
|
||||||
|
final Color greyWhisper;
|
||||||
|
final Color whiteSmoke;
|
||||||
|
final Color whiteSnow;
|
||||||
|
final Color white;
|
||||||
|
final Color blueAlice;
|
||||||
|
final Color accentBlue;
|
||||||
|
final Color accentRed;
|
||||||
|
final Color accentGreen;
|
||||||
|
final Effect borderTop;
|
||||||
|
final Effect borderBottom;
|
||||||
|
final Effect shadowIconButton;
|
||||||
|
final Effect modalShadow;
|
||||||
|
final Color highlight;
|
||||||
|
final Color overlay;
|
||||||
|
final Color overlayDark;
|
||||||
|
final Gradient bgGradient;
|
||||||
|
|
||||||
|
ColorTheme.light({
|
||||||
|
this.black = const Color(0xff000000),
|
||||||
|
this.grey = const Color(0xff7a7a7a),
|
||||||
|
this.greyGainsboro = const Color(0xffdbdbdb),
|
||||||
|
this.greyWhisper = const Color(0xffecebeb),
|
||||||
|
this.whiteSmoke = const Color(0xfff2f2f2),
|
||||||
|
this.whiteSnow = const Color(0xfffcfcfc),
|
||||||
|
this.white = const Color(0xffffffff),
|
||||||
|
this.blueAlice = const Color(0xffe9f2ff),
|
||||||
|
this.accentBlue = const Color(0xff005FFF),
|
||||||
|
this.accentRed = const Color(0xffFF3842),
|
||||||
|
this.accentGreen = const Color(0xff20E070),
|
||||||
|
this.highlight = const Color(0xfffbf4dd),
|
||||||
|
this.overlay = const Color.fromRGBO(0, 0, 0, 0.2),
|
||||||
|
this.overlayDark = const Color.fromRGBO(0, 0, 0, 0.6),
|
||||||
|
this.bgGradient = const LinearGradient(
|
||||||
|
begin: Alignment.topCenter,
|
||||||
|
end: Alignment.bottomCenter,
|
||||||
|
colors: [const Color(0xfff7f7f7), const Color(0xfffcfcfc)],
|
||||||
|
stops: [0, 1],
|
||||||
|
),
|
||||||
|
this.borderTop = const Effect(
|
||||||
|
sigmaX: 0, sigmaY: -1, color: Color(0xff141924), blur: 0.0),
|
||||||
|
this.borderBottom =
|
||||||
|
const Effect(sigmaX: 0, sigmaY: 1, color: Color(0xff141924), blur: 0.0),
|
||||||
|
this.shadowIconButton = const Effect(
|
||||||
|
sigmaX: 0, sigmaY: 2, color: Color(0xff000000), alpha: 0.5, blur: 4.0),
|
||||||
|
this.modalShadow = const Effect(
|
||||||
|
sigmaX: 0, sigmaY: 0, color: Color(0xff000000), alpha: 1, blur: 8.0),
|
||||||
|
});
|
||||||
|
|
||||||
|
ColorTheme.dark({
|
||||||
|
this.black = const Color(0xffffffff),
|
||||||
|
this.grey = const Color(0xff7a7a7a),
|
||||||
|
this.greyGainsboro = const Color(0xff2d2f2f),
|
||||||
|
this.greyWhisper = const Color(0xff1c1e22),
|
||||||
|
this.whiteSmoke = const Color(0xff13151b),
|
||||||
|
this.whiteSnow = const Color(0xff070A0D),
|
||||||
|
this.white = const Color(0xff101418),
|
||||||
|
this.blueAlice = const Color(0xff00193D),
|
||||||
|
this.accentBlue = const Color(0xff005FFF),
|
||||||
|
this.accentRed = const Color(0xffFF3742),
|
||||||
|
this.accentGreen = const Color(0xff20E070),
|
||||||
|
this.borderTop = const Effect(
|
||||||
|
sigmaX: 0, sigmaY: -1, color: Color(0xff141924), blur: 0.0),
|
||||||
|
this.borderBottom =
|
||||||
|
const Effect(sigmaX: 0, sigmaY: 1, color: Color(0xff141924), blur: 0.0),
|
||||||
|
this.shadowIconButton = const Effect(
|
||||||
|
sigmaX: 0, sigmaY: 2, color: Color(0xff000000), alpha: 0.5, blur: 4.0),
|
||||||
|
this.modalShadow = const Effect(
|
||||||
|
sigmaX: 0, sigmaY: 0, color: Color(0xff000000), alpha: 1, blur: 8.0),
|
||||||
|
this.highlight = const Color(0xff302d22),
|
||||||
|
this.overlay = const Color.fromRGBO(0, 0, 0, 0.4),
|
||||||
|
this.overlayDark = const Color.fromRGBO(255, 255, 255, 0.6),
|
||||||
|
this.bgGradient = const LinearGradient(
|
||||||
|
begin: Alignment.topCenter,
|
||||||
|
end: Alignment.bottomCenter,
|
||||||
|
colors: [const Color(0xff101214), const Color(0xff070a0d)],
|
||||||
|
stops: [0, 1],
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
ColorTheme copyWith({
|
||||||
|
ColorThemeType type = ColorThemeType.light,
|
||||||
|
Color black,
|
||||||
|
Color grey,
|
||||||
|
Color greyGainsboro,
|
||||||
|
Color greyWhisper,
|
||||||
|
Color whiteSmoke,
|
||||||
|
Color whiteSnow,
|
||||||
|
Color white,
|
||||||
|
Color blueAlice,
|
||||||
|
Color accentBlue,
|
||||||
|
Color accentRed,
|
||||||
|
Color accentGreen,
|
||||||
|
Effect borderTop,
|
||||||
|
Effect borderBottom,
|
||||||
|
Effect shadowIconButton,
|
||||||
|
Effect modalShadow,
|
||||||
|
Color highlight,
|
||||||
|
Color overlay,
|
||||||
|
Color overlayDark,
|
||||||
|
Gradient bgGradient,
|
||||||
|
}) {
|
||||||
|
return type == ColorThemeType.light
|
||||||
|
? ColorTheme.light(
|
||||||
|
black: black ?? this.black,
|
||||||
|
grey: grey ?? this.grey,
|
||||||
|
greyGainsboro: greyGainsboro ?? this.greyGainsboro,
|
||||||
|
greyWhisper: greyWhisper ?? this.greyWhisper,
|
||||||
|
whiteSmoke: whiteSmoke ?? this.whiteSmoke,
|
||||||
|
whiteSnow: whiteSnow ?? this.whiteSnow,
|
||||||
|
white: white ?? this.white,
|
||||||
|
blueAlice: blueAlice ?? this.blueAlice,
|
||||||
|
accentBlue: accentBlue ?? this.accentBlue,
|
||||||
|
accentRed: accentRed ?? this.accentRed,
|
||||||
|
accentGreen: accentGreen ?? this.accentGreen,
|
||||||
|
borderTop: borderTop ?? this.borderTop,
|
||||||
|
borderBottom: borderBottom ?? this.borderBottom,
|
||||||
|
shadowIconButton: shadowIconButton ?? this.shadowIconButton,
|
||||||
|
modalShadow: modalShadow ?? this.modalShadow,
|
||||||
|
highlight: highlight ?? this.highlight,
|
||||||
|
overlay: overlay ?? this.overlay,
|
||||||
|
overlayDark: overlayDark ?? this.overlayDark,
|
||||||
|
bgGradient: bgGradient ?? this.bgGradient,
|
||||||
|
)
|
||||||
|
: ColorTheme.dark(
|
||||||
|
black: black ?? this.black,
|
||||||
|
grey: grey ?? this.grey,
|
||||||
|
greyGainsboro: greyGainsboro ?? this.greyGainsboro,
|
||||||
|
greyWhisper: greyWhisper ?? this.greyWhisper,
|
||||||
|
whiteSmoke: whiteSmoke ?? this.whiteSmoke,
|
||||||
|
whiteSnow: whiteSnow ?? this.whiteSnow,
|
||||||
|
white: white ?? this.white,
|
||||||
|
blueAlice: blueAlice ?? this.blueAlice,
|
||||||
|
accentBlue: accentBlue ?? this.accentBlue,
|
||||||
|
accentRed: accentRed ?? this.accentRed,
|
||||||
|
accentGreen: accentGreen ?? this.accentGreen,
|
||||||
|
borderTop: borderTop ?? this.borderTop,
|
||||||
|
borderBottom: borderBottom ?? this.borderBottom,
|
||||||
|
shadowIconButton: shadowIconButton ?? this.shadowIconButton,
|
||||||
|
modalShadow: modalShadow ?? this.modalShadow,
|
||||||
|
highlight: highlight ?? this.highlight,
|
||||||
|
overlay: overlay ?? this.overlay,
|
||||||
|
overlayDark: overlayDark ?? this.overlayDark,
|
||||||
|
bgGradient: bgGradient ?? this.bgGradient,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ColorTheme merge(ColorTheme other) {
|
||||||
|
if (other == null) return this;
|
||||||
|
return copyWith(
|
||||||
|
black: other.black,
|
||||||
|
grey: other.grey,
|
||||||
|
greyGainsboro: other.greyGainsboro,
|
||||||
|
greyWhisper: other.greyWhisper,
|
||||||
|
whiteSmoke: other.whiteSmoke,
|
||||||
|
whiteSnow: other.whiteSnow,
|
||||||
|
white: other.white,
|
||||||
|
blueAlice: other.blueAlice,
|
||||||
|
accentBlue: other.accentBlue,
|
||||||
|
accentRed: other.accentRed,
|
||||||
|
accentGreen: other.accentGreen,
|
||||||
|
highlight: other.highlight,
|
||||||
|
overlay: other.overlay,
|
||||||
|
overlayDark: other.overlayDark,
|
||||||
|
bgGradient: other.bgGradient,
|
||||||
|
borderTop: other.borderTop,
|
||||||
|
borderBottom: other.borderBottom,
|
||||||
|
shadowIconButton: other.shadowIconButton,
|
||||||
|
modalShadow: other.modalShadow,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Channel theme data
|
/// Channel theme data
|
||||||
class ChannelTheme {
|
class ChannelTheme {
|
||||||
/// Theme of the [ChannelHeader] widget
|
/// Theme of the [ChannelHeader] widget
|
||||||
@@ -393,9 +653,6 @@ class ChannelTheme {
|
|||||||
/// Theme of the send button in [MessageInput]
|
/// Theme of the send button in [MessageInput]
|
||||||
final ButtonThemeData messageInputButtonTheme;
|
final ButtonThemeData messageInputButtonTheme;
|
||||||
|
|
||||||
/// Gradient of [MessageInput]
|
|
||||||
final Gradient inputGradient;
|
|
||||||
|
|
||||||
/// Background color of [MessageInput]
|
/// Background color of [MessageInput]
|
||||||
final Color inputBackground;
|
final Color inputBackground;
|
||||||
|
|
||||||
@@ -404,7 +661,6 @@ class ChannelTheme {
|
|||||||
this.messageInputButtonIconTheme,
|
this.messageInputButtonIconTheme,
|
||||||
this.messageInputButtonTheme,
|
this.messageInputButtonTheme,
|
||||||
this.inputBackground,
|
this.inputBackground,
|
||||||
this.inputGradient,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Creates a copy of [ChannelTheme] with specified attributes overridden.
|
/// Creates a copy of [ChannelTheme] with specified attributes overridden.
|
||||||
@@ -412,26 +668,29 @@ class ChannelTheme {
|
|||||||
ChannelHeaderTheme channelHeaderTheme,
|
ChannelHeaderTheme channelHeaderTheme,
|
||||||
IconThemeData messageInputButtonIconTheme,
|
IconThemeData messageInputButtonIconTheme,
|
||||||
ButtonThemeData messageInputButtonTheme,
|
ButtonThemeData messageInputButtonTheme,
|
||||||
Gradient inputGradient,
|
|
||||||
Color inputBackground,
|
Color inputBackground,
|
||||||
}) =>
|
}) =>
|
||||||
ChannelTheme(
|
ChannelTheme(
|
||||||
channelHeaderTheme: channelHeaderTheme?.copyWith(
|
channelHeaderTheme: channelHeaderTheme ?? this.channelHeaderTheme,
|
||||||
title: channelHeaderTheme?.title ?? this.channelHeaderTheme.title,
|
|
||||||
lastMessageAt: channelHeaderTheme?.lastMessageAt ??
|
|
||||||
this.channelHeaderTheme.lastMessageAt,
|
|
||||||
avatarTheme: channelHeaderTheme?.avatarTheme ??
|
|
||||||
this.channelHeaderTheme.avatarTheme,
|
|
||||||
color: channelHeaderTheme?.color ?? this.channelHeaderTheme.color,
|
|
||||||
) ??
|
|
||||||
this.channelHeaderTheme,
|
|
||||||
messageInputButtonIconTheme:
|
messageInputButtonIconTheme:
|
||||||
messageInputButtonIconTheme ?? this.messageInputButtonIconTheme,
|
messageInputButtonIconTheme ?? this.messageInputButtonIconTheme,
|
||||||
messageInputButtonTheme:
|
messageInputButtonTheme:
|
||||||
messageInputButtonTheme ?? this.messageInputButtonTheme,
|
messageInputButtonTheme ?? this.messageInputButtonTheme,
|
||||||
inputGradient: inputGradient ?? this.inputGradient,
|
|
||||||
inputBackground: inputBackground ?? this.inputBackground,
|
inputBackground: inputBackground ?? this.inputBackground,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
ChannelTheme merge(ChannelTheme other) {
|
||||||
|
if (other == null) return this;
|
||||||
|
return copyWith(
|
||||||
|
channelHeaderTheme: channelHeaderTheme?.merge(other.channelHeaderTheme) ??
|
||||||
|
other.channelHeaderTheme,
|
||||||
|
messageInputButtonIconTheme: messageInputButtonIconTheme
|
||||||
|
?.merge(other.messageInputButtonIconTheme) ??
|
||||||
|
other.messageInputButtonIconTheme,
|
||||||
|
messageInputButtonTheme: other.messageInputButtonTheme,
|
||||||
|
inputBackground: other.inputBackground,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class AvatarTheme {
|
class AvatarTheme {
|
||||||
@@ -451,6 +710,14 @@ class AvatarTheme {
|
|||||||
constraints: constraints ?? this.constraints,
|
constraints: constraints ?? this.constraints,
|
||||||
borderRadius: borderRadius ?? this.borderRadius,
|
borderRadius: borderRadius ?? this.borderRadius,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
AvatarTheme merge(AvatarTheme other) {
|
||||||
|
if (other == null) return this;
|
||||||
|
return copyWith(
|
||||||
|
constraints: other.constraints,
|
||||||
|
borderRadius: other.borderRadius,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class MessageTheme {
|
class MessageTheme {
|
||||||
@@ -460,9 +727,9 @@ class MessageTheme {
|
|||||||
final TextStyle createdAt;
|
final TextStyle createdAt;
|
||||||
final TextStyle replies;
|
final TextStyle replies;
|
||||||
final Color messageBackgroundColor;
|
final Color messageBackgroundColor;
|
||||||
|
final Color messageBorderColor;
|
||||||
final Color reactionsBackgroundColor;
|
final Color reactionsBackgroundColor;
|
||||||
final Color reactionsBorderColor;
|
final Color reactionsBorderColor;
|
||||||
final Color replyThreadColor;
|
|
||||||
final AvatarTheme avatarTheme;
|
final AvatarTheme avatarTheme;
|
||||||
|
|
||||||
const MessageTheme({
|
const MessageTheme({
|
||||||
@@ -471,9 +738,9 @@ class MessageTheme {
|
|||||||
this.messageAuthor,
|
this.messageAuthor,
|
||||||
this.messageLinks,
|
this.messageLinks,
|
||||||
this.messageBackgroundColor,
|
this.messageBackgroundColor,
|
||||||
|
this.messageBorderColor,
|
||||||
this.reactionsBackgroundColor,
|
this.reactionsBackgroundColor,
|
||||||
this.reactionsBorderColor,
|
this.reactionsBorderColor,
|
||||||
this.replyThreadColor,
|
|
||||||
this.avatarTheme,
|
this.avatarTheme,
|
||||||
this.createdAt,
|
this.createdAt,
|
||||||
});
|
});
|
||||||
@@ -485,10 +752,10 @@ class MessageTheme {
|
|||||||
TextStyle createdAt,
|
TextStyle createdAt,
|
||||||
TextStyle replies,
|
TextStyle replies,
|
||||||
Color messageBackgroundColor,
|
Color messageBackgroundColor,
|
||||||
|
Color messageBorderColor,
|
||||||
AvatarTheme avatarTheme,
|
AvatarTheme avatarTheme,
|
||||||
Color reactionsBackgroundColor,
|
Color reactionsBackgroundColor,
|
||||||
Color reactionsBorderColor,
|
Color reactionsBorderColor,
|
||||||
Color replyThreadColor,
|
|
||||||
}) =>
|
}) =>
|
||||||
MessageTheme(
|
MessageTheme(
|
||||||
messageText: messageText ?? this.messageText,
|
messageText: messageText ?? this.messageText,
|
||||||
@@ -497,13 +764,31 @@ class MessageTheme {
|
|||||||
createdAt: createdAt ?? this.createdAt,
|
createdAt: createdAt ?? this.createdAt,
|
||||||
messageBackgroundColor:
|
messageBackgroundColor:
|
||||||
messageBackgroundColor ?? this.messageBackgroundColor,
|
messageBackgroundColor ?? this.messageBackgroundColor,
|
||||||
|
messageBorderColor: messageBorderColor ?? this.messageBorderColor,
|
||||||
avatarTheme: avatarTheme ?? this.avatarTheme,
|
avatarTheme: avatarTheme ?? this.avatarTheme,
|
||||||
replies: replies ?? this.replies,
|
replies: replies ?? this.replies,
|
||||||
reactionsBackgroundColor:
|
reactionsBackgroundColor:
|
||||||
reactionsBackgroundColor ?? this.reactionsBackgroundColor,
|
reactionsBackgroundColor ?? this.reactionsBackgroundColor,
|
||||||
reactionsBorderColor: reactionsBorderColor ?? this.reactionsBorderColor,
|
reactionsBorderColor: reactionsBorderColor ?? this.reactionsBorderColor,
|
||||||
replyThreadColor: replyThreadColor ?? this.replyThreadColor,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
MessageTheme merge(MessageTheme other) {
|
||||||
|
if (other == null) return this;
|
||||||
|
return copyWith(
|
||||||
|
messageText: messageText?.merge(other.messageText) ?? other.messageText,
|
||||||
|
messageAuthor:
|
||||||
|
messageAuthor?.merge(other.messageAuthor) ?? other.messageAuthor,
|
||||||
|
messageLinks:
|
||||||
|
messageLinks?.merge(other.messageLinks) ?? other.messageLinks,
|
||||||
|
createdAt: createdAt?.merge(other.createdAt) ?? other.createdAt,
|
||||||
|
replies: replies?.merge(other.replies) ?? other.replies,
|
||||||
|
messageBackgroundColor: other.messageBackgroundColor,
|
||||||
|
messageBorderColor: other.messageBorderColor,
|
||||||
|
avatarTheme: avatarTheme?.merge(other.avatarTheme) ?? other.avatarTheme,
|
||||||
|
reactionsBackgroundColor: other.reactionsBackgroundColor,
|
||||||
|
reactionsBorderColor: other.reactionsBorderColor,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ChannelPreviewTheme {
|
class ChannelPreviewTheme {
|
||||||
@@ -512,6 +797,7 @@ class ChannelPreviewTheme {
|
|||||||
final TextStyle lastMessageAt;
|
final TextStyle lastMessageAt;
|
||||||
final AvatarTheme avatarTheme;
|
final AvatarTheme avatarTheme;
|
||||||
final Color unreadCounterColor;
|
final Color unreadCounterColor;
|
||||||
|
final double indicatorIconSize;
|
||||||
|
|
||||||
const ChannelPreviewTheme({
|
const ChannelPreviewTheme({
|
||||||
this.title,
|
this.title,
|
||||||
@@ -519,6 +805,7 @@ class ChannelPreviewTheme {
|
|||||||
this.lastMessageAt,
|
this.lastMessageAt,
|
||||||
this.avatarTheme,
|
this.avatarTheme,
|
||||||
this.unreadCounterColor,
|
this.unreadCounterColor,
|
||||||
|
this.indicatorIconSize,
|
||||||
});
|
});
|
||||||
|
|
||||||
ChannelPreviewTheme copyWith({
|
ChannelPreviewTheme copyWith({
|
||||||
@@ -527,6 +814,7 @@ class ChannelPreviewTheme {
|
|||||||
TextStyle lastMessageAt,
|
TextStyle lastMessageAt,
|
||||||
AvatarTheme avatarTheme,
|
AvatarTheme avatarTheme,
|
||||||
Color unreadCounterColor,
|
Color unreadCounterColor,
|
||||||
|
double indicatorIconSize,
|
||||||
}) =>
|
}) =>
|
||||||
ChannelPreviewTheme(
|
ChannelPreviewTheme(
|
||||||
title: title ?? this.title,
|
title: title ?? this.title,
|
||||||
@@ -534,7 +822,20 @@ class ChannelPreviewTheme {
|
|||||||
lastMessageAt: lastMessageAt ?? this.lastMessageAt,
|
lastMessageAt: lastMessageAt ?? this.lastMessageAt,
|
||||||
avatarTheme: avatarTheme ?? this.avatarTheme,
|
avatarTheme: avatarTheme ?? this.avatarTheme,
|
||||||
unreadCounterColor: unreadCounterColor ?? this.unreadCounterColor,
|
unreadCounterColor: unreadCounterColor ?? this.unreadCounterColor,
|
||||||
|
indicatorIconSize: indicatorIconSize ?? this.indicatorIconSize,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
ChannelPreviewTheme merge(ChannelPreviewTheme other) {
|
||||||
|
if (other == null) return this;
|
||||||
|
return copyWith(
|
||||||
|
title: title?.merge(other.title) ?? other.title,
|
||||||
|
subtitle: subtitle?.merge(other.subtitle) ?? other.subtitle,
|
||||||
|
lastMessageAt:
|
||||||
|
lastMessageAt?.merge(other.lastMessageAt) ?? other.lastMessageAt,
|
||||||
|
avatarTheme: avatarTheme?.merge(other.avatarTheme) ?? other.avatarTheme,
|
||||||
|
unreadCounterColor: other.unreadCounterColor,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ChannelHeaderTheme {
|
class ChannelHeaderTheme {
|
||||||
@@ -562,4 +863,46 @@ class ChannelHeaderTheme {
|
|||||||
avatarTheme: avatarTheme ?? this.avatarTheme,
|
avatarTheme: avatarTheme ?? this.avatarTheme,
|
||||||
color: color ?? this.color,
|
color: color ?? this.color,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
ChannelHeaderTheme merge(ChannelHeaderTheme other) {
|
||||||
|
if (other == null) return this;
|
||||||
|
return copyWith(
|
||||||
|
title: title?.merge(other.title) ?? other.title,
|
||||||
|
lastMessageAt:
|
||||||
|
lastMessageAt?.merge(other.lastMessageAt) ?? other.lastMessageAt,
|
||||||
|
avatarTheme: avatarTheme?.merge(other.avatarTheme) ?? other.avatarTheme,
|
||||||
|
color: other.color,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Effect {
|
||||||
|
final double sigmaX;
|
||||||
|
final double sigmaY;
|
||||||
|
final Color color;
|
||||||
|
final double alpha;
|
||||||
|
final double blur;
|
||||||
|
|
||||||
|
const Effect({
|
||||||
|
this.sigmaX,
|
||||||
|
this.sigmaY,
|
||||||
|
this.color,
|
||||||
|
this.alpha,
|
||||||
|
this.blur,
|
||||||
|
});
|
||||||
|
|
||||||
|
Effect copyWith({
|
||||||
|
double sigmaX,
|
||||||
|
double sigmaY,
|
||||||
|
Color color,
|
||||||
|
double alpha,
|
||||||
|
double blur,
|
||||||
|
}) =>
|
||||||
|
Effect(
|
||||||
|
sigmaX: sigmaX ?? this.sigmaX,
|
||||||
|
sigmaY: sigmaY ?? this.sigmaY,
|
||||||
|
color: color ?? this.color,
|
||||||
|
alpha: color ?? this.alpha,
|
||||||
|
blur: blur ?? this.blur,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ class StreamSvgIcon extends StatelessWidget {
|
|||||||
|
|
||||||
const StreamSvgIcon({
|
const StreamSvgIcon({
|
||||||
this.assetName,
|
this.assetName,
|
||||||
this.width,
|
|
||||||
this.height,
|
|
||||||
this.color,
|
this.color,
|
||||||
|
this.width = 24,
|
||||||
|
this.height = 24,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -194,6 +194,30 @@ class StreamSvgIcon extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
factory StreamSvgIcon.checkAll({
|
||||||
|
double size,
|
||||||
|
Color color,
|
||||||
|
}) {
|
||||||
|
return StreamSvgIcon(
|
||||||
|
assetName: 'Icon_check_all.svg',
|
||||||
|
color: color,
|
||||||
|
width: size,
|
||||||
|
height: size,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
factory StreamSvgIcon.checkSend({
|
||||||
|
double size,
|
||||||
|
Color color,
|
||||||
|
}) {
|
||||||
|
return StreamSvgIcon(
|
||||||
|
assetName: 'Icon_check_send.svg',
|
||||||
|
color: color,
|
||||||
|
width: size,
|
||||||
|
height: size,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
factory StreamSvgIcon.penWrite({
|
factory StreamSvgIcon.penWrite({
|
||||||
double size,
|
double size,
|
||||||
Color color,
|
Color color,
|
||||||
@@ -326,6 +350,18 @@ class StreamSvgIcon extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
factory StreamSvgIcon.reply({
|
||||||
|
double size,
|
||||||
|
Color color,
|
||||||
|
}) {
|
||||||
|
return StreamSvgIcon(
|
||||||
|
assetName: 'Icon_curve_line_left_up_big.svg',
|
||||||
|
color: color,
|
||||||
|
width: size,
|
||||||
|
height: size,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
factory StreamSvgIcon.edit({
|
factory StreamSvgIcon.edit({
|
||||||
double size,
|
double size,
|
||||||
Color color,
|
Color color,
|
||||||
@@ -757,4 +793,40 @@ class StreamSvgIcon extends StatelessWidget {
|
|||||||
height: size,
|
height: size,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
factory StreamSvgIcon.error({
|
||||||
|
double size,
|
||||||
|
Color color,
|
||||||
|
}) {
|
||||||
|
return StreamSvgIcon(
|
||||||
|
assetName: 'Icon_error.svg',
|
||||||
|
color: color,
|
||||||
|
width: size,
|
||||||
|
height: size,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
factory StreamSvgIcon.circle_up({
|
||||||
|
double size,
|
||||||
|
Color color,
|
||||||
|
}) {
|
||||||
|
return StreamSvgIcon(
|
||||||
|
assetName: 'Icon_circle_up.svg',
|
||||||
|
color: color,
|
||||||
|
width: size,
|
||||||
|
height: size,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
factory StreamSvgIcon.Icon_user_settings({
|
||||||
|
double size,
|
||||||
|
Color color,
|
||||||
|
}) {
|
||||||
|
return StreamSvgIcon(
|
||||||
|
assetName: 'Icon_user_settings.svg',
|
||||||
|
color: color,
|
||||||
|
width: size,
|
||||||
|
height: size,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,163 @@
|
|||||||
|
import 'dart:math' as math;
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'stream_chat_theme.dart';
|
||||||
|
|
||||||
|
///
|
||||||
|
class Swipeable extends StatefulWidget {
|
||||||
|
final Widget child;
|
||||||
|
final Widget backgroundIcon;
|
||||||
|
final VoidCallback onSwipeStart;
|
||||||
|
final VoidCallback onSwipeCancel;
|
||||||
|
final VoidCallback onSwipeEnd;
|
||||||
|
final double threshold;
|
||||||
|
|
||||||
|
///
|
||||||
|
const Swipeable({
|
||||||
|
@required this.child,
|
||||||
|
@required this.backgroundIcon,
|
||||||
|
this.onSwipeStart,
|
||||||
|
this.onSwipeCancel,
|
||||||
|
this.onSwipeEnd,
|
||||||
|
this.threshold = 82.0,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<StatefulWidget> createState() => _SwipeableState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SwipeableState extends State<Swipeable> with TickerProviderStateMixin {
|
||||||
|
double _dragExtent = 0.0;
|
||||||
|
AnimationController _moveController;
|
||||||
|
AnimationController _iconMoveController;
|
||||||
|
Animation<Offset> _moveAnimation;
|
||||||
|
Animation<Offset> _iconTransitionAnimation;
|
||||||
|
Animation<double> _iconFadeAnimation;
|
||||||
|
bool _pastThreshold = false;
|
||||||
|
|
||||||
|
final _animationDuration = const Duration(milliseconds: 200);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_moveController =
|
||||||
|
AnimationController(duration: _animationDuration, vsync: this);
|
||||||
|
_iconMoveController =
|
||||||
|
AnimationController(duration: _animationDuration, vsync: this);
|
||||||
|
_moveAnimation = Tween<Offset>(begin: Offset.zero, end: Offset(1.0, 0.0))
|
||||||
|
.animate(_moveController);
|
||||||
|
_iconTransitionAnimation =
|
||||||
|
Tween<Offset>(begin: Offset(-0.1, 0.0), end: Offset(0.4, 0.0))
|
||||||
|
.animate(_moveController);
|
||||||
|
_iconFadeAnimation =
|
||||||
|
Tween<double>(begin: 0.7, end: 1.0).animate(_iconMoveController);
|
||||||
|
|
||||||
|
final controllerValue = 0.0;
|
||||||
|
_moveController.animateTo(controllerValue);
|
||||||
|
_iconMoveController.animateTo(controllerValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_moveController.dispose();
|
||||||
|
_iconMoveController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleDragStart(DragStartDetails details) {
|
||||||
|
if (widget.onSwipeStart != null) {
|
||||||
|
widget.onSwipeStart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleDragUpdate(DragUpdateDetails details) {
|
||||||
|
final delta = details.primaryDelta;
|
||||||
|
_dragExtent += delta;
|
||||||
|
|
||||||
|
if (_dragExtent.isNegative) return;
|
||||||
|
|
||||||
|
var movePastThresholdPixels = widget.threshold;
|
||||||
|
var newPos = _dragExtent.abs() / context.size.width;
|
||||||
|
|
||||||
|
if (_dragExtent.abs() > movePastThresholdPixels) {
|
||||||
|
// how many "thresholds" past the threshold we are. 1 = the threshold 2
|
||||||
|
// = two thresholds.
|
||||||
|
var n = _dragExtent.abs() / movePastThresholdPixels;
|
||||||
|
|
||||||
|
// Take the number of thresholds past the threshold, and reduce this
|
||||||
|
// number
|
||||||
|
var reducedThreshold = math.pow(n, 0.3);
|
||||||
|
|
||||||
|
var adjustedPixelPos = movePastThresholdPixels * reducedThreshold;
|
||||||
|
newPos = adjustedPixelPos / context.size.width;
|
||||||
|
|
||||||
|
if (_dragExtent > 0 && !_pastThreshold) {
|
||||||
|
_iconMoveController.value = 1;
|
||||||
|
_pastThreshold = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Send a cancel event if the user has swiped back underneath the
|
||||||
|
// threshold
|
||||||
|
if (_pastThreshold && widget.onSwipeCancel != null) {
|
||||||
|
widget.onSwipeCancel();
|
||||||
|
}
|
||||||
|
_pastThreshold = false;
|
||||||
|
}
|
||||||
|
if (!_pastThreshold || newPos < _moveController.value) {
|
||||||
|
_iconMoveController.value = newPos;
|
||||||
|
}
|
||||||
|
_moveController.value = newPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleDragEnd(DragEndDetails details) {
|
||||||
|
_moveController.animateTo(0.0, duration: _animationDuration);
|
||||||
|
_iconMoveController.animateTo(0.0, duration: _animationDuration);
|
||||||
|
_dragExtent = 0.0;
|
||||||
|
if (_pastThreshold && widget.onSwipeEnd != null) {
|
||||||
|
widget.onSwipeEnd();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return GestureDetector(
|
||||||
|
onHorizontalDragStart: _handleDragStart,
|
||||||
|
onHorizontalDragUpdate: _handleDragUpdate,
|
||||||
|
onHorizontalDragEnd: _handleDragEnd,
|
||||||
|
behavior: HitTestBehavior.opaque,
|
||||||
|
child: Stack(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
fit: StackFit.passthrough,
|
||||||
|
children: [
|
||||||
|
SlideTransition(
|
||||||
|
position: _iconTransitionAnimation,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
FadeTransition(
|
||||||
|
opacity: _iconFadeAnimation,
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(4),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
border: Border.all(
|
||||||
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.greyGainsboro,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: widget.backgroundIcon,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SlideTransition(
|
||||||
|
position: _moveAnimation,
|
||||||
|
child: widget.child,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -51,9 +51,13 @@ class UrlAttachment extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
hostDisplayName,
|
hostDisplayName,
|
||||||
style: TextStyle(
|
style: StreamChatTheme.of(context)
|
||||||
fontWeight: FontWeight.w700,
|
.textTheme
|
||||||
color: Color(0xFF006CFF),
|
.bodyBold
|
||||||
|
.copyWith(
|
||||||
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.accentBlue,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -61,7 +65,7 @@ class UrlAttachment extends StatelessWidget {
|
|||||||
borderRadius: BorderRadius.only(
|
borderRadius: BorderRadius.only(
|
||||||
topRight: Radius.circular(16.0),
|
topRight: Radius.circular(16.0),
|
||||||
),
|
),
|
||||||
color: Color(0xFFE9F2FF),
|
color: StreamChatTheme.of(context).colorTheme.blueAlice,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -81,18 +85,18 @@ class UrlAttachment extends StatelessWidget {
|
|||||||
urlAttachment.title.trim(),
|
urlAttachment.title.trim(),
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
style: TextStyle(
|
style: StreamChatTheme.of(context)
|
||||||
fontWeight: FontWeight.w700,
|
.textTheme
|
||||||
fontSize: 12.0,
|
.body
|
||||||
),
|
.copyWith(fontWeight: FontWeight.w700),
|
||||||
),
|
),
|
||||||
if (urlAttachment.text != null)
|
if (urlAttachment.text != null)
|
||||||
Text(
|
Text(
|
||||||
urlAttachment.text,
|
urlAttachment.text,
|
||||||
style: TextStyle(
|
style: StreamChatTheme.of(context)
|
||||||
fontWeight: FontWeight.w400,
|
.textTheme
|
||||||
fontSize: 12.0,
|
.body
|
||||||
),
|
.copyWith(fontWeight: FontWeight.w400),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ class UserAvatar extends StatelessWidget {
|
|||||||
this.borderRadius,
|
this.borderRadius,
|
||||||
this.onlineIndicatorAlignment = Alignment.topRight,
|
this.onlineIndicatorAlignment = Alignment.topRight,
|
||||||
this.selected = false,
|
this.selected = false,
|
||||||
this.selectionColor = const Color(0xFF006CFF),
|
this.selectionColor,
|
||||||
this.selectionThickness = 4,
|
this.selectionThickness = 4,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@@ -47,7 +47,7 @@ class UserAvatar extends StatelessWidget {
|
|||||||
constraints: constraints ??
|
constraints: constraints ??
|
||||||
streamChatTheme.ownMessageTheme.avatarTheme.constraints,
|
streamChatTheme.ownMessageTheme.avatarTheme.constraints,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: streamChatTheme.accentColor,
|
color: streamChatTheme.colorTheme.accentBlue,
|
||||||
),
|
),
|
||||||
child: hasImage
|
child: hasImage
|
||||||
? CachedNetworkImage(
|
? CachedNetworkImage(
|
||||||
@@ -61,13 +61,17 @@ class UserAvatar extends StatelessWidget {
|
|||||||
: streamChatTheme.defaultUserImage(context, user),
|
: streamChatTheme.defaultUserImage(context, user),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (selected) {
|
if (selected) {
|
||||||
avatar = ClipRRect(
|
avatar = ClipRRect(
|
||||||
borderRadius: (borderRadius ??
|
borderRadius: (borderRadius ??
|
||||||
streamChatTheme.ownMessageTheme.avatarTheme.borderRadius) +
|
streamChatTheme.ownMessageTheme.avatarTheme.borderRadius) +
|
||||||
BorderRadius.circular(selectionThickness),
|
BorderRadius.circular(selectionThickness),
|
||||||
child: Container(
|
child: Container(
|
||||||
color: selectionColor,
|
constraints: constraints ??
|
||||||
|
streamChatTheme.ownMessageTheme.avatarTheme.constraints,
|
||||||
|
color: selectionColor ??
|
||||||
|
StreamChatTheme.of(context).colorTheme.accentBlue,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.all(selectionThickness),
|
padding: EdgeInsets.all(selectionThickness),
|
||||||
child: avatar,
|
child: avatar,
|
||||||
@@ -96,10 +100,10 @@ class UserAvatar extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
child: Material(
|
child: Material(
|
||||||
shape: CircleBorder(),
|
shape: CircleBorder(),
|
||||||
color: Color(0xff20E070),
|
color: streamChatTheme.colorTheme.accentGreen,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
color: Colors.white,
|
color: StreamChatTheme.of(context).colorTheme.white,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -73,25 +73,25 @@ class UserItem extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
trailing: selected
|
trailing: selected
|
||||||
? CircleAvatar(
|
? StreamSvgIcon.checkSend(
|
||||||
child: StreamSvgIcon.check(
|
color: StreamChatTheme.of(context).colorTheme.accentBlue,
|
||||||
size: 20,
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
radius: 10,
|
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
title: Text(
|
title: Text(
|
||||||
user.name,
|
user.name,
|
||||||
style: TextStyle(fontWeight: FontWeight.bold),
|
style: StreamChatTheme.of(context).textTheme.bodyBold,
|
||||||
),
|
),
|
||||||
subtitle: showLastOnline ? _buildLastActive(context) : null,
|
subtitle: showLastOnline ? _buildLastActive(context) : null,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildLastActive(context) {
|
Widget _buildLastActive(context) {
|
||||||
return user.online == true
|
return Text(
|
||||||
? Text('Online')
|
user.online == true
|
||||||
: Text('Last online ${Jiffy(user.lastActive).fromNow()}');
|
? 'Online'
|
||||||
|
: 'Last online ${Jiffy(user.lastActive).fromNow()}',
|
||||||
|
style: StreamChatTheme.of(context).textTheme.footnote.copyWith(
|
||||||
|
color: StreamChatTheme.of(context).colorTheme.black.withOpacity(.5)),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -356,15 +356,17 @@ class _UserListViewState extends State<UserListView>
|
|||||||
headerItem: (header) {
|
headerItem: (header) {
|
||||||
return Container(
|
return Container(
|
||||||
key: ValueKey<String>('HEADER-$header'),
|
key: ValueKey<String>('HEADER-$header'),
|
||||||
color: Colors.black.withOpacity(0.05),
|
color:
|
||||||
|
StreamChatTheme.of(context).colorTheme.black.withOpacity(0.05),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 6),
|
padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 6),
|
||||||
child: Text(
|
child: Text(
|
||||||
header,
|
header,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
fontSize: 12,
|
fontSize: 14.5,
|
||||||
color: Colors.black.withOpacity(0.3)),
|
color: StreamChatTheme.of(context).colorTheme.grey,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -449,7 +451,10 @@ class _UserListViewState extends State<UserListView>
|
|||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (snapshot.hasError) {
|
if (snapshot.hasError) {
|
||||||
return Container(
|
return Container(
|
||||||
color: Color(0xffd0021B).withAlpha(26),
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.accentRed
|
||||||
|
.withOpacity(.2),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 16.0),
|
padding: const EdgeInsets.symmetric(vertical: 16.0),
|
||||||
child: Center(
|
child: Center(
|
||||||
@@ -471,9 +476,7 @@ class _UserListViewState extends State<UserListView>
|
|||||||
Widget _separatorBuilder(context, i) {
|
Widget _separatorBuilder(context, i) {
|
||||||
return Container(
|
return Container(
|
||||||
height: 1,
|
height: 1,
|
||||||
color: Theme.of(context).brightness == Brightness.dark
|
color: StreamChatTheme.of(context).colorTheme.greyWhisper,
|
||||||
? Colors.white.withOpacity(0.1)
|
|
||||||
: Colors.black.withOpacity(0.1),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:stream_chat/stream_chat.dart';
|
import 'package:stream_chat/stream_chat.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
|
import 'stream_svg_icon.dart';
|
||||||
|
|
||||||
import '../stream_chat_flutter.dart';
|
import '../stream_chat_flutter.dart';
|
||||||
|
|
||||||
Future<void> launchURL(BuildContext context, String url) async {
|
Future<void> launchURL(BuildContext context, String url) async {
|
||||||
@@ -25,7 +27,7 @@ Future<bool> showConfirmationDialog(
|
|||||||
String cancelText,
|
String cancelText,
|
||||||
}) {
|
}) {
|
||||||
return showModalBottomSheet(
|
return showModalBottomSheet(
|
||||||
backgroundColor: Colors.white,
|
backgroundColor: StreamChatTheme.of(context).colorTheme.white,
|
||||||
context: context,
|
context: context,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.only(
|
borderRadius: BorderRadius.only(
|
||||||
@@ -45,7 +47,7 @@ Future<bool> showConfirmationDialog(
|
|||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
title,
|
title,
|
||||||
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16.0),
|
style: StreamChatTheme.of(context).textTheme.headlineBold,
|
||||||
),
|
),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: 7.0,
|
height: 7.0,
|
||||||
@@ -55,7 +57,8 @@ Future<bool> showConfirmationDialog(
|
|||||||
height: 36.0,
|
height: 36.0,
|
||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
color: Color(0xffe6e6e6),
|
color:
|
||||||
|
StreamChatTheme.of(context).colorTheme.black.withOpacity(.08),
|
||||||
height: 1.0,
|
height: 1.0,
|
||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
@@ -65,7 +68,10 @@ Future<bool> showConfirmationDialog(
|
|||||||
child: Text(
|
child: Text(
|
||||||
cancelText,
|
cancelText,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.black.withOpacity(0.5),
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.black
|
||||||
|
.withOpacity(0.5),
|
||||||
fontWeight: FontWeight.w400),
|
fontWeight: FontWeight.w400),
|
||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
@@ -93,11 +99,119 @@ Future<bool> showConfirmationDialog(
|
|||||||
String getRandomPicUrl(User user) =>
|
String getRandomPicUrl(User user) =>
|
||||||
'https://getstream.io/random_png/?id=${user.id}&name=${user.name}';
|
'https://getstream.io/random_png/?id=${user.id}&name=${user.name}';
|
||||||
|
|
||||||
/// List extension
|
/// Get websiteName from [hostName]
|
||||||
extension ListX<T> on List<T> {
|
String getWebsiteName(String hostName) {
|
||||||
/// Insert any item<T> inBetween the list items
|
switch (hostName) {
|
||||||
List<T> insertBetween(T item) => expand((e) sync* {
|
case 'reddit':
|
||||||
yield item;
|
return 'Reddit';
|
||||||
yield e;
|
case 'youtube':
|
||||||
}).skip(1).toList(growable: false);
|
return 'Youtube';
|
||||||
|
case 'wikipedia':
|
||||||
|
return 'Wikipedia';
|
||||||
|
case 'twitter':
|
||||||
|
return 'Twitter';
|
||||||
|
case 'facebook':
|
||||||
|
return 'Facebook';
|
||||||
|
case 'amazon':
|
||||||
|
return 'Amazon';
|
||||||
|
case 'yelp':
|
||||||
|
return 'Yelp';
|
||||||
|
case 'imdb':
|
||||||
|
return 'IMDB';
|
||||||
|
case 'pinterest':
|
||||||
|
return 'Pinterest';
|
||||||
|
case 'tripadvisor':
|
||||||
|
return 'TripAdvisor';
|
||||||
|
case 'instagram':
|
||||||
|
return 'Instagram';
|
||||||
|
case 'walmart':
|
||||||
|
return 'Walmart';
|
||||||
|
case 'craigslist':
|
||||||
|
return 'Craigslist';
|
||||||
|
case 'ebay':
|
||||||
|
return 'eBay';
|
||||||
|
case 'linkedin':
|
||||||
|
return 'LinkedIn';
|
||||||
|
case 'google':
|
||||||
|
return 'Google';
|
||||||
|
case 'apple':
|
||||||
|
return 'Apple';
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
String getSizeText(int bytes) {
|
||||||
|
if (bytes == null) {
|
||||||
|
return 'Size N/A';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bytes <= 1000) {
|
||||||
|
return '${bytes} bytes';
|
||||||
|
} else if (bytes <= 100000) {
|
||||||
|
return '${(bytes / 1000).toStringAsFixed(2)} KB';
|
||||||
|
} else {
|
||||||
|
return '${(bytes / 1000000).toStringAsFixed(2)} MB';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
StreamSvgIcon getFileTypeImage(String type) {
|
||||||
|
switch (type) {
|
||||||
|
case '7z':
|
||||||
|
return StreamSvgIcon.filetype_7z();
|
||||||
|
break;
|
||||||
|
case 'csv':
|
||||||
|
return StreamSvgIcon.filetype_csv();
|
||||||
|
break;
|
||||||
|
case 'doc':
|
||||||
|
return StreamSvgIcon.filetype_doc();
|
||||||
|
break;
|
||||||
|
case 'docx':
|
||||||
|
return StreamSvgIcon.filetype_docx();
|
||||||
|
break;
|
||||||
|
case 'html':
|
||||||
|
return StreamSvgIcon.filetype_html();
|
||||||
|
break;
|
||||||
|
case 'md':
|
||||||
|
return StreamSvgIcon.filetype_md();
|
||||||
|
break;
|
||||||
|
case 'odt':
|
||||||
|
return StreamSvgIcon.filetype_odt();
|
||||||
|
break;
|
||||||
|
case 'pdf':
|
||||||
|
return StreamSvgIcon.filetype_pdf();
|
||||||
|
break;
|
||||||
|
case 'ppt':
|
||||||
|
return StreamSvgIcon.filetype_ppt();
|
||||||
|
break;
|
||||||
|
case 'pptx':
|
||||||
|
return StreamSvgIcon.filetype_pptx();
|
||||||
|
break;
|
||||||
|
case 'rar':
|
||||||
|
return StreamSvgIcon.filetype_rar();
|
||||||
|
break;
|
||||||
|
case 'rtf':
|
||||||
|
return StreamSvgIcon.filetype_rtf();
|
||||||
|
break;
|
||||||
|
case 'tar':
|
||||||
|
return StreamSvgIcon.filetype_tar();
|
||||||
|
break;
|
||||||
|
case 'txt':
|
||||||
|
return StreamSvgIcon.filetype_txt();
|
||||||
|
break;
|
||||||
|
case 'xls':
|
||||||
|
return StreamSvgIcon.filetype_xls();
|
||||||
|
break;
|
||||||
|
case 'xlsx':
|
||||||
|
return StreamSvgIcon.filetype_xlsx();
|
||||||
|
break;
|
||||||
|
case 'zip':
|
||||||
|
return StreamSvgIcon.filetype_zip();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return StreamSvgIcon.filetype_Generic();
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ class VideoAttachment extends StatefulWidget {
|
|||||||
final MessageTheme messageTheme;
|
final MessageTheme messageTheme;
|
||||||
final Size size;
|
final Size size;
|
||||||
final Message message;
|
final Message message;
|
||||||
|
final ShowMessageCallback onShowMessage;
|
||||||
|
|
||||||
VideoAttachment({
|
VideoAttachment({
|
||||||
Key key,
|
Key key,
|
||||||
@@ -21,6 +22,7 @@ class VideoAttachment extends StatefulWidget {
|
|||||||
@required this.messageTheme,
|
@required this.messageTheme,
|
||||||
this.message,
|
this.message,
|
||||||
this.size,
|
this.size,
|
||||||
|
this.onShowMessage,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -95,6 +97,7 @@ class _VideoAttachmentState extends State<VideoAttachment> {
|
|||||||
userName: widget.message.user.name,
|
userName: widget.message.user.name,
|
||||||
sentAt: widget.message.createdAt,
|
sentAt: widget.message.createdAt,
|
||||||
message: widget.message,
|
message: widget.message,
|
||||||
|
onShowMessage: widget.onShowMessage,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -43,3 +43,6 @@ export 'src/message_search_bloc.dart';
|
|||||||
export 'src/message_search_item.dart';
|
export 'src/message_search_item.dart';
|
||||||
export 'src/message_search_list_view.dart';
|
export 'src/message_search_list_view.dart';
|
||||||
export 'src/unread_indicator.dart';
|
export 'src/unread_indicator.dart';
|
||||||
|
export 'src/option_list_tile.dart';
|
||||||
|
export 'src/channel_file_display_screen.dart';
|
||||||
|
export 'src/channel_media_display_screen.dart';
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 11C14.2091 11 16 9.20914 16 7C16 4.79086 14.2091 3 12 3C9.79086 3 8 4.79086 8 7C8 9.20914 9.79086 11 12 11ZM12 9C13.1046 9 14 8.10457 14 7C14 5.89543 13.1046 5 12 5C10.8954 5 10 5.89543 10 7C10 8.10457 10.8954 9 12 9Z" fill="#7A7A7A"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 12C6.4686 12 4 15.6324 4 18C4 18.5523 3.55228 19 3 19C2.44771 19 2 18.5523 2 18C2 14.3676 5.5314 10 12 10C13.9955 10 15.7139 10.4121 17.1402 11.0991C17.6378 11.3387 17.8469 11.9363 17.6072 12.4339C17.3676 12.9315 16.7699 13.1406 16.2724 12.9009C15.1351 12.3532 13.716 12 12 12Z" fill="#7A7A7A"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M18.5 14C18.2239 14 18 14.2239 18 14.5V15.2679L17.3349 14.884C17.0958 14.7459 16.79 14.8278 16.6519 15.067L16.1519 15.933C16.0139 16.1722 16.0958 16.478 16.3349 16.616L17 17L16.3349 17.384C16.0958 17.522 16.0138 17.8278 16.1519 18.067L16.6519 18.933C16.79 19.1722 17.0958 19.2541 17.3349 19.116L18 18.732V19.5C18 19.7761 18.2239 20 18.5 20H19.5C19.7761 20 20 19.7761 20 19.5V18.732L20.6651 19.116C20.9042 19.2541 21.21 19.1722 21.3481 18.933L21.8481 18.067C21.9861 17.8278 21.9042 17.522 21.6651 17.384L21 17L21.665 16.616C21.9042 16.478 21.9861 16.1722 21.8481 15.933L21.3481 15.067C21.21 14.8278 20.9042 14.7459 20.665 14.884L20 15.2679V14.5C20 14.2239 19.7761 14 19.5 14H18.5ZM19 18C19.5523 18 20 17.5523 20 17C20 16.4477 19.5523 16 19 16C18.4477 16 18 16.4477 18 17C18 17.5523 18.4477 18 19 18Z" fill="#7A7A7A"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.6 KiB |
@@ -24,11 +24,11 @@ dependencies:
|
|||||||
url_launcher: ^5.4.11
|
url_launcher: ^5.4.11
|
||||||
emojis: ^0.9.3
|
emojis: ^0.9.3
|
||||||
video_player: ^1.0.0
|
video_player: ^1.0.0
|
||||||
chewie: ^0.10.4
|
chewie: ^0.12.1
|
||||||
file_picker: ^2.0.12
|
file_picker: ^2.0.12
|
||||||
image_picker: ^0.6.7+2
|
image_picker: ^0.6.7+2
|
||||||
flutter_keyboard_visibility: ^3.3.0
|
flutter_keyboard_visibility: ^4.0.2
|
||||||
stream_chat: ^0.2.20
|
stream_chat: ^0.2.23
|
||||||
mime: ^0.9.6+3
|
mime: ^0.9.6+3
|
||||||
video_compress: ^2.1.1
|
video_compress: ^2.1.1
|
||||||
visibility_detector: ^0.1.5
|
visibility_detector: ^0.1.5
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ void main() {
|
|||||||
showCopyMessage: false,
|
showCopyMessage: false,
|
||||||
showDeleteMessage: false,
|
showDeleteMessage: false,
|
||||||
showReply: false,
|
showReply: false,
|
||||||
|
showThreadReply: false,
|
||||||
message: Message(
|
message: Message(
|
||||||
text: 'test',
|
text: 'test',
|
||||||
user: User(
|
user: User(
|
||||||
@@ -87,6 +88,7 @@ void main() {
|
|||||||
|
|
||||||
await tester.pump(Duration(milliseconds: 1000));
|
await tester.pump(Duration(milliseconds: 1000));
|
||||||
expect(find.byKey(Key('MessageWidget')), findsOneWidget);
|
expect(find.byKey(Key('MessageWidget')), findsOneWidget);
|
||||||
|
expect(find.text('Reply'), findsNothing);
|
||||||
expect(find.text('Thread reply'), findsNothing);
|
expect(find.text('Thread reply'), findsNothing);
|
||||||
expect(find.text('Edit message'), findsNothing);
|
expect(find.text('Edit message'), findsNothing);
|
||||||
expect(find.text('Delete message'), findsNothing);
|
expect(find.text('Delete message'), findsNothing);
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ void main() {
|
|||||||
),
|
),
|
||||||
latestReactions: [
|
latestReactions: [
|
||||||
Reaction(
|
Reaction(
|
||||||
type: 'thumbs_up',
|
type: 'like',
|
||||||
user: User(id: testUserId),
|
user: User(id: testUserId),
|
||||||
),
|
),
|
||||||
Reaction(
|
Reaction(
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ void main() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
testWidgets(
|
testWidgets(
|
||||||
'it should show a thumb up',
|
'it should show a like',
|
||||||
(WidgetTester tester) async {
|
(WidgetTester tester) async {
|
||||||
final client = MockClient();
|
final client = MockClient();
|
||||||
final clientState = MockClientState();
|
final clientState = MockClientState();
|
||||||
@@ -50,7 +50,7 @@ void main() {
|
|||||||
child: ReactionBubble(
|
child: ReactionBubble(
|
||||||
reactions: [
|
reactions: [
|
||||||
Reaction(
|
Reaction(
|
||||||
type: 'thumbs_up',
|
type: 'like',
|
||||||
user: User(id: 'test'),
|
user: User(id: 'test'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -86,7 +86,7 @@ void main() {
|
|||||||
child: ReactionBubble(
|
child: ReactionBubble(
|
||||||
reactions: [
|
reactions: [
|
||||||
Reaction(
|
Reaction(
|
||||||
type: 'thumbs_up',
|
type: 'like',
|
||||||
user: User(id: 'test'),
|
user: User(id: 'test'),
|
||||||
),
|
),
|
||||||
Reaction(
|
Reaction(
|
||||||
|
|||||||