diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml index a90d828e..ca0d2720 100644 --- a/example/android/app/src/main/AndroidManifest.xml +++ b/example/android/app/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ additional functionality it is fine to subclass or reimplement FlutterApplication and put your custom class here. --> + Navigator.of(context).pop(); }, message: widget.message, + urls: widget.urls, + currentIndex: _currentPage, ), ImageFooter( currentPage: _currentPage, diff --git a/lib/src/image_actions_modal.dart b/lib/src/image_actions_modal.dart index 8b39737d..f07efcb6 100644 --- a/lib/src/image_actions_modal.dart +++ b/lib/src/image_actions_modal.dart @@ -1,6 +1,7 @@ import 'dart:ui'; import 'package:flutter/material.dart'; +import 'package:gallery_saver/gallery_saver.dart'; import '../stream_chat_flutter.dart'; import 'stream_icons.dart'; @@ -9,8 +10,11 @@ class ImageActionsModal extends StatelessWidget { final Message message; final String userName; final String sentAt; + final List urls; + final currentIndex; - ImageActionsModal({this.message, this.userName, this.sentAt}); + ImageActionsModal( + {this.message, this.userName, this.sentAt, this.urls, this.currentIndex}); @override Widget build(BuildContext context) { @@ -112,8 +116,12 @@ class ImageActionsModal extends StatelessWidget { Navigator.pop(context); Navigator.pop(context); }), - _buildButton( - context, 'Save Image', StreamIcons.save_1, () {}), + _buildButton(context, 'Save Image', StreamIcons.save_1, + () async { + await GallerySaver.saveImage( + urls[currentIndex].split('?')[0]); + Navigator.pop(context); + }), _buildButton(context, 'Copy', StreamIcons.copy, () {}), if (StreamChat.of(context).user.id == message.user.id) _buildButton(context, 'Delete', StreamIcons.delete, diff --git a/lib/src/image_footer.dart b/lib/src/image_footer.dart index 83960091..57119f8e 100644 --- a/lib/src/image_footer.dart +++ b/lib/src/image_footer.dart @@ -1,7 +1,12 @@ +import 'dart:io'; import 'dart:math'; +import 'dart:typed_data'; import 'package:cached_network_image/cached_network_image.dart'; +import 'package:esys_flutter_share/esys_flutter_share.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:gallery_saver/gallery_saver.dart'; import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat_flutter/src/back_button.dart'; import 'package:stream_chat_flutter/src/channel_info.dart'; @@ -61,6 +66,7 @@ class _ImageFooterState extends State { TextEditingController _messageController = TextEditingController(); List _selectedUsers = []; + bool _loading = false; @override void initState() { @@ -311,7 +317,8 @@ class _ImageFooterState extends State { _selectedUsers .remove(user); } else { - _selectedUsers.add(user); + _selectedUsers + .add(user); } modalSetState(() {}); }, @@ -368,14 +375,23 @@ class _ImageFooterState extends State { margin: EdgeInsets.symmetric( horizontal: 8.0, ), - child: SizedBox.expand( - child: Center( - child: Text( - 'Save to Photos', - style: TextStyle( - color: StreamChatTheme.of(modalContext) - .accentColor, - fontWeight: FontWeight.bold, + child: Material( + child: InkWell( + onTap: () async { + await GallerySaver.saveImage( + widget.urls[widget.currentPage].split('?')[0]); + Navigator.pop(context); + }, + child: SizedBox.expand( + child: Center( + child: Text( + 'Save to Photos', + style: TextStyle( + color: StreamChatTheme.of(modalContext) + .accentColor, + fontWeight: FontWeight.bold, + ), + ), ), ), ), @@ -493,7 +509,14 @@ class _ImageFooterState extends State { color: Colors.black, ), iconSize: 24.0, - onPressed: () {}, + onPressed: () async { + var url = widget.urls[widget.currentPage]; + var type = url?.split('?')?.first?.split('.')?.last ?? 'jpg'; + var request = await HttpClient().getUrl(Uri.parse(url)); + var response = await request.close(); + var bytes = await consolidateHttpClientResponseBytes(response); + await Share.file('File', 'image.$type', bytes, 'image/$type'); + }, ), ), ], @@ -507,7 +530,12 @@ class _ImageFooterState extends State { child: Container( color: Colors.white, height: 56.0, - child: Row( + child: _loading ? Center( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: CircularProgressIndicator(), + ), + ) : Row( children: [ Expanded( child: Padding( @@ -553,8 +581,14 @@ class _ImageFooterState extends State { child: Padding( padding: const EdgeInsets.all(8.0), child: InkWell( - onTap: () { - sendMessage(); + onTap: () async { + modalSetState(() { + _loading = true; + }); + await sendMessage(); + modalSetState(() { + _loading = false; + }); }, child: Transform.rotate( angle: -pi / 2, @@ -607,7 +641,7 @@ class _ImageFooterState extends State { } /// Sends the current message - void sendMessage() async { + Future sendMessage() async { var text = _messageController.text.trim(); final attachments = widget.message.attachments; @@ -615,8 +649,8 @@ class _ImageFooterState extends State { _messageController.clear(); final client = StreamChat.of(context).client; - - for(var user in _selectedUsers) { + + for (var user in _selectedUsers) { var c = client.channel('messaging', extraData: { 'members': [ user.id, diff --git a/lib/src/image_header.dart b/lib/src/image_header.dart index 29c36ad9..707750c5 100644 --- a/lib/src/image_header.dart +++ b/lib/src/image_header.dart @@ -30,10 +30,15 @@ class ImageHeader extends StatelessWidget implements PreferredSizeWidget { final String userName; final String sentAt; + final List urls; + final currentIndex; + /// Creates a channel header ImageHeader({ Key key, this.message, + this.urls, + this.currentIndex, this.showBackButton = true, this.onBackPressed, this.onTitleTap, @@ -117,6 +122,8 @@ class ImageHeader extends StatelessWidget implements PreferredSizeWidget { userName: userName, sentAt: sentAt, message: message, + urls: urls, + currentIndex: currentIndex, ), ); }); diff --git a/pubspec.yaml b/pubspec.yaml index 241025d0..78add1a5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -35,6 +35,8 @@ dependencies: flutter_slidable: ^0.5.4 carousel_slider: ^2.2.1 clipboard: ^0.1.2+8 + gallery_saver: ^2.0.1 + esys_flutter_share: ^1.0.2 flutter: assets: