feat: Added ability to add custom attachment types
This commit is contained in:
@@ -2,7 +2,6 @@ import 'dart:async';
|
|||||||
|
|
||||||
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:image_picker/image_picker.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';
|
||||||
@@ -15,6 +14,11 @@ typedef FilePickerCallback = void Function(
|
|||||||
bool camera,
|
bool camera,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
typedef CustomAttachmentIconBuilder = Widget Function(
|
||||||
|
BuildContext context,
|
||||||
|
bool active,
|
||||||
|
);
|
||||||
|
|
||||||
class StreamAttachmentPicker extends StatefulWidget {
|
class StreamAttachmentPicker extends StatefulWidget {
|
||||||
final bool isOpen;
|
final bool isOpen;
|
||||||
final double pickerSize;
|
final double pickerSize;
|
||||||
@@ -38,6 +42,8 @@ class StreamAttachmentPicker extends StatefulWidget {
|
|||||||
|
|
||||||
final List<DefaultAttachmentTypes> allowedAttachmentTypes;
|
final List<DefaultAttachmentTypes> allowedAttachmentTypes;
|
||||||
|
|
||||||
|
final List<CustomAttachmentType> customAttachmentTypes;
|
||||||
|
|
||||||
const StreamAttachmentPicker({
|
const StreamAttachmentPicker({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.messageInputController,
|
required this.messageInputController,
|
||||||
@@ -56,6 +62,7 @@ class StreamAttachmentPicker extends StatefulWidget {
|
|||||||
DefaultAttachmentTypes.file,
|
DefaultAttachmentTypes.file,
|
||||||
DefaultAttachmentTypes.video,
|
DefaultAttachmentTypes.video,
|
||||||
],
|
],
|
||||||
|
this.customAttachmentTypes = const [],
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -70,9 +77,15 @@ class _StreamAttachmentPickerState extends State<StreamAttachmentPicker> {
|
|||||||
var _streamChatTheme = StreamChatTheme.of(context);
|
var _streamChatTheme = StreamChatTheme.of(context);
|
||||||
var messageInputController = widget.messageInputController;
|
var messageInputController = widget.messageInputController;
|
||||||
|
|
||||||
|
final _attachmentContainsImage =
|
||||||
|
messageInputController.attachments.any((it) => it.type == 'image');
|
||||||
|
|
||||||
final _attachmentContainsFile =
|
final _attachmentContainsFile =
|
||||||
messageInputController.attachments.any((it) => it.type == 'file');
|
messageInputController.attachments.any((it) => it.type == 'file');
|
||||||
|
|
||||||
|
final _attachmentContainsVideo =
|
||||||
|
messageInputController.attachments.any((it) => it.type == 'video');
|
||||||
|
|
||||||
final attachmentLimitCrossed =
|
final attachmentLimitCrossed =
|
||||||
messageInputController.attachments.length >= widget.attachmentLimit;
|
messageInputController.attachments.length >= widget.attachmentLimit;
|
||||||
|
|
||||||
@@ -80,12 +93,13 @@ class _StreamAttachmentPickerState extends State<StreamAttachmentPicker> {
|
|||||||
final streamChatThemeData = _streamChatTheme;
|
final streamChatThemeData = _streamChatTheme;
|
||||||
switch (index) {
|
switch (index) {
|
||||||
case 0:
|
case 0:
|
||||||
return messageInputController.attachments.isEmpty
|
return _filePickerIndex == 0 || _attachmentContainsImage
|
||||||
? streamChatThemeData.colorTheme.accentPrimary
|
? streamChatThemeData.colorTheme.accentPrimary
|
||||||
: (!_attachmentContainsFile
|
: (_attachmentContainsImage
|
||||||
? streamChatThemeData.colorTheme.accentPrimary
|
? streamChatThemeData.colorTheme.accentPrimary
|
||||||
: streamChatThemeData.colorTheme.textHighEmphasis
|
: streamChatThemeData.colorTheme.textHighEmphasis.withOpacity(
|
||||||
.withOpacity(0.2));
|
messageInputController.attachments.isEmpty ? 0.5 : 0.2,
|
||||||
|
));
|
||||||
case 1:
|
case 1:
|
||||||
return _attachmentContainsFile
|
return _attachmentContainsFile
|
||||||
? streamChatThemeData.colorTheme.accentPrimary
|
? streamChatThemeData.colorTheme.accentPrimary
|
||||||
@@ -95,7 +109,8 @@ class _StreamAttachmentPickerState extends State<StreamAttachmentPicker> {
|
|||||||
: streamChatThemeData.colorTheme.textHighEmphasis
|
: streamChatThemeData.colorTheme.textHighEmphasis
|
||||||
.withOpacity(0.2));
|
.withOpacity(0.2));
|
||||||
case 2:
|
case 2:
|
||||||
return attachmentLimitCrossed
|
return widget.messageInputController.attachments.isNotEmpty &&
|
||||||
|
(!_attachmentContainsImage || attachmentLimitCrossed)
|
||||||
? streamChatThemeData.colorTheme.textHighEmphasis.withOpacity(0.2)
|
? streamChatThemeData.colorTheme.textHighEmphasis.withOpacity(0.2)
|
||||||
: _attachmentContainsFile &&
|
: _attachmentContainsFile &&
|
||||||
messageInputController.attachments.isNotEmpty
|
messageInputController.attachments.isNotEmpty
|
||||||
@@ -104,7 +119,8 @@ class _StreamAttachmentPickerState extends State<StreamAttachmentPicker> {
|
|||||||
: streamChatThemeData.colorTheme.textHighEmphasis
|
: streamChatThemeData.colorTheme.textHighEmphasis
|
||||||
.withOpacity(0.5);
|
.withOpacity(0.5);
|
||||||
case 3:
|
case 3:
|
||||||
return attachmentLimitCrossed
|
return widget.messageInputController.attachments.isNotEmpty &&
|
||||||
|
(!_attachmentContainsVideo || attachmentLimitCrossed)
|
||||||
? streamChatThemeData.colorTheme.textHighEmphasis.withOpacity(0.2)
|
? streamChatThemeData.colorTheme.textHighEmphasis.withOpacity(0.2)
|
||||||
: _attachmentContainsFile &&
|
: _attachmentContainsFile &&
|
||||||
messageInputController.attachments.isNotEmpty
|
messageInputController.attachments.isNotEmpty
|
||||||
@@ -138,14 +154,15 @@ class _StreamAttachmentPickerState extends State<StreamAttachmentPicker> {
|
|||||||
icon: StreamSvgIcon.pictures(
|
icon: StreamSvgIcon.pictures(
|
||||||
color: _getIconColor(0),
|
color: _getIconColor(0),
|
||||||
),
|
),
|
||||||
onPressed: _attachmentContainsFile &&
|
onPressed:
|
||||||
messageInputController.attachments.isNotEmpty
|
messageInputController.attachments.isNotEmpty &&
|
||||||
? null
|
!_attachmentContainsImage
|
||||||
: () {
|
? null
|
||||||
setState(() {
|
: () {
|
||||||
_filePickerIndex = 0;
|
setState(() {
|
||||||
});
|
_filePickerIndex = 0;
|
||||||
},
|
});
|
||||||
|
},
|
||||||
),
|
),
|
||||||
if (widget.allowedAttachmentTypes
|
if (widget.allowedAttachmentTypes
|
||||||
.contains(DefaultAttachmentTypes.file))
|
.contains(DefaultAttachmentTypes.file))
|
||||||
@@ -154,8 +171,9 @@ class _StreamAttachmentPickerState extends State<StreamAttachmentPicker> {
|
|||||||
icon: StreamSvgIcon.files(
|
icon: StreamSvgIcon.files(
|
||||||
color: _getIconColor(1),
|
color: _getIconColor(1),
|
||||||
),
|
),
|
||||||
onPressed: !_attachmentContainsFile &&
|
onPressed: messageInputController
|
||||||
messageInputController.attachments.isNotEmpty
|
.attachments.isNotEmpty &&
|
||||||
|
!_attachmentContainsFile
|
||||||
? null
|
? null
|
||||||
: () {
|
: () {
|
||||||
widget
|
widget
|
||||||
@@ -169,9 +187,9 @@ class _StreamAttachmentPickerState extends State<StreamAttachmentPicker> {
|
|||||||
color: _getIconColor(2),
|
color: _getIconColor(2),
|
||||||
),
|
),
|
||||||
onPressed: attachmentLimitCrossed ||
|
onPressed: attachmentLimitCrossed ||
|
||||||
(_attachmentContainsFile &&
|
(messageInputController
|
||||||
messageInputController
|
.attachments.isNotEmpty &&
|
||||||
.attachments.isNotEmpty)
|
!_attachmentContainsVideo)
|
||||||
? null
|
? null
|
||||||
: () {
|
: () {
|
||||||
widget.onFilePicked(
|
widget.onFilePicked(
|
||||||
@@ -188,9 +206,9 @@ class _StreamAttachmentPickerState extends State<StreamAttachmentPicker> {
|
|||||||
color: _getIconColor(3),
|
color: _getIconColor(3),
|
||||||
),
|
),
|
||||||
onPressed: attachmentLimitCrossed ||
|
onPressed: attachmentLimitCrossed ||
|
||||||
(_attachmentContainsFile &&
|
(messageInputController
|
||||||
messageInputController
|
.attachments.isNotEmpty &&
|
||||||
.attachments.isNotEmpty)
|
!_attachmentContainsVideo)
|
||||||
? null
|
? null
|
||||||
: () {
|
: () {
|
||||||
widget.onFilePicked(
|
widget.onFilePicked(
|
||||||
@@ -199,6 +217,26 @@ class _StreamAttachmentPickerState extends State<StreamAttachmentPicker> {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
for (int i = 0;
|
||||||
|
i < widget.customAttachmentTypes.length;
|
||||||
|
i++)
|
||||||
|
IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
if (messageInputController.attachments.isNotEmpty) {
|
||||||
|
if (!messageInputController.attachments.any((e) =>
|
||||||
|
e.type ==
|
||||||
|
widget.customAttachmentTypes[i].type)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
_filePickerIndex = i + 1;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
icon: widget.customAttachmentTypes[i]
|
||||||
|
.iconBuilder(context, _filePickerIndex == i + 1),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
DecoratedBox(
|
DecoratedBox(
|
||||||
@@ -252,6 +290,7 @@ class _StreamAttachmentPickerState extends State<StreamAttachmentPicker> {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
allowedAttachmentTypes: widget.allowedAttachmentTypes,
|
allowedAttachmentTypes: widget.allowedAttachmentTypes,
|
||||||
|
customAttachmentTypes: widget.customAttachmentTypes,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -350,6 +389,7 @@ class _PickerWidget extends StatefulWidget {
|
|||||||
required this.onMediaSelected,
|
required this.onMediaSelected,
|
||||||
required this.streamChatTheme,
|
required this.streamChatTheme,
|
||||||
required this.allowedAttachmentTypes,
|
required this.allowedAttachmentTypes,
|
||||||
|
required this.customAttachmentTypes,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
final int filePickerIndex;
|
final int filePickerIndex;
|
||||||
@@ -359,6 +399,7 @@ class _PickerWidget extends StatefulWidget {
|
|||||||
final void Function(AssetEntity) onMediaSelected;
|
final void Function(AssetEntity) onMediaSelected;
|
||||||
final StreamChatThemeData streamChatTheme;
|
final StreamChatThemeData streamChatTheme;
|
||||||
final List<DefaultAttachmentTypes> allowedAttachmentTypes;
|
final List<DefaultAttachmentTypes> allowedAttachmentTypes;
|
||||||
|
final List<CustomAttachmentType> customAttachmentTypes;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_PickerWidgetState createState() => _PickerWidgetState();
|
_PickerWidgetState createState() => _PickerWidgetState();
|
||||||
@@ -376,7 +417,8 @@ class _PickerWidgetState extends State<_PickerWidget> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (widget.filePickerIndex != 0) {
|
if (widget.filePickerIndex != 0) {
|
||||||
return const Offstage();
|
return widget.customAttachmentTypes[widget.filePickerIndex - 1]
|
||||||
|
.pickerBuilder(context);
|
||||||
}
|
}
|
||||||
return FutureBuilder<bool>(
|
return FutureBuilder<bool>(
|
||||||
future: requestPermission,
|
future: requestPermission,
|
||||||
@@ -453,3 +495,15 @@ class _PickerWidgetState extends State<_PickerWidget> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class CustomAttachmentType {
|
||||||
|
String type;
|
||||||
|
CustomAttachmentIconBuilder iconBuilder;
|
||||||
|
WidgetBuilder pickerBuilder;
|
||||||
|
|
||||||
|
CustomAttachmentType({
|
||||||
|
required this.type,
|
||||||
|
required this.iconBuilder,
|
||||||
|
required this.pickerBuilder,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user