From 8dd718b148f4981cb259a5fbc20d140f349d2bff Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 13 Nov 2020 16:26:24 +0100 Subject: [PATCH] ask permissions --- example/ios/Podfile.lock | 16 +- example/ios/Runner.xcodeproj/project.pbxproj | 4 +- example/lib/choose_user_page.dart | 268 ++++++++++--------- example/lib/main.dart | 130 ++++----- example/pubspec.yaml | 2 +- lib/src/message_input.dart | 102 +++++-- svgs/icon_picture_empty_state.svg | 4 + 7 files changed, 303 insertions(+), 223 deletions(-) create mode 100644 svgs/icon_picture_empty_state.svg diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index af3a3d40..131e8aec 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -108,6 +108,8 @@ PODS: - GoogleUtilities/Logger - image_picker (0.0.1): - Flutter + - media_gallery (0.0.1): + - Flutter - nanopb (1.30906.0): - nanopb/decode (= 1.30906.0) - nanopb/encode (= 1.30906.0) @@ -115,7 +117,7 @@ PODS: - nanopb/encode (1.30906.0) - path_provider (0.0.1): - Flutter - - photo_gallery (0.0.1): + - "permission_handler (5.0.1+1)": - Flutter - PromisesObjC (1.2.11) - Protobuf (3.13.0) @@ -167,8 +169,9 @@ DEPENDENCIES: - flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`) - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`) - image_picker (from `.symlinks/plugins/image_picker/ios`) + - media_gallery (from `.symlinks/plugins/media_gallery/ios`) - path_provider (from `.symlinks/plugins/path_provider/ios`) - - photo_gallery (from `.symlinks/plugins/photo_gallery/ios`) + - permission_handler (from `.symlinks/plugins/permission_handler/ios`) - shared_preferences (from `.symlinks/plugins/shared_preferences/ios`) - sqflite (from `.symlinks/plugins/sqflite/ios`) - sqlite3_flutter_libs (from `.symlinks/plugins/sqlite3_flutter_libs/ios`) @@ -220,10 +223,12 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/flutter_secure_storage/ios" image_picker: :path: ".symlinks/plugins/image_picker/ios" + media_gallery: + :path: ".symlinks/plugins/media_gallery/ios" path_provider: :path: ".symlinks/plugins/path_provider/ios" - photo_gallery: - :path: ".symlinks/plugins/photo_gallery/ios" + permission_handler: + :path: ".symlinks/plugins/permission_handler/ios" shared_preferences: :path: ".symlinks/plugins/shared_preferences/ios" sqflite: @@ -259,9 +264,10 @@ SPEC CHECKSUMS: GoogleDataTransport: f56af7caa4ed338dc8e138a5d7c5973e66440833 GoogleUtilities: 7f2f5a07f888cdb145101d6042bc4422f57e70b3 image_picker: 9c3312491f862b28d21ecd8fdf0ee14e601b3f09 + media_gallery: 834a04455a476b975c280aa1f24ed2853dfe36de nanopb: 59317e09cf1f1a0af72f12af412d54edf52603fc path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c - photo_gallery: 9f95e57747cd22c10676ece3660d1ffe6c603ee5 + permission_handler: eac8e15b4a1a3fba55b761d19f3f4e6b005d15b6 PromisesObjC: 8c196f5a328c2cba3e74624585467a557dcb482f Protobuf: 3dac39b34a08151c6d949560efe3f86134a3f748 SDWebImage: b69257f4ab14e9b6a2ef53e910fdf914d8f757c1 diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index ea52b6eb..709db931 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -327,9 +327,9 @@ "${BUILT_PRODUCTS_DIR}/flutter_local_notifications/flutter_local_notifications.framework", "${BUILT_PRODUCTS_DIR}/flutter_secure_storage/flutter_secure_storage.framework", "${BUILT_PRODUCTS_DIR}/image_picker/image_picker.framework", + "${BUILT_PRODUCTS_DIR}/media_gallery/media_gallery.framework", "${BUILT_PRODUCTS_DIR}/nanopb/nanopb.framework", "${BUILT_PRODUCTS_DIR}/path_provider/path_provider.framework", - "${BUILT_PRODUCTS_DIR}/photo_gallery/photo_gallery.framework", "${BUILT_PRODUCTS_DIR}/shared_preferences/shared_preferences.framework", "${BUILT_PRODUCTS_DIR}/sqflite/sqflite.framework", "${BUILT_PRODUCTS_DIR}/sqlite3/sqlite3.framework", @@ -358,9 +358,9 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_local_notifications.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_secure_storage.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/image_picker.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/media_gallery.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/nanopb.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/path_provider.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/photo_gallery.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/shared_preferences.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/sqflite.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/sqlite3.framework", diff --git a/example/lib/choose_user_page.dart b/example/lib/choose_user_page.dart index fd7d2b6c..30205088 100644 --- a/example/lib/choose_user_page.dart +++ b/example/lib/choose_user_page.dart @@ -59,165 +59,167 @@ class ChooseUserPage extends StatelessWidget { (_, value) => value..extraData['image'] = getRandomPicUrl(value)); return Scaffold( - body: Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Padding( - padding: const EdgeInsets.only( - top: 34, - bottom: 20, - ), - child: Center( - child: SvgPicture.asset( - 'assets/logo.svg', - height: 40, + body: SafeArea( + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.only( + top: 34, + bottom: 20, + ), + child: Center( + child: SvgPicture.asset( + 'assets/logo.svg', + height: 40, + ), ), ), - ), - Padding( - padding: const EdgeInsets.only(bottom: 13.0), - child: Text( - 'Welcome to Stream Chat', + Padding( + padding: const EdgeInsets.only(bottom: 13.0), + child: Text( + 'Welcome to Stream Chat', + style: TextStyle( + fontSize: 22, + color: Colors.black, + fontWeight: FontWeight.bold, + ), + ), + ), + Text( + 'Select a user to try the Flutter SDK:', style: TextStyle( - fontSize: 22, + fontSize: 14.5, color: Colors.black, - fontWeight: FontWeight.bold, ), ), - ), - Text( - 'Select a user to try the Flutter SDK:', - style: TextStyle( - fontSize: 14.5, - color: Colors.black, - ), - ), - Expanded( - child: ListView.separated( - separatorBuilder: (context, i) { - return Container( - width: double.infinity, - color: Colors.black12, - height: 1, - ); - }, - itemCount: users.length + 1, - itemBuilder: (context, i) { - return [ - ...users.entries.map((entry) { - final token = entry.key; - final user = entry.value; - return ListTile( - onTap: () async { - showDialog( - barrierDismissible: false, - context: context, - builder: (context) => Center( - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(16), - color: Colors.white, - ), - height: 100, - width: 100, - child: Center( - child: CircularProgressIndicator(), + Expanded( + child: ListView.separated( + separatorBuilder: (context, i) { + return Container( + width: double.infinity, + color: Colors.black12, + height: 1, + ); + }, + itemCount: users.length + 1, + itemBuilder: (context, i) { + return [ + ...users.entries.map((entry) { + final token = entry.key; + final user = entry.value; + return ListTile( + onTap: () async { + showDialog( + barrierDismissible: false, + context: context, + builder: (context) => Center( + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(16), + color: Colors.white, + ), + height: 100, + width: 100, + child: Center( + child: CircularProgressIndicator(), + ), ), ), + ); + + final secureStorage = FlutterSecureStorage(); + final client = StreamChat.of(context).client; + + await client.setUser( + user, + token, + ); + + secureStorage.write( + key: kStreamApiKey, + value: kDefaultStreamApiKey, + ); + secureStorage.write( + key: kStreamUserId, + value: user.id, + ); + secureStorage.write( + key: kStreamToken, + value: token, + ); + + if (!kIsWeb) { + initNotifications(client); + } + + Navigator.pop(context); + await Navigator.pushReplacement( + context, + MaterialPageRoute( + builder: (context) { + return StreamChat( + client: client, + child: ChannelListPage(), + ); + }, + ), + ); + }, + leading: UserAvatar( + user: user, + constraints: BoxConstraints.tight( + Size.fromRadius(20), ), - ); - - final secureStorage = FlutterSecureStorage(); - final client = StreamChat.of(context).client; - - await client.setUser( - user, - token, - ); - - secureStorage.write( - key: kStreamApiKey, - value: kDefaultStreamApiKey, - ); - secureStorage.write( - key: kStreamUserId, - value: user.id, - ); - secureStorage.write( - key: kStreamToken, - value: token, - ); - - if (!kIsWeb) { - initNotifications(client); - } - - Navigator.pop(context); - await Navigator.pushReplacement( + ), + title: Text( + user.name, + style: TextStyle(fontWeight: FontWeight.bold), + ), + subtitle: Text('Stream test account'), + trailing: SvgPicture.asset( + 'assets/icon_arrow_right.svg', + height: 24, + width: 24, + ), + ); + }), + ListTile( + onTap: () { + Navigator.push( context, MaterialPageRoute( - builder: (context) { - return StreamChat( - client: client, - child: ChannelListPage(), - ); - }, + builder: (context) => AdvancedOptionsPage(), ), ); }, - leading: UserAvatar( - user: user, - constraints: BoxConstraints.tight( - Size.fromRadius(20), + leading: CircleAvatar( + child: Icon( + StreamIcons.settings, + color: Colors.black, ), + backgroundColor: + StreamChatTheme.of(context).secondaryColor, ), title: Text( - user.name, + 'Advanced Options', style: TextStyle(fontWeight: FontWeight.bold), ), - subtitle: Text('Stream test account'), + subtitle: Text('Custom settings'), trailing: SvgPicture.asset( 'assets/icon_arrow_right.svg', height: 24, width: 24, + clipBehavior: Clip.none, ), - ); - }), - ListTile( - onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => AdvancedOptionsPage(), - ), - ); - }, - leading: CircleAvatar( - child: Icon( - StreamIcons.settings, - color: Colors.black, - ), - backgroundColor: - StreamChatTheme.of(context).secondaryColor, ), - title: Text( - 'Advanced Options', - style: TextStyle(fontWeight: FontWeight.bold), - ), - subtitle: Text('Custom settings'), - trailing: SvgPicture.asset( - 'assets/icon_arrow_right.svg', - height: 24, - width: 24, - clipBehavior: Clip.none, - ), - ), - ][i]; - }, + ][i]; + }, + ), ), - ), - StreamVersion(), - ], + StreamVersion(), + ], + ), ), ); } diff --git a/example/lib/main.dart b/example/lib/main.dart index 1da02072..2e3f43e2 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -64,83 +64,85 @@ class ChannelListPage extends StatelessWidget { final user = StreamChat.of(context).user; return Scaffold( drawer: Drawer( - child: Padding( - padding: EdgeInsets.only( - top: MediaQuery.of(context).viewPadding.top + 8, - ), - child: Column( - children: [ - Padding( - padding: const EdgeInsets.only( - bottom: 20.0, - left: 8, - ), - child: Row( - children: [ - UserAvatar( - user: user, - showOnlineStatus: false, - constraints: BoxConstraints.tight(Size.fromRadius(20)), - ), - Padding( - padding: const EdgeInsets.only(left: 16.0), - child: Text( - user.name, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, + child: SafeArea( + child: Padding( + padding: EdgeInsets.only( + top: MediaQuery.of(context).viewPadding.top + 8, + ), + child: Column( + children: [ + Padding( + padding: const EdgeInsets.only( + bottom: 20.0, + left: 8, + ), + child: Row( + children: [ + UserAvatar( + user: user, + showOnlineStatus: false, + constraints: BoxConstraints.tight(Size.fromRadius(20)), + ), + Padding( + padding: const EdgeInsets.only(left: 16.0), + child: Text( + user.name, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), ), ), + ], + ), + ), + ListTile( + leading: Icon(StreamIcons.edit), + title: Text( + 'New direct message', + style: TextStyle( + fontSize: 14.5, ), - ], - ), - ), - ListTile( - leading: Icon(StreamIcons.edit), - title: Text( - 'New direct message', - style: TextStyle( - fontSize: 14.5, ), ), - ), - ListTile( - leading: Icon(StreamIcons.group), - title: Text( - 'New group', - style: TextStyle( - fontSize: 14.5, + ListTile( + leading: Icon(StreamIcons.group), + title: Text( + 'New group', + style: TextStyle( + fontSize: 14.5, + ), ), ), - ), - Expanded( - child: Container( - alignment: Alignment.bottomCenter, - child: ListTile( - onTap: () async { - await StreamChat.of(context).client.disconnect(); + Expanded( + child: Container( + alignment: Alignment.bottomCenter, + child: ListTile( + onTap: () async { + await StreamChat.of(context).client.disconnect(); - final secureStorage = FlutterSecureStorage(); - await secureStorage.deleteAll(); - Navigator.pop(context); - await Navigator.pushReplacement( - context, - MaterialPageRoute( - builder: (context) => ChooseUserPage(), + final secureStorage = FlutterSecureStorage(); + await secureStorage.deleteAll(); + Navigator.pop(context); + await Navigator.pushReplacement( + context, + MaterialPageRoute( + builder: (context) => ChooseUserPage(), + ), + ); + }, + leading: Icon(StreamIcons.user), + title: Text( + 'Sign out', + style: TextStyle( + fontSize: 14.5, ), - ); - }, - leading: Icon(StreamIcons.user), - title: Text( - 'Sign out', - style: TextStyle( - fontSize: 14.5, ), ), ), ), - ), - ], + ], + ), ), ), ), diff --git a/example/pubspec.yaml b/example/pubspec.yaml index e1442635..8fc2dadb 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -1,6 +1,6 @@ name: example description: A new Flutter project. -version: 1.0.58+60 +version: 1.0.59+61 environment: sdk: ">=2.2.2 <3.0.0" diff --git a/lib/src/message_input.dart b/lib/src/message_input.dart index 72c754e0..1f157ed3 100644 --- a/lib/src/message_input.dart +++ b/lib/src/message_input.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:io'; import 'dart:math'; import 'package:emojis/emoji.dart'; @@ -659,7 +660,7 @@ class MessageInputState extends State { setState(() { _animateContainer = false; _filePickerSize = (_filePickerSize - update.delta.dy).clamp( - 100, + 240.0, MediaQuery.of(context).size.height / 1.7, ); }); @@ -709,17 +710,80 @@ class MessageInputState extends State { Widget _buildPickerSection() { switch (_filePickerIndex) { case 0: - return MediaListView( - selectedIds: _attachments.map((e) => e.id).toList(), - onSelect: (media) { - if (!_attachments.any((element) => element.id == media.id)) { - _addAttachment(media); - } else { - _attachments.removeWhere((element) => element.id == media.id); - setState(() {}); - } - }, - ); + return FutureBuilder( + future: Platform.isAndroid + ? Permission.storage.status + : Permission.photos.status, + builder: (context, snapshot) { + if (!snapshot.hasData) { + return Center( + child: CircularProgressIndicator(), + ); + } + + if (snapshot.data.isGranted) { + return MediaListView( + selectedIds: _attachments.map((e) => e.id).toList(), + onSelect: (media) { + if (!_attachments + .any((element) => element.id == media.id)) { + _addAttachment(media); + } else { + _attachments + .removeWhere((element) => element.id == media.id); + setState(() {}); + } + }, + ); + } + + return InkWell( + onTap: () async { + var status = await (Platform.isAndroid + ? Permission.storage.status + : Permission.photos.status); + print('status: ${status}'); + if (status.isPermanentlyDenied || status.isDenied) { + if (await openAppSettings()) { + setState(() {}); + } + } else { + status = await (Platform.isAndroid + ? Permission.storage + : Permission.photos) + .request(); + if (status.isGranted) { + setState(() {}); + } + } + }, + child: Container( + color: Color(0xFFF2F2F2), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + SvgPicture.asset( + 'svgs/icon_picture_empty_state.svg', + package: 'stream_chat_flutter', + height: 140, + color: StreamChatTheme.of(context).accentColor, + ), + Center( + child: Text( + 'Allow access to your gallery', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + color: StreamChatTheme.of(context).accentColor, + ), + ), + ), + ], + ), + ), + ); + }); break; case 1: break; @@ -1235,12 +1299,14 @@ class MessageInputState extends State { _filePickerSize = 250.0; }); } else { - final status = await Permission.storage.request(); - if (status.isDenied || status.isPermanentlyDenied) { - final res = await openAppSettings(); - if (!res) { - return; - } + final status = await (Platform.isAndroid + ? Permission.storage.status + : Permission.photos.status); + if (status.isUndetermined) { + await (Platform.isAndroid + ? Permission.storage + : Permission.photos) + .request(); } showAttachmentModal(); } diff --git a/svgs/icon_picture_empty_state.svg b/svgs/icon_picture_empty_state.svg new file mode 100644 index 00000000..135772d0 --- /dev/null +++ b/svgs/icon_picture_empty_state.svg @@ -0,0 +1,4 @@ + + + +