Merge branch 'develop' into v4

This commit is contained in:
Salvatore Giordano
2022-04-29 16:24:12 +02:00
80 changed files with 607 additions and 180 deletions
@@ -64,30 +64,34 @@ The second type looks like this:
We can use a `Stack` for achieving this: We can use a `Stack` for achieving this:
```dart ```dart
Scaffold( Stack(
body: Stack( children: <Widget>[
children: <Widget>[ // Add your video implementation here
// Add your video implementation here ShaderMask(
ShaderMask( shaderCallback: (rect) {
shaderCallback: (rect) { return const LinearGradient(
return LinearGradient(
begin: Alignment.bottomCenter, begin: Alignment.bottomCenter,
end: Alignment.topCenter, end: Alignment.topCenter,
colors: [Colors.black, Colors.transparent], colors: [Colors.black, Colors.transparent],
stops: [0.4, 0.65] stops: [0.4, 0.8]).createShader(
).createShader(Rect.fromLTRB(0, 0, rect.width, rect.height)); Rect.fromLTRB(0, 0, rect.width, rect.height),
}, );
blendMode: BlendMode.dstIn, },
child: Column( blendMode: BlendMode.dstIn,
children: [ child: Column(
Expanded( children: const [
child: MessageListView(), Expanded(
), child: MessageListViewTheme(
MessageInput(), data: MessageListViewThemeData(
], backgroundColor: Colors.transparent,
), ),
), child: MessageListView(),
], ),
), ),
) MessageInput(),
],
),
),
],
),
``` ```
@@ -86,6 +86,14 @@ firebaseMessaging.onTokenRefresh.listen((token) {
}); });
``` ```
Push Notifications v2 also supports specifying a name to the push device tokens you register. By setting the optional `pushProviderName` param in the `addDevice` call you can support different configurations between the device and the `PushProvider`.
```dart
firebaseMessaging.onTokenRefresh.listen((token) {
client.addDevice(token, PushProvider.firebase, pushProviderName: 'my-custom-config');
});
```
### Receiving Notifications ### Receiving Notifications
Push notifications behave a bit differently depending on whether you are using iOS or Android. Push notifications behave a bit differently depending on whether you are using iOS or Android.
+6
View File
@@ -1,3 +1,9 @@
## Upcoming
✅ Added
- Added `push_provider_name` to `addDevice` API call
## 4.0.0-beta.2 ## 4.0.0-beta.2
🐞 Fixed 🐞 Fixed
+24 -16
View File
@@ -64,7 +64,7 @@ class StreamChatClient {
StreamChatClient( StreamChatClient(
String apiKey, { String apiKey, {
this.logLevel = Level.WARNING, this.logLevel = Level.WARNING,
LogHandlerFunction? logHandlerFunction, this.logHandlerFunction = StreamChatClient.defaultLogHandler,
RetryPolicy? retryPolicy, RetryPolicy? retryPolicy,
@Deprecated(''' @Deprecated('''
Location is now deprecated in favor of the new edge server. Will be removed in v4.0.0. Location is now deprecated in favor of the new edge server. Will be removed in v4.0.0.
@@ -77,7 +77,6 @@ class StreamChatClient {
WebSocket? ws, WebSocket? ws,
AttachmentFileUploader? attachmentFileUploader, AttachmentFileUploader? attachmentFileUploader,
}) { }) {
this.logHandlerFunction = logHandlerFunction ?? _defaultLogHandler;
logger.info('Initiating new StreamChatClient'); logger.info('Initiating new StreamChatClient');
final options = StreamHttpClientOptions( final options = StreamHttpClientOptions(
@@ -134,7 +133,7 @@ class StreamChatClient {
'${CurrentPlatform.name}-' '${CurrentPlatform.name}-'
'${PACKAGE_VERSION.split('+')[0]}'; '${PACKAGE_VERSION.split('+')[0]}';
/// Additionals headers for all requests /// Additional headers for all requests
static Map<String, Object?> additionalHeaders = {}; static Map<String, Object?> additionalHeaders = {};
ChatPersistenceClient? _originalChatPersistenceClient; ChatPersistenceClient? _originalChatPersistenceClient;
@@ -189,7 +188,7 @@ class StreamChatClient {
/// final client = StreamChatClient("stream-chat-api-key", /// final client = StreamChatClient("stream-chat-api-key",
/// logHandlerFunction: myLogHandlerFunction); /// logHandlerFunction: myLogHandlerFunction);
///``` ///```
late LogHandlerFunction logHandlerFunction; final LogHandlerFunction logHandlerFunction;
StreamSubscription<ConnectionStatus>? _connectionStatusSubscription; StreamSubscription<ConnectionStatus>? _connectionStatusSubscription;
@@ -214,17 +213,18 @@ class StreamChatClient {
Stream<ConnectionStatus> get wsConnectionStatusStream => Stream<ConnectionStatus> get wsConnectionStatusStream =>
_wsConnectionStatusController.stream.distinct(); _wsConnectionStatusController.stream.distinct();
LogHandlerFunction get _defaultLogHandler => (LogRecord record) { /// Default log handler function for the [StreamChatClient] logger.
print( static void defaultLogHandler(LogRecord record) {
'${record.time} ' print(
'${_levelEmojiMapper[record.level] ?? record.level.name} ' '${record.time} '
'${record.loggerName} ${record.message} ', '${_levelEmojiMapper[record.level] ?? record.level.name} '
); '${record.loggerName} ${record.message} ',
if (record.error != null) print(record.error); );
if (record.stackTrace != null) print(record.stackTrace); if (record.error != null) print(record.error);
}; if (record.stackTrace != null) print(record.stackTrace);
}
/// /// Default logger for the [StreamChatClient].
Logger detachedLogger(String name) => Logger.detached(name) Logger detachedLogger(String name) => Logger.detached(name)
..level = logLevel ..level = logLevel
..onRecord.listen(logHandlerFunction); ..onRecord.listen(logHandlerFunction);
@@ -820,8 +820,16 @@ class StreamChatClient {
); );
/// Add a device for Push Notifications. /// Add a device for Push Notifications.
Future<EmptyResponse> addDevice(String id, PushProvider pushProvider) => Future<EmptyResponse> addDevice(
_chatApi.device.addDevice(id, pushProvider); String id,
PushProvider pushProvider, {
String? pushProviderName,
}) =>
_chatApi.device.addDevice(
id,
pushProvider,
pushProviderName: pushProviderName,
);
/// Gets a list of user devices. /// Gets a list of user devices.
Future<ListDevicesResponse> getDevices() => _chatApi.device.getDevices(); Future<ListDevicesResponse> getDevices() => _chatApi.device.getDevices();
@@ -29,13 +29,16 @@ class DeviceApi {
/// Add a device for Push Notifications. /// Add a device for Push Notifications.
Future<EmptyResponse> addDevice( Future<EmptyResponse> addDevice(
String deviceId, String deviceId,
PushProvider pushProvider, PushProvider pushProvider, {
) async { String? pushProviderName,
}) async {
final response = await _client.post( final response = await _client.post(
'/devices', '/devices',
data: { data: {
'id': deviceId, 'id': deviceId,
'push_provider': pushProvider.name, 'push_provider': pushProvider.name,
if (pushProviderName != null && pushProviderName.isNotEmpty)
'push_provider_name': pushProviderName,
}, },
); );
return EmptyResponse.fromJson(response.data); return EmptyResponse.fromJson(response.data);
+29 -1
View File
@@ -5,7 +5,7 @@ export 'package:dio/src/dio_error.dart';
export 'package:dio/src/multipart_file.dart'; export 'package:dio/src/multipart_file.dart';
export 'package:dio/src/options.dart'; export 'package:dio/src/options.dart';
export 'package:dio/src/options.dart' show ProgressCallback; export 'package:dio/src/options.dart' show ProgressCallback;
export 'package:logging/logging.dart' show Logger, Level; export 'package:logging/logging.dart' show Logger, Level, LogRecord;
export 'package:rate_limiter/rate_limiter.dart'; export 'package:rate_limiter/rate_limiter.dart';
export 'package:uuid/uuid.dart'; export 'package:uuid/uuid.dart';
@@ -41,3 +41,31 @@ export './src/permission_type.dart';
export './src/ws/connection_status.dart'; export './src/ws/connection_status.dart';
export 'src/client/channel.dart'; export 'src/client/channel.dart';
export 'src/client/client.dart'; export 'src/client/client.dart';
export 'src/core/api/attachment_file_uploader.dart' show AttachmentFileUploader;
export 'src/core/api/requests.dart';
export 'src/core/api/requests.dart';
export 'src/core/api/responses.dart';
export 'src/core/api/stream_chat_api.dart' show PushProvider;
export 'src/core/error/error.dart';
export 'src/core/models/action.dart';
export 'src/core/models/attachment.dart';
export 'src/core/models/attachment_file.dart';
export 'src/core/models/channel_config.dart';
export 'src/core/models/channel_model.dart';
export 'src/core/models/channel_state.dart';
export 'src/core/models/command.dart';
export 'src/core/models/device.dart';
export 'src/core/models/event.dart';
export 'src/core/models/filter.dart' show Filter;
export 'src/core/models/member.dart';
export 'src/core/models/message.dart';
export 'src/core/models/mute.dart';
export 'src/core/models/own_user.dart';
export 'src/core/models/reaction.dart';
export 'src/core/models/read.dart';
export 'src/core/models/user.dart';
export 'src/core/util/extension.dart';
export 'src/db/chat_persistence_client.dart';
export 'src/event_type.dart';
export 'src/location.dart';
export 'src/ws/connection_status.dart';
@@ -1171,7 +1171,7 @@ void main() {
verifyNoMoreInteractions(api.channel); verifyNoMoreInteractions(api.channel);
}); });
test('`.addDevice`', () async { test('`.addDevice should work`', () async {
const id = 'test-device-id'; const id = 'test-device-id';
const provider = PushProvider.firebase; const provider = PushProvider.firebase;
@@ -1185,6 +1185,34 @@ void main() {
verifyNoMoreInteractions(api.device); verifyNoMoreInteractions(api.device);
}); });
test('`.addDevice should work with pushProviderName`', () async {
const id = 'test-device-id';
const provider = PushProvider.firebase;
const pushProviderName = 'my-custom-config';
when(
() => api.device.addDevice(
id,
provider,
pushProviderName: pushProviderName,
),
).thenAnswer((_) async => EmptyResponse());
final res = await client.addDevice(
id,
provider,
pushProviderName: pushProviderName,
);
expect(res, isNotNull);
verify(() => api.device.addDevice(
id,
provider,
pushProviderName: pushProviderName,
)).called(1);
verifyNoMoreInteractions(api.device);
});
test('`.getDevices`', () async { test('`.getDevices`', () async {
final devices = List.generate( final devices = List.generate(
3, 3,
@@ -20,7 +20,7 @@ void main() {
deviceApi = DeviceApi(client); deviceApi = DeviceApi(client);
}); });
test('addDevice', () async { test('addDevice should work', () async {
const deviceId = 'test-device-id'; const deviceId = 'test-device-id';
const pushProvider = PushProvider.firebase; const pushProvider = PushProvider.firebase;
@@ -44,6 +44,36 @@ void main() {
verifyNoMoreInteractions(client); verifyNoMoreInteractions(client);
}); });
test('addDevice should work with pushProviderName', () async {
const deviceId = 'test-device-id';
const pushProvider = PushProvider.firebase;
const pushProviderName = 'my-custom-config';
const path = '/devices';
when(() => client.post(
path,
data: {
'id': deviceId,
'push_provider': pushProvider.name,
'push_provider_name': pushProviderName,
},
))
.thenAnswer(
(_) async => successResponse(path, data: <String, dynamic>{}));
final res = await deviceApi.addDevice(
deviceId,
pushProvider,
pushProviderName: pushProviderName,
);
expect(res, isNotNull);
verify(() => client.post(path, data: any(named: 'data'))).called(1);
verifyNoMoreInteractions(client);
});
test('getDevices', () async { test('getDevices', () async {
const path = '/devices'; const path = '/devices';
+13
View File
@@ -1,3 +1,16 @@
## Upcoming
✅ Added
- [[#1087]](https://github.com/GetStream/stream-chat-flutter/issues/1087): Handle limited access to camera on iOS.
- `centerTitle` and `elevation` properties to `ChannelHeader`, `ThreadHeader` and `ChannelListHeader`.
🐞 Fixed
- [[#1067]](https://github.com/GetStream/stream-chat-flutter/issues/1067): Fix name text overflow in reaction card.
- [[#842]](https://github.com/GetStream/stream-chat-flutter/issues/842): show date divider for first message.
- Loosen up url check for attachment download.
- Use `ogScrapeUrl` for LinkAttachments.
## 4.0.0-beta.2 ## 4.0.0-beta.2
✅ Added ✅ Added
@@ -24,14 +24,11 @@ class StreamAttachmentTitle extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final normalizedTitleLink = attachment.titleLink?.replaceFirst( final ogScrapeUrl = attachment.ogScrapeUrl;
RegExp(r'https?://(www\.)?'),
'',
);
return GestureDetector( return GestureDetector(
onTap: () { onTap: () {
final titleLink = attachment.titleLink; final ogScrapeUrl = attachment.ogScrapeUrl;
if (titleLink != null) launchURL(context, titleLink); if (ogScrapeUrl != null) launchURL(context, ogScrapeUrl);
}, },
child: Padding( child: Padding(
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),
@@ -48,8 +45,8 @@ class StreamAttachmentTitle extends StatelessWidget {
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
), ),
if (normalizedTitleLink != null) if (ogScrapeUrl != null)
Text(normalizedTitleLink, style: messageTheme.messageTextStyle), Text(ogScrapeUrl, style: messageTheme.messageTextStyle),
], ],
), ),
), ),
@@ -43,11 +43,11 @@ class StreamUrlAttachment extends StatelessWidget {
final chatThemeData = StreamChatTheme.of(context); final chatThemeData = StreamChatTheme.of(context);
return GestureDetector( return GestureDetector(
onTap: () { onTap: () {
final titleLink = urlAttachment.titleLink; final ogScrapeUrl = urlAttachment.ogScrapeUrl;
if (titleLink != null) { if (ogScrapeUrl != null) {
onLinkTap != null onLinkTap != null
? onLinkTap!(titleLink) ? onLinkTap!(ogScrapeUrl)
: launchURL(context, titleLink); : launchURL(context, ogScrapeUrl);
} }
}, },
child: Column( child: Column(
@@ -68,9 +68,11 @@ class StreamChannelHeader extends StatelessWidget
this.showConnectionStateTile = false, this.showConnectionStateTile = false,
this.title, this.title,
this.subtitle, this.subtitle,
this.centerTitle,
this.leading, this.leading,
this.actions, this.actions,
this.backgroundColor, this.backgroundColor,
this.elevation = 1,
}) : preferredSize = const Size.fromHeight(kToolbarHeight), }) : preferredSize = const Size.fromHeight(kToolbarHeight),
super(key: key); super(key: key);
@@ -99,6 +101,9 @@ class StreamChannelHeader extends StatelessWidget
/// Subtitle widget /// Subtitle widget
final Widget? subtitle; final Widget? subtitle;
/// Whether the title should be centered
final bool? centerTitle;
/// Leading widget /// Leading widget
final Widget? leading; final Widget? leading;
@@ -109,8 +114,16 @@ class StreamChannelHeader extends StatelessWidget
/// The background color for this [StreamChannelHeader]. /// The background color for this [StreamChannelHeader].
final Color? backgroundColor; final Color? backgroundColor;
/// The elevation for this [StreamChannelHeader].
final double elevation;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final effectiveCenterTitle = getEffectiveCenterTitle(
Theme.of(context),
actions: actions,
centerTitle: centerTitle,
);
final channel = StreamChannel.of(context).channel; final channel = StreamChannel.of(context).channel;
final channelHeaderTheme = StreamChannelHeaderTheme.of(context); final channelHeaderTheme = StreamChannelHeaderTheme.of(context);
@@ -151,7 +164,7 @@ class StreamChannelHeader extends StatelessWidget
systemOverlayStyle: theme.brightness == Brightness.dark systemOverlayStyle: theme.brightness == Brightness.dark
? SystemUiOverlayStyle.light ? SystemUiOverlayStyle.light
: SystemUiOverlayStyle.dark, : SystemUiOverlayStyle.dark,
elevation: 1, elevation: elevation,
leading: leadingWidget, leading: leadingWidget,
backgroundColor: backgroundColor ?? channelHeaderTheme.color, backgroundColor: backgroundColor ?? channelHeaderTheme.color,
actions: actions ?? actions: actions ??
@@ -170,14 +183,16 @@ class StreamChannelHeader extends StatelessWidget
), ),
), ),
], ],
centerTitle: true, centerTitle: centerTitle,
title: InkWell( title: InkWell(
onTap: onTitleTap, onTap: onTitleTap,
child: SizedBox( child: SizedBox(
height: preferredSize.height, height: preferredSize.height,
width: preferredSize.width,
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: effectiveCenterTitle
? CrossAxisAlignment.center
: CrossAxisAlignment.stretch,
children: <Widget>[ children: <Widget>[
title ?? title ??
StreamChannelName( StreamChannelName(
@@ -62,9 +62,11 @@ class StreamChannelListHeader extends StatelessWidget
this.showConnectionStateTile = false, this.showConnectionStateTile = false,
this.preNavigationCallback, this.preNavigationCallback,
this.subtitle, this.subtitle,
this.centerTitle,
this.leading, this.leading,
this.actions, this.actions,
this.backgroundColor, this.backgroundColor,
this.elevation = 1,
}) : super(key: key); }) : super(key: key);
/// Pass this if you don't have a [StreamChatClient] in your widget tree. /// Pass this if you don't have a [StreamChatClient] in your widget tree.
@@ -89,6 +91,9 @@ class StreamChannelListHeader extends StatelessWidget
/// Subtitle widget /// Subtitle widget
final Widget? subtitle; final Widget? subtitle;
/// Whether the title should be centered
final bool? centerTitle;
/// Leading widget /// Leading widget
/// By default it shows the logged in user avatar /// By default it shows the logged in user avatar
final Widget? leading; final Widget? leading;
@@ -100,6 +105,9 @@ class StreamChannelListHeader extends StatelessWidget
/// The background color for this [StreamChannelListHeader]. /// The background color for this [StreamChannelListHeader].
final Color? backgroundColor; final Color? backgroundColor;
/// The elevation for this [StreamChannelListHeader].
final double elevation;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final _client = client ?? StreamChat.of(context).client; final _client = client ?? StreamChat.of(context).client;
@@ -135,10 +143,10 @@ class StreamChannelListHeader extends StatelessWidget
systemOverlayStyle: theme.brightness == Brightness.dark systemOverlayStyle: theme.brightness == Brightness.dark
? SystemUiOverlayStyle.light ? SystemUiOverlayStyle.light
: SystemUiOverlayStyle.dark, : SystemUiOverlayStyle.dark,
elevation: 1, elevation: elevation,
backgroundColor: backgroundColor:
backgroundColor ?? channelListHeaderThemeData.color, backgroundColor ?? channelListHeaderThemeData.color,
centerTitle: true, centerTitle: centerTitle,
leading: leading ?? leading: leading ??
Center( Center(
child: user != null child: user != null
@@ -1,3 +1,6 @@
// ignore: lines_longer_than_80_chars
// ignore_for_file: deprecated_member_use_from_same_package, deprecated_member_use
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_slidable/flutter_slidable.dart'; import 'package:flutter_slidable/flutter_slidable.dart';
import 'package:shimmer/shimmer.dart'; import 'package:shimmer/shimmer.dart';
@@ -272,3 +272,12 @@ extension MessageX on Message {
return copyWith(text: messageTextToSend); return copyWith(text: messageTextToSend);
} }
} }
/// Extensions on [Uri]
extension UriX on Uri {
/// Return the URI adding the http scheme if it is missing
Uri get withScheme {
if (hasScheme) return this;
return Uri.parse('http://${toString()}');
}
}
@@ -5,7 +5,6 @@ import 'package:cached_network_image/cached_network_image.dart';
import 'package:chewie/chewie.dart'; import 'package:chewie/chewie.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:photo_view/photo_view.dart'; import 'package:photo_view/photo_view.dart';
import 'package:stream_chat_flutter/src/stream_attachment_package.dart';
import 'package:stream_chat_flutter/src/extension.dart'; import 'package:stream_chat_flutter/src/extension.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:video_player/video_player.dart'; import 'package:video_player/video_player.dart';
@@ -1,7 +1,6 @@
import 'package:jiffy/jiffy.dart'; import 'package:jiffy/jiffy.dart';
import 'package:stream_chat_flutter/src/connection_status_builder.dart'; import 'package:stream_chat_flutter/src/connection_status_builder.dart';
import 'package:stream_chat_flutter/src/message_list_view.dart'; import 'package:stream_chat_flutter/src/message_list_view.dart';
import 'package:stream_chat_flutter/src/message_search_list_view.dart';
import 'package:stream_chat_flutter/src/v4/message_input/stream_message_input.dart'; import 'package:stream_chat_flutter/src/v4/message_input/stream_message_input.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart' import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'
show User; show User;
@@ -321,6 +320,9 @@ abstract class Translations {
/// The label for "Reply to message" /// The label for "Reply to message"
String get replyToMessageLabel; String get replyToMessageLabel;
/// The label for "View library"
String get viewLibrary;
/// Label for "Attachment limit exceeded: /// Label for "Attachment limit exceeded:
/// it's not possible to add more than $limit attachments" /// it's not possible to add more than $limit attachments"
String attachmentLimitExceedError(int limit); String attachmentLimitExceedError(int limit);
@@ -696,6 +698,9 @@ class DefaultTranslations implements Translations {
@override @override
String get slowModeOnLabel => 'Slow mode ON'; String get slowModeOnLabel => 'Slow mode ON';
@override
String get viewLibrary => 'View library';
@override @override
String attachmentLimitExceedError(int limit) => """ String attachmentLimitExceedError(int limit) => """
Attachment limit exceeded: it's not possible to add more than $limit attachments"""; Attachment limit exceeded: it's not possible to add more than $limit attachments""";
@@ -5,6 +5,7 @@ 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/media_list_view_controller.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart';
/// {@macro media_list_view} /// {@macro media_list_view}
@@ -20,6 +21,7 @@ class StreamMediaListView extends StatefulWidget {
Key? key, Key? key,
this.selectedIds = const [], this.selectedIds = const [],
this.onSelect, this.onSelect,
this.controller,
}) : super(key: key); }) : super(key: key);
/// Stores the media selected /// Stores the media selected
@@ -28,18 +30,28 @@ class StreamMediaListView extends StatefulWidget {
/// Callback for on media selected /// Callback for on media selected
final void Function(AssetEntity media)? onSelect; final void Function(AssetEntity media)? onSelect;
/// Controller that handles MediaListView
final MediaListViewController? controller;
@override @override
_StreamMediaListViewState createState() => _StreamMediaListViewState(); _StreamMediaListViewState createState() => _StreamMediaListViewState();
} }
class _StreamMediaListViewState extends State<StreamMediaListView> { class _StreamMediaListViewState extends State<StreamMediaListView> {
final _media = <AssetEntity>[]; var _media = <AssetEntity>[];
final ScrollController _scrollController = ScrollController(); var _currentPage = 0;
int _currentPage = 0; final _scrollController = ScrollController();
/// Controller necessary to verify limited access to photo gallery in iOS and
/// update the media list when listerners are emitted
late final controller = widget.controller ?? MediaListViewController();
@override @override
Widget build(BuildContext context) => LazyLoadScrollView( Widget build(BuildContext context) => LazyLoadScrollView(
onEndOfPage: () async => _getMedia(), onEndOfPage: () async {
await _getMedia();
_updatePage();
},
child: GridView.builder( child: GridView.builder(
itemCount: _media.length, itemCount: _media.length,
controller: _scrollController, controller: _scrollController,
@@ -136,9 +148,29 @@ class _StreamMediaListViewState extends State<StreamMediaListView> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
controller.addListener(_updateMediaList);
_getMedia(); _getMedia();
} }
@override
void dispose() {
super.dispose();
controller.removeListener(_updateMediaList);
if (widget.controller == null) {
controller.dispose();
}
}
void _updateMediaList() {
if (controller.shouldUpdateMedia) {
_getMedia();
}
}
void _updatePage() {
++_currentPage;
}
Future<void> _getMedia() async { Future<void> _getMedia() async {
final assetList = (await PhotoManager.getAssetPathList( final assetList = (await PhotoManager.getAssetPathList(
filterOption: FilterOptionGroup( filterOption: FilterOptionGroup(
@@ -157,13 +189,11 @@ class _StreamMediaListViewState extends State<StreamMediaListView> {
page: _currentPage, page: _currentPage,
size: 50, size: 50,
); );
if (media?.isNotEmpty == true) { if (media?.isNotEmpty == true) {
setState(() { setState(() {
_media.addAll(media!); _media = media!;
}); });
} }
++_currentPage;
} }
} }
@@ -0,0 +1,16 @@
import 'package:flutter/material.dart';
/// Controller for MediaListView Widget
class MediaListViewController extends ChangeNotifier {
var _shouldUpdateMedia = false;
/// Getter that knows if the media should be updated.
bool get shouldUpdateMedia => _shouldUpdateMedia;
/// Method that update shouldUpdateMedia and notify all listeners
/// about this update.
void updateMedia({required bool newValue}) {
_shouldUpdateMedia = newValue;
notifyListeners();
}
}
@@ -1,3 +1,5 @@
// ignore_for_file: deprecated_member_use_from_same_package
import 'dart:async'; import 'dart:async';
import 'dart:math'; import 'dart:math';
@@ -15,7 +17,7 @@ import 'package:stream_chat_flutter/src/emoji/emoji.dart';
import 'package:stream_chat_flutter/src/emoji_overlay.dart'; import 'package:stream_chat_flutter/src/emoji_overlay.dart';
import 'package:stream_chat_flutter/src/extension.dart'; import 'package:stream_chat_flutter/src/extension.dart';
import 'package:stream_chat_flutter/src/media_list_view.dart'; import 'package:stream_chat_flutter/src/media_list_view.dart';
import 'package:stream_chat_flutter/src/multi_overlay.dart'; import 'package:stream_chat_flutter/src/media_list_view_controller.dart';
import 'package:stream_chat_flutter/src/quoted_message_widget.dart'; import 'package:stream_chat_flutter/src/quoted_message_widget.dart';
import 'package:stream_chat_flutter/src/user_mentions_overlay.dart'; import 'package:stream_chat_flutter/src/user_mentions_overlay.dart';
import 'package:stream_chat_flutter/src/video_thumbnail_image.dart'; import 'package:stream_chat_flutter/src/video_thumbnail_image.dart';
@@ -333,6 +335,7 @@ class MessageInputState extends State<MessageInput> {
final List<User> _mentionedUsers = []; final List<User> _mentionedUsers = [];
final _imagePicker = ImagePicker(); final _imagePicker = ImagePicker();
final _mediaListViewController = MediaListViewController();
late final _focusNode = widget.focusNode ?? FocusNode(); late final _focusNode = widget.focusNode ?? FocusNode();
late final _isInternalFocusNode = widget.focusNode == null; late final _isInternalFocusNode = widget.focusNode == null;
bool _inputEnabled = true; bool _inputEnabled = true;
@@ -1046,6 +1049,26 @@ class MessageInputState extends State<MessageInput> {
); );
}, },
), ),
const Spacer(),
FutureBuilder(
future: PhotoManager.requestPermissionExtend(),
builder: (context, snapshot) {
if (snapshot.hasData &&
snapshot.data == PermissionState.limited) {
return TextButton(
child: Text(context.translations.viewLibrary),
onPressed: () async {
await PhotoManager.presentLimited();
_mediaListViewController.updateMedia(
newValue: true,
);
},
);
}
return const SizedBox.shrink();
},
),
], ],
), ),
DecoratedBox( DecoratedBox(
@@ -1078,6 +1101,7 @@ class MessageInputState extends State<MessageInput> {
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
child: _PickerWidget( child: _PickerWidget(
mediaListViewController: _mediaListViewController,
filePickerIndex: _filePickerIndex, filePickerIndex: _filePickerIndex,
streamChatTheme: _streamChatTheme, streamChatTheme: _streamChatTheme,
containsFile: _attachmentContainsFile, containsFile: _attachmentContainsFile,
@@ -1241,7 +1265,7 @@ class MessageInputState extends State<MessageInput> {
Widget _buildReplyToMessage() { Widget _buildReplyToMessage() {
if (!_hasQuotedMessage) return const Offstage(); if (!_hasQuotedMessage) return const Offstage();
final containsUrl = widget.quotedMessage!.attachments final containsUrl = widget.quotedMessage!.attachments
.any((element) => element.titleLink != null); .any((element) => element.ogScrapeUrl != null);
return StreamQuotedMessageWidget( return StreamQuotedMessageWidget(
reverse: true, reverse: true,
showBorder: !containsUrl, showBorder: !containsUrl,
@@ -1909,6 +1933,7 @@ class _PickerWidget extends StatefulWidget {
required this.onAddMoreFilesClick, required this.onAddMoreFilesClick,
required this.onMediaSelected, required this.onMediaSelected,
required this.streamChatTheme, required this.streamChatTheme,
required this.mediaListViewController,
}) : super(key: key); }) : super(key: key);
final int filePickerIndex; final int filePickerIndex;
@@ -1917,6 +1942,7 @@ class _PickerWidget extends StatefulWidget {
final void Function(DefaultAttachmentTypes) onAddMoreFilesClick; final void Function(DefaultAttachmentTypes) onAddMoreFilesClick;
final void Function(AssetEntity) onMediaSelected; final void Function(AssetEntity) onMediaSelected;
final StreamChatThemeData streamChatTheme; final StreamChatThemeData streamChatTheme;
final MediaListViewController mediaListViewController;
@override @override
_PickerWidgetState createState() => _PickerWidgetState(); _PickerWidgetState createState() => _PickerWidgetState();
@@ -1964,7 +1990,9 @@ class _PickerWidgetState extends State<_PickerWidget> {
), ),
); );
} }
return StreamMediaListView( return StreamMediaListView(
controller: widget.mediaListViewController,
selectedIds: widget.selectedMedias, selectedIds: widget.selectedMedias,
onSelect: widget.onMediaSelected, onSelect: widget.onMediaSelected,
); );
@@ -579,6 +579,9 @@ class _StreamMessageListViewState extends State<StreamMessageListView> {
if (widget.reverse if (widget.reverse
? widget.headerBuilder == null ? widget.headerBuilder == null
: widget.footerBuilder == null) { : widget.footerBuilder == null) {
if (messages.isNotEmpty) {
return _buildDateDivider(messages.last);
}
if (_isThreadConversation) return const Offstage(); if (_isThreadConversation) return const Offstage();
return const SizedBox(height: 52); return const SizedBox(height: 52);
} }
@@ -603,21 +606,12 @@ class _StreamMessageListViewState extends State<StreamMessageListView> {
message = messages[i - 2]; message = messages[i - 2];
nextMessage = messages[i - 1]; nextMessage = messages[i - 1];
} }
if (!Jiffy(message.createdAt.toLocal()).isSame( if (!Jiffy(message.createdAt.toLocal()).isSame(
nextMessage.createdAt.toLocal(), nextMessage.createdAt.toLocal(),
Units.DAY, Units.DAY,
)) { )) {
final divider = widget.dateDividerBuilder != null return _buildDateDivider(nextMessage);
? widget.dateDividerBuilder!(
nextMessage.createdAt.toLocal(),
)
: Padding(
padding: const EdgeInsets.symmetric(vertical: 12),
child: StreamDateDivider(
dateTime: nextMessage.createdAt.toLocal(),
),
);
return divider;
} }
final timeDiff = final timeDiff =
Jiffy(nextMessage.createdAt.toLocal()).diff( Jiffy(nextMessage.createdAt.toLocal()).diff(
@@ -769,6 +763,20 @@ class _StreamMessageListViewState extends State<StreamMessageListView> {
return child; return child;
} }
Widget _buildDateDivider(Message message) {
final divider = widget.dateDividerBuilder != null
? widget.dateDividerBuilder!(
message.createdAt.toLocal(),
)
: Padding(
padding: const EdgeInsets.symmetric(vertical: 12),
child: StreamDateDivider(
dateTime: message.createdAt.toLocal(),
),
);
return divider;
}
Widget _buildThreadSeparator() { Widget _buildThreadSeparator() {
if (widget.threadSeparatorBuilder != null) { if (widget.threadSeparatorBuilder != null) {
return widget.threadSeparatorBuilder!.call(context); return widget.threadSeparatorBuilder!.call(context);
@@ -825,7 +833,11 @@ class _StreamMessageListViewState extends State<StreamMessageListView> {
index = _getBottomElementIndex(values); index = _getBottomElementIndex(values);
} }
if (index == null) return const Offstage(); if ((index == null) ||
(!_isThreadConversation && index == itemCount - 2) ||
(_isThreadConversation && index == itemCount - 1)) {
return const Offstage();
}
if (index <= 2 || index >= itemCount - 3) { if (index <= 2 || index >= itemCount - 3) {
if (widget.reverse) { if (widget.reverse) {
@@ -1109,7 +1121,7 @@ class _StreamMessageListViewState extends State<StreamMessageListView> {
final isOnlyEmoji = message.text?.isOnlyEmoji ?? false; final isOnlyEmoji = message.text?.isOnlyEmoji ?? false;
final hasUrlAttachment = final hasUrlAttachment =
message.attachments.any((it) => it.titleLink != null); message.attachments.any((it) => it.ogScrapeUrl != null);
final borderSide = final borderSide =
isOnlyEmoji || hasUrlAttachment || (isMyMessage && !hasFileAttachment) isOnlyEmoji || hasUrlAttachment || (isMyMessage && !hasFileAttachment)
@@ -246,6 +246,8 @@ class StreamMessageReactionsModal extends StatelessWidget {
reaction.user!.name.split(' ')[0], reaction.user!.name.split(' ')[0],
style: chatThemeData.textTheme.footnoteBold, style: chatThemeData.textTheme.footnoteBold,
textAlign: TextAlign.center, textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
maxLines: 1,
), ),
], ],
), ),
@@ -1,3 +1,6 @@
// ignore: lines_longer_than_80_chars
// ignore_for_file: deprecated_member_use_from_same_package, deprecated_member_use
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/src/extension.dart'; import 'package:stream_chat_flutter/src/extension.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart';
@@ -581,11 +581,11 @@ class _StreamMessageWidgetState extends State<StreamMessageWidget>
bool get isOnlyEmoji => widget.message.text?.isOnlyEmoji == true; bool get isOnlyEmoji => widget.message.text?.isOnlyEmoji == true;
bool get hasNonUrlAttachments => widget.message.attachments bool get hasNonUrlAttachments => widget.message.attachments
.where((it) => it.titleLink == null || it.type == 'giphy') .where((it) => it.ogScrapeUrl == null || it.type == 'giphy')
.isNotEmpty; .isNotEmpty;
bool get hasUrlAttachments => widget.message.attachments bool get hasUrlAttachments => widget.message.attachments
.any((it) => it.titleLink != null && it.type != 'giphy'); .any((it) => it.ogScrapeUrl != null && it.type != 'giphy');
bool get showBottomRow => bool get showBottomRow =>
showThreadReplyIndicator || showThreadReplyIndicator ||
@@ -1006,9 +1006,9 @@ class _StreamMessageWidgetState extends State<StreamMessageWidget>
Widget _buildUrlAttachment() { Widget _buildUrlAttachment() {
final urlAttachment = widget.message.attachments final urlAttachment = widget.message.attachments
.firstWhere((element) => element.titleLink != null); .firstWhere((element) => element.ogScrapeUrl != null);
final host = Uri.parse(urlAttachment.titleLink!).host; final host = Uri.parse(urlAttachment.ogScrapeUrl!).withScheme.host;
final splitList = host.split('.'); final splitList = host.split('.');
final hostName = splitList.length == 3 ? splitList[1] : splitList[0]; final hostName = splitList.length == 3 ? splitList[1] : splitList[0];
final hostDisplayName = urlAttachment.authorName?.capitalize() ?? final hostDisplayName = urlAttachment.authorName?.capitalize() ??
@@ -1176,7 +1176,7 @@ class _StreamMessageWidgetState extends State<StreamMessageWidget>
widget.message.attachments widget.message.attachments
.where((element) => .where((element) =>
(element.titleLink == null && element.type != null) || (element.ogScrapeUrl == null && element.type != null) ||
element.type == 'giphy') element.type == 'giphy')
.forEach((e) { .forEach((e) {
if (attachmentGroups[e.type] == null) { if (attachmentGroups[e.type] == null) {
@@ -57,7 +57,7 @@ class StreamQuotedMessageWidget extends StatelessWidget {
bool get _hasAttachments => message.attachments.isNotEmpty; bool get _hasAttachments => message.attachments.isNotEmpty;
bool get _containsLinkAttachment => bool get _containsLinkAttachment =>
message.attachments.any((element) => element.titleLink != null); message.attachments.any((element) => element.ogScrapeUrl != null);
bool get _containsText => message.text?.isNotEmpty == true; bool get _containsText => message.text?.isNotEmpty == true;
@@ -161,7 +161,7 @@ class StreamQuotedMessageWidget extends StatelessWidget {
Attachment attachment; Attachment attachment;
if (_containsLinkAttachment) { if (_containsLinkAttachment) {
attachment = message.attachments.firstWhere( attachment = message.attachments.firstWhere(
(element) => element.titleLink != null, (element) => element.ogScrapeUrl != null,
); );
child = _buildUrlAttachment(attachment); child = _buildUrlAttachment(attachment);
} else { } else {
@@ -414,7 +414,7 @@ class StreamChatThemeData {
/// Theme configuration for the [StreamUserListView] widget. /// Theme configuration for the [StreamUserListView] widget.
final StreamUserListViewThemeData userListViewTheme; final StreamUserListViewThemeData userListViewTheme;
/// Theme configuration for the [MessageSearchListView] widget. /// Theme configuration for the [StreamMessageSearchListView] widget.
final StreamMessageSearchListViewThemeData messageSearchListViewTheme; final StreamMessageSearchListViewThemeData messageSearchListViewTheme;
/// Creates a copy of [StreamChatThemeData] with specified attributes /// Creates a copy of [StreamChatThemeData] with specified attributes
@@ -72,11 +72,13 @@ class StreamThreadHeader extends StatelessWidget
this.onBackPressed, this.onBackPressed,
this.title, this.title,
this.subtitle, this.subtitle,
this.centerTitle,
this.leading, this.leading,
this.actions, this.actions,
this.onTitleTap, this.onTitleTap,
this.showTypingIndicator = true, this.showTypingIndicator = true,
this.backgroundColor, this.backgroundColor,
this.elevation = 1,
}) : preferredSize = const Size.fromHeight(kToolbarHeight), }) : preferredSize = const Size.fromHeight(kToolbarHeight),
super(key: key); super(key: key);
@@ -99,6 +101,9 @@ class StreamThreadHeader extends StatelessWidget
/// Subtitle widget /// Subtitle widget
final Widget? subtitle; final Widget? subtitle;
/// Whether the title should be centered
final bool? centerTitle;
/// Leading widget /// Leading widget
final Widget? leading; final Widget? leading;
@@ -112,8 +117,17 @@ class StreamThreadHeader extends StatelessWidget
/// The background color of this [StreamThreadHeader]. /// The background color of this [StreamThreadHeader].
final Color? backgroundColor; final Color? backgroundColor;
/// The elevation for this [StreamThreadHeader].
final double elevation;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final effectiveCenterTitle = getEffectiveCenterTitle(
Theme.of(context),
actions: actions,
centerTitle: centerTitle,
);
final channelHeaderTheme = StreamChannelHeaderTheme.of(context); final channelHeaderTheme = StreamChannelHeaderTheme.of(context);
final defaultSubtitle = subtitle ?? final defaultSubtitle = subtitle ??
@@ -126,7 +140,8 @@ class StreamThreadHeader extends StatelessWidget
style: channelHeaderTheme.subtitleStyle, style: channelHeaderTheme.subtitleStyle,
), ),
Flexible( Flexible(
child: ChannelName( child: StreamChannelName(
channel: StreamChannel.of(context).channel,
textStyle: channelHeaderTheme.subtitleStyle, textStyle: channelHeaderTheme.subtitleStyle,
), ),
), ),
@@ -141,7 +156,7 @@ class StreamThreadHeader extends StatelessWidget
systemOverlayStyle: theme.brightness == Brightness.dark systemOverlayStyle: theme.brightness == Brightness.dark
? SystemUiOverlayStyle.light ? SystemUiOverlayStyle.light
: SystemUiOverlayStyle.dark, : SystemUiOverlayStyle.dark,
elevation: 1, elevation: elevation,
leading: leading ?? leading: leading ??
(showBackButton (showBackButton
? StreamBackButton( ? StreamBackButton(
@@ -151,7 +166,7 @@ class StreamThreadHeader extends StatelessWidget
) )
: const SizedBox()), : const SizedBox()),
backgroundColor: backgroundColor ?? channelHeaderTheme.color, backgroundColor: backgroundColor ?? channelHeaderTheme.color,
centerTitle: true, centerTitle: centerTitle,
actions: actions, actions: actions,
title: InkWell( title: InkWell(
onTap: onTitleTap, onTap: onTitleTap,
@@ -160,6 +175,9 @@ class StreamThreadHeader extends StatelessWidget
width: 250, width: 250,
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: effectiveCenterTitle
? CrossAxisAlignment.center
: CrossAxisAlignment.stretch,
children: [ children: [
title ?? title ??
Text( Text(
@@ -49,6 +49,12 @@ class StreamTypingIndicator extends StatelessWidget {
.where((element) => element.value.parentId == parentId) .where((element) => element.value.parentId == parentId)
.map((e) => e.key)), .map((e) => e.key)),
builder: (context, users) => AnimatedSwitcher( builder: (context, users) => AnimatedSwitcher(
layoutBuilder: (currentChild, previousChildren) => Stack(
children: <Widget>[
...previousChildren,
if (currentChild != null) currentChild,
],
),
duration: const Duration(milliseconds: 300), duration: const Duration(milliseconds: 300),
child: users.isNotEmpty child: users.isNotEmpty
? Padding( ? Padding(
@@ -1,3 +1,6 @@
// ignore: lines_longer_than_80_chars
// ignore_for_file: deprecated_member_use_from_same_package, deprecated_member_use
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/src/extension.dart'; import 'package:stream_chat_flutter/src/extension.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart';
@@ -2,22 +2,43 @@ import 'dart:async';
import 'dart:math' as math; import 'dart:math' as math;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/src/stream_attachment_package.dart';
import 'package:stream_chat_flutter/src/extension.dart'; import 'package:stream_chat_flutter/src/extension.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
/// Launch URL /// Launch URL
Future<void> launchURL(BuildContext context, String url) async { Future<void> launchURL(BuildContext context, String url) async {
if (await canLaunch(url)) { try {
await launch(url); await launch(Uri.parse(url).withScheme.toString());
} else { } catch (e) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(context.translations.launchUrlError)), SnackBar(content: Text(context.translations.launchUrlError)),
); );
} }
} }
/// Get centerTitle considering a default and platform specific behaviour
bool getEffectiveCenterTitle(
ThemeData theme, {
bool? centerTitle,
List<Widget>? actions,
}) {
if (centerTitle != null) return centerTitle;
if (theme.appBarTheme.centerTitle != null) {
return theme.appBarTheme.centerTitle!;
}
switch (theme.platform) {
case TargetPlatform.android:
case TargetPlatform.fuchsia:
case TargetPlatform.linux:
case TargetPlatform.windows:
return false;
case TargetPlatform.iOS:
case TargetPlatform.macOS:
return actions == null || actions.length < 2;
}
}
/// Shows confirmation dialog /// Shows confirmation dialog
Future<bool?> showConfirmationDialog( Future<bool?> showConfirmationDialog(
BuildContext context, { BuildContext context, {
@@ -433,8 +454,8 @@ int levenshtein(String s, String t, {bool caseSensitive = true}) {
/// An easy way to handle attachment related operations on a message /// An easy way to handle attachment related operations on a message
extension AttachmentPackagesX on Message { extension AttachmentPackagesX on Message {
/// This extension will return a List of type [StreamAttachmentPackage] from the /// This extension will return a List of type [StreamAttachmentPackage]
/// existing attachments of the message /// from the existing attachments of the message
List<StreamAttachmentPackage> getAttachmentPackageList() { List<StreamAttachmentPackage> getAttachmentPackageList() {
final _attachmentPackages = List<StreamAttachmentPackage>.generate( final _attachmentPackages = List<StreamAttachmentPackage>.generate(
attachments.length, attachments.length,
@@ -5,6 +5,7 @@ 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/extension.dart'; import 'package:stream_chat_flutter/src/extension.dart';
import 'package:stream_chat_flutter/src/media_list_view.dart'; import 'package:stream_chat_flutter/src/media_list_view.dart';
import 'package:stream_chat_flutter/src/media_list_view_controller.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart';
/// Callback for when a file has to be picked. /// Callback for when a file has to be picked.
@@ -113,6 +114,7 @@ class StreamAttachmentPicker extends StatefulWidget {
class _StreamAttachmentPickerState extends State<StreamAttachmentPicker> { class _StreamAttachmentPickerState extends State<StreamAttachmentPicker> {
int _filePickerIndex = 0; int _filePickerIndex = 0;
final _mediaListViewController = MediaListViewController();
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -281,6 +283,26 @@ class _StreamAttachmentPickerState extends State<StreamAttachmentPicker> {
), ),
], ],
), ),
const Spacer(),
FutureBuilder(
future: PhotoManager.requestPermissionExtend(),
builder: (context, snapshot) {
if (snapshot.hasData &&
snapshot.data == PermissionState.limited) {
return TextButton(
child: Text(context.translations.viewLibrary),
onPressed: () async {
await PhotoManager.presentLimited();
_mediaListViewController.updateMedia(
newValue: true,
);
},
);
}
return const SizedBox.shrink();
},
),
DecoratedBox( DecoratedBox(
decoration: BoxDecoration( decoration: BoxDecoration(
color: _streamChatTheme.colorTheme.barsBg, color: _streamChatTheme.colorTheme.barsBg,
@@ -315,6 +337,7 @@ class _StreamAttachmentPickerState extends State<StreamAttachmentPicker> {
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
child: _PickerWidget( child: _PickerWidget(
mediaListViewController: _mediaListViewController,
filePickerIndex: _filePickerIndex, filePickerIndex: _filePickerIndex,
streamChatTheme: _streamChatTheme, streamChatTheme: _streamChatTheme,
containsFile: _attachmentContainsFile, containsFile: _attachmentContainsFile,
@@ -410,6 +433,7 @@ class _PickerWidget extends StatefulWidget {
required this.streamChatTheme, required this.streamChatTheme,
required this.allowedAttachmentTypes, required this.allowedAttachmentTypes,
required this.customAttachmentTypes, required this.customAttachmentTypes,
required this.mediaListViewController,
}) : super(key: key); }) : super(key: key);
final int filePickerIndex; final int filePickerIndex;
@@ -420,6 +444,7 @@ class _PickerWidget extends StatefulWidget {
final StreamChatThemeData streamChatTheme; final StreamChatThemeData streamChatTheme;
final List<DefaultAttachmentTypes> allowedAttachmentTypes; final List<DefaultAttachmentTypes> allowedAttachmentTypes;
final List<CustomAttachmentType> customAttachmentTypes; final List<CustomAttachmentType> customAttachmentTypes;
final MediaListViewController mediaListViewController;
@override @override
_PickerWidgetState createState() => _PickerWidgetState(); _PickerWidgetState createState() => _PickerWidgetState();
@@ -473,6 +498,7 @@ class _PickerWidgetState extends State<_PickerWidget> {
return StreamMediaListView( return StreamMediaListView(
selectedIds: widget.selectedMedias, selectedIds: widget.selectedMedias,
onSelect: widget.onMediaSelected, onSelect: widget.onMediaSelected,
controller: widget.mediaListViewController,
); );
} }
@@ -13,7 +13,6 @@ import 'package:stream_chat_flutter/src/commands_overlay.dart';
import 'package:stream_chat_flutter/src/emoji/emoji.dart'; import 'package:stream_chat_flutter/src/emoji/emoji.dart';
import 'package:stream_chat_flutter/src/emoji_overlay.dart'; import 'package:stream_chat_flutter/src/emoji_overlay.dart';
import 'package:stream_chat_flutter/src/extension.dart'; import 'package:stream_chat_flutter/src/extension.dart';
import 'package:stream_chat_flutter/src/multi_overlay.dart';
import 'package:stream_chat_flutter/src/quoted_message_widget.dart'; import 'package:stream_chat_flutter/src/quoted_message_widget.dart';
import 'package:stream_chat_flutter/src/user_mentions_overlay.dart'; import 'package:stream_chat_flutter/src/user_mentions_overlay.dart';
import 'package:stream_chat_flutter/src/v4/message_input/simple_safe_area.dart'; import 'package:stream_chat_flutter/src/v4/message_input/simple_safe_area.dart';
@@ -437,7 +437,7 @@ class StreamMessageTextField extends StatefulWidget {
/// This setting is only honored on iOS devices. /// This setting is only honored on iOS devices.
/// ///
/// If unset, defaults to the brightness of /// If unset, defaults to the brightness of
/// [ThemeData.primaryColorBrightness]. /// [ThemeData.brightness].
final Brightness? keyboardAppearance; final Brightness? keyboardAppearance;
/// {@macro flutter.widgets.editableText.scrollPadding} /// {@macro flutter.widgets.editableText.scrollPadding}
@@ -158,7 +158,8 @@ class StreamChannelGridView extends StatelessWidget {
/// only scroll the view if it has sufficient content. See [physics]. /// only scroll the view if it has sufficient content. See [physics].
/// ///
/// Also when true, the scroll view is used for default [ScrollAction]s. If a /// Also when true, the scroll view is used for default [ScrollAction]s. If a
/// ScrollAction is not handled by an otherwise focused part of the application, /// ScrollAction is not handled by
/// an otherwise focused part of the application,
/// the ScrollAction will be evaluated using this scroll view, for example, /// the ScrollAction will be evaluated using this scroll view, for example,
/// when executing [Shortcuts] key events like page up and down. /// when executing [Shortcuts] key events like page up and down.
/// ///
@@ -288,7 +289,8 @@ class StreamChannelGridView extends StatelessWidget {
/// ///
/// See also: /// See also:
/// ///
/// * [SemanticsConfiguration.scrollChildCount], the corresponding semantics property. /// * [SemanticsConfiguration.scrollChildCount],
/// the corresponding semantics property.
final int? semanticChildCount; final int? semanticChildCount;
/// {@macro flutter.widgets.scrollable.dragStartBehavior} /// {@macro flutter.widgets.scrollable.dragStartBehavior}
@@ -2,7 +2,6 @@ import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/src/extension.dart'; import 'package:stream_chat_flutter/src/extension.dart';
import 'package:stream_chat_flutter/src/v4/scroll_view/stream_scroll_view_empty_widget.dart';
import 'package:stream_chat_flutter/src/v4/scroll_view/stream_scroll_view_error_widget.dart'; import 'package:stream_chat_flutter/src/v4/scroll_view/stream_scroll_view_error_widget.dart';
import 'package:stream_chat_flutter/src/v4/scroll_view/stream_scroll_view_load_more_error.dart'; import 'package:stream_chat_flutter/src/v4/scroll_view/stream_scroll_view_load_more_error.dart';
import 'package:stream_chat_flutter/src/v4/scroll_view/stream_scroll_view_load_more_indicator.dart'; import 'package:stream_chat_flutter/src/v4/scroll_view/stream_scroll_view_load_more_indicator.dart';
@@ -75,7 +74,8 @@ class StreamMessageSearchGridView extends StatelessWidget {
/// A builder that is called to build items in the [PagedValueGridView]. /// A builder that is called to build items in the [PagedValueGridView].
/// ///
/// The `value` parameter is the [GetMessageBuilder] at this position in the grid. /// The `value` parameter is the [GetMessageBuilder]
/// at this position in the grid.
final StreamMessageSearchGridViewIndexedWidgetBuilder itemBuilder; final StreamMessageSearchGridViewIndexedWidgetBuilder itemBuilder;
/// A builder that is called to build the empty state of the grid. /// A builder that is called to build the empty state of the grid.
@@ -144,7 +144,8 @@ class StreamMessageSearchGridView extends StatelessWidget {
/// only scroll the view if it has sufficient content. See [physics]. /// only scroll the view if it has sufficient content. See [physics].
/// ///
/// Also when true, the scroll view is used for default [ScrollAction]s. If a /// Also when true, the scroll view is used for default [ScrollAction]s. If a
/// ScrollAction is not handled by an otherwise focused part of the application, /// ScrollAction is not handled by
/// an otherwise focused part of the application,
/// the ScrollAction will be evaluated using this scroll view, for example, /// the ScrollAction will be evaluated using this scroll view, for example,
/// when executing [Shortcuts] key events like page up and down. /// when executing [Shortcuts] key events like page up and down.
/// ///
@@ -274,7 +275,8 @@ class StreamMessageSearchGridView extends StatelessWidget {
/// ///
/// See also: /// See also:
/// ///
/// * [SemanticsConfiguration.scrollChildCount], the corresponding semantics property. /// * [SemanticsConfiguration.scrollChildCount],
/// the corresponding semantics property.
final int? semanticChildCount; final int? semanticChildCount;
/// {@macro flutter.widgets.scrollable.dragStartBehavior} /// {@macro flutter.widgets.scrollable.dragStartBehavior}
@@ -1,7 +1,8 @@
// ignore_for_file: deprecated_member_use_from_same_package
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/src/extension.dart'; import 'package:stream_chat_flutter/src/extension.dart';
import 'package:stream_chat_flutter/src/v4/scroll_view/stream_scroll_view_empty_widget.dart';
import 'package:stream_chat_flutter/src/v4/scroll_view/stream_scroll_view_error_widget.dart'; import 'package:stream_chat_flutter/src/v4/scroll_view/stream_scroll_view_error_widget.dart';
import 'package:stream_chat_flutter/src/v4/scroll_view/stream_scroll_view_load_more_error.dart'; import 'package:stream_chat_flutter/src/v4/scroll_view/stream_scroll_view_load_more_error.dart';
import 'package:stream_chat_flutter/src/v4/scroll_view/stream_scroll_view_load_more_indicator.dart'; import 'package:stream_chat_flutter/src/v4/scroll_view/stream_scroll_view_load_more_indicator.dart';
@@ -1,7 +1,6 @@
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/src/extension.dart'; import 'package:stream_chat_flutter/src/extension.dart';
import 'package:stream_chat_flutter/src/v4/scroll_view/stream_scroll_view_empty_widget.dart';
import 'package:stream_chat_flutter/src/v4/scroll_view/stream_scroll_view_error_widget.dart'; import 'package:stream_chat_flutter/src/v4/scroll_view/stream_scroll_view_error_widget.dart';
import 'package:stream_chat_flutter/src/v4/scroll_view/stream_scroll_view_load_more_error.dart'; import 'package:stream_chat_flutter/src/v4/scroll_view/stream_scroll_view_load_more_error.dart';
import 'package:stream_chat_flutter/src/v4/scroll_view/stream_scroll_view_load_more_indicator.dart'; import 'package:stream_chat_flutter/src/v4/scroll_view/stream_scroll_view_load_more_indicator.dart';
@@ -152,7 +151,8 @@ class StreamUserGridView extends StatelessWidget {
/// only scroll the view if it has sufficient content. See [physics]. /// only scroll the view if it has sufficient content. See [physics].
/// ///
/// Also when true, the scroll view is used for default [ScrollAction]s. If a /// Also when true, the scroll view is used for default [ScrollAction]s. If a
/// ScrollAction is not handled by an otherwise focused part of the application, /// ScrollAction is not handled by
/// an otherwise focused part of the application,
/// the ScrollAction will be evaluated using this scroll view, for example, /// the ScrollAction will be evaluated using this scroll view, for example,
/// when executing [Shortcuts] key events like page up and down. /// when executing [Shortcuts] key events like page up and down.
/// ///
@@ -282,7 +282,8 @@ class StreamUserGridView extends StatelessWidget {
/// ///
/// See also: /// See also:
/// ///
/// * [SemanticsConfiguration.scrollChildCount], the corresponding semantics property. /// * [SemanticsConfiguration.scrollChildCount],
/// the corresponding semantics property.
final int? semanticChildCount; final int? semanticChildCount;
/// {@macro flutter.widgets.scrollable.dragStartBehavior} /// {@macro flutter.widgets.scrollable.dragStartBehavior}
@@ -48,10 +48,11 @@ class StreamUserListTile extends StatelessWidget {
/// A widget to display at the end of tile. /// A widget to display at the end of tile.
final Widget? selectedWidget; final Widget? selectedWidget;
/// If this tile is also [enabled] then icons and text are rendered with the same color. /// If this tile is also [enabled] then icons
/// and text are rendered with the same color.
/// ///
/// By default the selected color is the theme's primary color. The selected color /// By default the selected color is the theme's primary color.
/// can be overridden with a [ListTileTheme]. /// The selected color can be overridden with a [ListTileTheme].
/// ///
/// {@tool dartpad} /// {@tool dartpad}
/// Here is an example of using a [StatefulWidget] to keep track of the /// Here is an example of using a [StatefulWidget] to keep track of the
@@ -1,7 +1,8 @@
// ignore_for_file: deprecated_member_use_from_same_package
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/src/extension.dart'; import 'package:stream_chat_flutter/src/extension.dart';
import 'package:stream_chat_flutter/src/v4/scroll_view/stream_scroll_view_empty_widget.dart';
import 'package:stream_chat_flutter/src/v4/scroll_view/stream_scroll_view_error_widget.dart'; import 'package:stream_chat_flutter/src/v4/scroll_view/stream_scroll_view_error_widget.dart';
import 'package:stream_chat_flutter/src/v4/scroll_view/stream_scroll_view_load_more_error.dart'; import 'package:stream_chat_flutter/src/v4/scroll_view/stream_scroll_view_load_more_error.dart';
import 'package:stream_chat_flutter/src/v4/scroll_view/stream_scroll_view_load_more_indicator.dart'; import 'package:stream_chat_flutter/src/v4/scroll_view/stream_scroll_view_load_more_indicator.dart';
@@ -22,6 +22,7 @@ export 'src/info_tile.dart';
export 'src/localization/stream_chat_localizations.dart'; export 'src/localization/stream_chat_localizations.dart';
export 'src/localization/translations.dart' show DefaultTranslations; export 'src/localization/translations.dart' show DefaultTranslations;
export 'src/message_action.dart'; export 'src/message_action.dart';
// ignore: deprecated_member_use_from_same_package
export 'src/message_input.dart' show MessageInput, MessageInputState; export 'src/message_input.dart' show MessageInput, MessageInputState;
export 'src/message_list_view.dart'; export 'src/message_list_view.dart';
export 'src/message_search_item.dart'; export 'src/message_search_item.dart';
@@ -280,7 +280,7 @@ void main() {
expect(find.text('test'), findsNothing); expect(find.text('test'), findsNothing);
expect(find.byType(StreamBackButton), findsNothing); expect(find.byType(StreamBackButton), findsNothing);
expect(find.byType(ChannelAvatar), findsNothing); expect(find.byType(StreamChannelAvatar), findsNothing);
expect(find.byType(StreamChannelInfo), findsNothing); expect(find.byType(StreamChannelInfo), findsNothing);
expect(find.text('leading'), findsOneWidget); expect(find.text('leading'), findsOneWidget);
expect(find.text('title'), findsOneWidget); expect(find.text('title'), findsOneWidget);
@@ -31,8 +31,10 @@ void main() {
client: client, client: client,
child: StreamChannel( child: StreamChannel(
channel: channel, channel: channel,
child: const Scaffold( child: Scaffold(
body: ChannelAvatar(), body: StreamChannelAvatar(
channel: channel,
),
), ),
), ),
), ),
@@ -101,8 +103,10 @@ void main() {
client: client, client: client,
child: StreamChannel( child: StreamChannel(
channel: channel, channel: channel,
child: const Scaffold( child: Scaffold(
body: ChannelAvatar(), body: StreamChannelAvatar(
channel: channel,
),
), ),
), ),
), ),
@@ -162,8 +166,10 @@ void main() {
client: client, client: client,
child: StreamChannel( child: StreamChannel(
channel: channel, channel: channel,
child: const Scaffold( child: Scaffold(
body: ChannelAvatar(), body: StreamChannelAvatar(
channel: channel,
),
), ),
), ),
), ),
@@ -202,9 +208,10 @@ void main() {
client: client, client: client,
child: StreamChannel( child: StreamChannel(
channel: channel, channel: channel,
child: const Scaffold( child: Scaffold(
body: ChannelAvatar( body: StreamChannelAvatar(
selected: true, selected: true,
channel: channel,
), ),
), ),
), ),
@@ -57,8 +57,10 @@ void main() {
client: client, client: client,
child: StreamChannel( child: StreamChannel(
channel: channel, channel: channel,
child: const Scaffold( child: Scaffold(
body: ChannelName(), body: StreamChannelName(
channel: channel,
),
), ),
), ),
), ),
@@ -1,3 +1,5 @@
// ignore_for_file: deprecated_member_use_from_same_package
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart'; import 'package:mocktail/mocktail.dart';
@@ -0,0 +1,37 @@
import 'package:stream_chat_flutter/src/media_list_view_controller.dart';
import 'package:test/test.dart';
void main() {
test('should update media', () {
final controller = MediaListViewController();
expect(controller.shouldUpdateMedia, false);
controller.updateMedia(newValue: true);
expect(controller.shouldUpdateMedia, true);
controller.dispose();
});
test('should notify listeners on update media', () {
final controller = MediaListViewController();
var callCount = 0;
void updateCallsSpy() => callCount++;
controller.addListener(updateCallsSpy);
expect(callCount, 0);
controller.updateMedia(newValue: false);
expect(controller.shouldUpdateMedia, false);
expect(callCount, 1);
controller.updateMedia(newValue: true);
expect(controller.shouldUpdateMedia, true);
expect(callCount, 2);
controller
..removeListener(updateCallsSpy)
..dispose();
});
}
@@ -1,3 +1,5 @@
// ignore_for_file: deprecated_member_use_from_same_package
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart';
@@ -1,3 +1,6 @@
// ignore: lines_longer_than_80_chars
// ignore_for_file: deprecated_member_use, deprecated_member_use_from_same_package
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart';
@@ -1,3 +1,6 @@
// ignore: lines_longer_than_80_chars
// ignore_for_file: deprecated_member_use_from_same_package, deprecated_member_use
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart';
@@ -1,3 +1,5 @@
// ignore_for_file: deprecated_member_use_from_same_package
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart'; import 'package:mocktail/mocktail.dart';
@@ -7,7 +7,6 @@
# The following line activates a set of recommended lints for Flutter apps, # The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices. # packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml
linter: linter:
# The lint rules applied to this project can be customized in the # The lint rules applied to this project can be customized in the
@@ -1,30 +0,0 @@
// This is a basic Flutter widget test.
//
// To perform an interaction with a widget in your test, use the WidgetTester
// utility that Flutter provides. For example, you can send tap and scroll
// gestures. You can also use WidgetTester to find child widgets in the widget
// tree, read text, and verify that the values of widget properties are correct.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:example/main.dart';
void main() {
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(const MyApp());
// Verify that our counter starts at 0.
expect(find.text('0'), findsOneWidget);
expect(find.text('1'), findsNothing);
// Tap the '+' icon and trigger a frame.
await tester.tap(find.byIcon(Icons.add));
await tester.pump();
// Verify that our counter has incremented.
expect(find.text('0'), findsNothing);
expect(find.text('1'), findsOneWidget);
});
}
@@ -1,3 +1,5 @@
// ignore_for_file: deprecated_member_use_from_same_package
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
@@ -1,3 +1,5 @@
// ignore_for_file: deprecated_member_use_from_same_package
import 'dart:async'; import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@@ -1,3 +1,5 @@
// ignore_for_file: deprecated_member_use_from_same_package
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:rxdart/rxdart.dart'; import 'package:rxdart/rxdart.dart';
import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat/stream_chat.dart';
@@ -1,3 +1,5 @@
// ignore_for_file: deprecated_member_use_from_same_package
import 'dart:convert'; import 'dart:convert';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
@@ -463,7 +463,8 @@ class PagedValueGridView<K, V> extends StatefulWidget {
/// only scroll the view if it has sufficient content. See [physics]. /// only scroll the view if it has sufficient content. See [physics].
/// ///
/// Also when true, the scroll view is used for default [ScrollAction]s. If a /// Also when true, the scroll view is used for default [ScrollAction]s. If a
/// ScrollAction is not handled by an otherwise focused part of the application, /// ScrollAction is not handled by
/// an otherwise focused part of the application,
/// the ScrollAction will be evaluated using this scroll view, for example, /// the ScrollAction will be evaluated using this scroll view, for example,
/// when executing [Shortcuts] key events like page up and down. /// when executing [Shortcuts] key events like page up and down.
/// ///
@@ -593,7 +594,8 @@ class PagedValueGridView<K, V> extends StatefulWidget {
/// ///
/// See also: /// See also:
/// ///
/// * [SemanticsConfiguration.scrollChildCount], the corresponding semantics property. /// * [SemanticsConfiguration.scrollChildCount], the corresponding
/// semantics property.
final int? semanticChildCount; final int? semanticChildCount;
/// {@macro flutter.widgets.scrollable.dragStartBehavior} /// {@macro flutter.widgets.scrollable.dragStartBehavior}
@@ -49,6 +49,7 @@ class StreamChannelListEventHandler {
/// This event is fired when a channel is updated. /// This event is fired when a channel is updated.
/// ///
/// By default, this updates the channel received in the event. /// By default, this updates the channel received in the event.
// ignore: no-empty-block
void onChannelUpdated(Event event, StreamChannelListController controller) {} void onChannelUpdated(Event event, StreamChannelListController controller) {}
/// Function which gets called for the event /// Function which gets called for the event
@@ -274,8 +274,9 @@ class StreamMessageInputController extends ValueNotifier<Message> {
/// A [RestorableProperty] that knows how to store and restore a /// A [RestorableProperty] that knows how to store and restore a
/// [StreamMessageInputController]. /// [StreamMessageInputController].
/// ///
/// The [StreamMessageInputController] is accessible via the [value] getter. During /// The [StreamMessageInputController] is accessible via the [value] getter.
/// state restoration, the property will restore [StreamMessageInputController.value] /// During state restoration,
/// the property will restore [StreamMessageInputController.value]
/// to the value it had when the restoration data it is getting restored from /// to the value it had when the restoration data it is getting restored from
/// was collected. /// was collected.
class StreamRestorableMessageInputController class StreamRestorableMessageInputController
@@ -1,3 +1,5 @@
// ignore_for_file: deprecated_member_use_from_same_package
import 'dart:convert'; import 'dart:convert';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
@@ -1,3 +1,5 @@
// ignore_for_file: deprecated_member_use_from_same_package
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:rxdart/rxdart.dart'; import 'package:rxdart/rxdart.dart';
import 'package:stream_chat_flutter_core/src/stream_controller_extension.dart'; import 'package:stream_chat_flutter_core/src/stream_controller_extension.dart';
@@ -1,3 +1,5 @@
// ignore_for_file: deprecated_member_use_from_same_package
import 'dart:async'; import 'dart:async';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
@@ -1,3 +1,5 @@
// ignore_for_file: deprecated_member_use_from_same_package
import 'dart:async'; import 'dart:async';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
@@ -1,3 +1,5 @@
// ignore_for_file: deprecated_member_use_from_same_package
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart'; import 'package:mocktail/mocktail.dart';
@@ -1,3 +1,5 @@
// ignore_for_file: deprecated_member_use_from_same_package
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart'; import 'package:mocktail/mocktail.dart';
@@ -1,3 +1,5 @@
// ignore_for_file: deprecated_member_use_from_same_package
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart'; import 'package:mocktail/mocktail.dart';
@@ -1,3 +1,5 @@
// ignore_for_file: deprecated_member_use_from_same_package
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart'; import 'package:mocktail/mocktail.dart';
@@ -1,3 +1,7 @@
## Upcoming
* Added translations for viewLibrary.
## 3.0.0-beta.1 ## 3.0.0-beta.1
* Updated `stream_chat_flutter` dependency to [`4.0.0-beta.1`](https://pub.dev/packages/stream_chat_flutter/changelog). * Updated `stream_chat_flutter` dependency to [`4.0.0-beta.1`](https://pub.dev/packages/stream_chat_flutter/changelog).
@@ -403,6 +403,9 @@ class NnStreamChatLocalizations extends GlobalStreamChatLocalizations {
@override @override
String get linkDisabledError => 'Links are disabled'; String get linkDisabledError => 'Links are disabled';
@override
String get viewLibrary => 'View library';
} }
void main() async { void main() async {
@@ -500,7 +503,8 @@ class MyApp extends StatelessWidget {
/// A list of messages sent in the current channel. /// A list of messages sent in the current channel.
/// ///
/// This is implemented using [MessageListView], a widget that provides query /// This is implemented using [StreamMessageListView],
/// a widget that provides query
/// functionalities fetching the messages from the api and showing them in a /// functionalities fetching the messages from the api and showing them in a
/// listView. /// listView.
class ChannelPage extends StatelessWidget { class ChannelPage extends StatelessWidget {
@@ -511,13 +515,13 @@ class ChannelPage extends StatelessWidget {
@override @override
Widget build(BuildContext context) => Scaffold( Widget build(BuildContext context) => Scaffold(
appBar: const ChannelHeader(), appBar: const StreamChannelHeader(),
body: Column( body: Column(
children: const <Widget>[ children: const <Widget>[
Expanded( Expanded(
child: MessageListView(), child: StreamMessageListView(),
), ),
MessageInput(), StreamMessageInput(),
], ],
), ),
); );
@@ -94,7 +94,8 @@ class MyApp extends StatelessWidget {
/// A list of messages sent in the current channel. /// A list of messages sent in the current channel.
/// ///
/// This is implemented using [MessageListView], a widget that provides query /// This is implemented using [StreamMessageListView],
/// a widget that provides query
/// functionalities fetching the messages from the api and showing them in a /// functionalities fetching the messages from the api and showing them in a
/// listView. /// listView.
class ChannelPage extends StatelessWidget { class ChannelPage extends StatelessWidget {
@@ -105,13 +106,13 @@ class ChannelPage extends StatelessWidget {
@override @override
Widget build(BuildContext context) => Scaffold( Widget build(BuildContext context) => Scaffold(
appBar: const ChannelHeader(), appBar: const StreamChannelHeader(),
body: Column( body: Column(
children: const <Widget>[ children: const <Widget>[
Expanded( Expanded(
child: MessageListView(), child: StreamMessageListView(),
), ),
MessageInput(), StreamMessageInput(),
], ],
), ),
); );
@@ -121,7 +121,8 @@ class MyApp extends StatelessWidget {
/// A list of messages sent in the current channel. /// A list of messages sent in the current channel.
/// ///
/// This is implemented using [MessageListView], a widget that provides query /// This is implemented using [StreamMessageListView],
/// a widget that provides query
/// functionalities fetching the messages from the api and showing them in a /// functionalities fetching the messages from the api and showing them in a
/// listView. /// listView.
class ChannelPage extends StatelessWidget { class ChannelPage extends StatelessWidget {
@@ -132,13 +133,13 @@ class ChannelPage extends StatelessWidget {
@override @override
Widget build(BuildContext context) => Scaffold( Widget build(BuildContext context) => Scaffold(
appBar: const ChannelHeader(), appBar: const StreamChannelHeader(),
body: Column( body: Column(
children: const <Widget>[ children: const <Widget>[
Expanded( Expanded(
child: MessageListView(), child: StreamMessageListView(),
), ),
MessageInput(), StreamMessageInput(),
], ],
), ),
); );
@@ -74,8 +74,9 @@ GlobalStreamChatLocalizations? getStreamChatTranslation(Locale locale) {
return const StreamChatLocalizationsKo(); return const StreamChatLocalizationsKo();
case 'pt': case 'pt':
return const StreamChatLocalizationsPt(); return const StreamChatLocalizationsPt();
default:
return null;
} }
return null;
} }
/// Implementation of localized strings for the stream chat widgets /// Implementation of localized strings for the stream chat widgets
@@ -379,4 +379,7 @@ class StreamChatLocalizationsEn extends GlobalStreamChatLocalizations {
@override @override
String get linkDisabledError => 'Links are disabled'; String get linkDisabledError => 'Links are disabled';
@override
String get viewLibrary => 'View library';
} }
@@ -376,6 +376,9 @@ class StreamChatLocalizationsEs extends GlobalStreamChatLocalizations {
No es posible añadir más de $limit archivos adjuntos No es posible añadir más de $limit archivos adjuntos
'''; ''';
@override
String get viewLibrary => 'Ver Librería';
@override @override
String get slowModeOnLabel => 'Modo lento activado'; String get slowModeOnLabel => 'Modo lento activado';
@@ -375,6 +375,9 @@ class StreamChatLocalizationsFr extends GlobalStreamChatLocalizations {
Limite de pièces jointes dépassée : il n'est pas possible d'ajouter plus de $limit pièces jointes Limite de pièces jointes dépassée : il n'est pas possible d'ajouter plus de $limit pièces jointes
'''; ''';
@override
String get viewLibrary => 'Voir la bibliothèque';
@override @override
String get slowModeOnLabel => 'Mode lent activé'; String get slowModeOnLabel => 'Mode lent activé';
@@ -369,6 +369,9 @@ class StreamChatLocalizationsHi extends GlobalStreamChatLocalizations {
िि: $limit ि िि: $limit ि
'''; ''';
@override
String get viewLibrary => 'पुस्तकालय देखिये';
@override @override
String get slowModeOnLabel => 'स्लो मोड चालू'; String get slowModeOnLabel => 'स्लो मोड चालू';
@@ -372,6 +372,9 @@ Il file è troppo grande per essere caricato. Il limite è di $limitInMB MB.''';
Attenzione: il limite massimo di $limit file è stato superato. Attenzione: il limite massimo di $limit file è stato superato.
'''; ''';
@override
String get viewLibrary => 'Vedi la biblioteca';
@override @override
String get slowModeOnLabel => 'Slowmode attiva'; String get slowModeOnLabel => 'Slowmode attiva';
@@ -353,6 +353,9 @@ class StreamChatLocalizationsJa extends GlobalStreamChatLocalizations {
@override @override
String get slowModeOnLabel => 'スローモードオン'; String get slowModeOnLabel => 'スローモードオン';
@override
String get viewLibrary => 'ライブラリを表示';
@override @override
String attachmentLimitExceedError(int limit) => ''' String attachmentLimitExceedError(int limit) => '''
$limit個のファイル以上を添付することはできません $limit個のファイル以上を添付することはできません
@@ -354,6 +354,10 @@ class StreamChatLocalizationsKo extends GlobalStreamChatLocalizations {
@override @override
String get slowModeOnLabel => '슬로모드 켜짐'; String get slowModeOnLabel => '슬로모드 켜짐';
@override
@override
String get viewLibrary => '라이브러리 보기';
@override @override
String attachmentLimitExceedError(int limit) => String attachmentLimitExceedError(int limit) =>
'첨부 파일 제한 초과: $limit 이상의 첨부 파일을 추가할 수 없습니다'; '첨부 파일 제한 초과: $limit 이상의 첨부 파일을 추가할 수 없습니다';
@@ -382,4 +382,7 @@ Não é possível adicionar mais de $limit arquivos de uma vez
@override @override
String get sendMessagePermissionError => String get sendMessagePermissionError =>
'Você não tem permissão para enviar mensagens'; 'Você não tem permissão para enviar mensagens';
@override
String get viewLibrary => 'Ver biblioteca';
} }
@@ -1,5 +1,4 @@
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:logging/logging.dart' show LogRecord;
import 'package:mutex/mutex.dart'; import 'package:mutex/mutex.dart';
import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat/stream_chat.dart';