Merge branch 'develop' into v4
This commit is contained in:
@@ -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.
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
+1
-1
@@ -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}
|
||||||
|
|||||||
+4
-2
@@ -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}
|
||||||
|
|||||||
+6
-4
@@ -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}
|
||||||
|
|||||||
+2
-1
@@ -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';
|
||||||
|
|||||||
+4
-3
@@ -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}
|
||||||
|
|||||||
+4
-3
@@ -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
|
||||||
|
|||||||
+2
-1
@@ -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';
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user