diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 70cc5b45f..b0e57297c 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -70,5 +70,14 @@ android:name="com.yalantis.ucrop.UCropActivity" android:screenOrientation="portrait" android:theme="@style/Theme.AppCompat.Light.NoActionBar"/> + + + diff --git a/android/app/src/main/java/com/example/openbook/MainActivity.java b/android/app/src/main/java/com/example/openbook/MainActivity.java index 937066273..5746d2da8 100644 --- a/android/app/src/main/java/com/example/openbook/MainActivity.java +++ b/android/app/src/main/java/com/example/openbook/MainActivity.java @@ -25,6 +25,8 @@ import io.flutter.plugin.common.EventChannel; import io.flutter.plugins.GeneratedPluginRegistrant; +import social.openbook.app.plugins.Permissions; + public class MainActivity extends FlutterActivity { public static final String SHARE_STREAM = "openbook.social/receive_share"; @@ -37,6 +39,7 @@ public class MainActivity extends FlutterActivity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); GeneratedPluginRegistrant.registerWith(this); + Permissions.registerWith(this.registrarFor("social.openbook.app.plugins.Permissions")); new EventChannel(getFlutterView(), SHARE_STREAM).setStreamHandler( new EventChannel.StreamHandler() { diff --git a/android/app/src/main/java/com/example/openbook/plugins/Permissions.java b/android/app/src/main/java/com/example/openbook/plugins/Permissions.java new file mode 100644 index 000000000..cee52da77 --- /dev/null +++ b/android/app/src/main/java/com/example/openbook/plugins/Permissions.java @@ -0,0 +1,80 @@ +package social.openbook.app.plugins; + +import android.Manifest; +import android.content.pm.PackageManager; +import android.util.SparseArray; +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; + +import java.util.HashMap; +import java.util.Map; + +import io.flutter.plugin.common.MethodChannel; +import io.flutter.plugin.common.MethodChannel.MethodCallHandler; +import io.flutter.plugin.common.MethodChannel.Result; +import io.flutter.plugin.common.MethodCall; +import io.flutter.plugin.common.PluginRegistry; +import io.flutter.plugin.common.PluginRegistry.Registrar; + +public class Permissions implements MethodCallHandler, PluginRegistry.RequestPermissionsResultListener { + private Registrar registrar; + private SparseArray result; + + public static void registerWith(Registrar registrar) { + final MethodChannel channel = new MethodChannel(registrar.messenger(), "openbook.social/permissions"); + Permissions permissions = new Permissions(registrar); + channel.setMethodCallHandler(permissions); + registrar.addRequestPermissionsResultListener(permissions); + } + + private Permissions(Registrar registrar) { + this.registrar = registrar; + this.result = new SparseArray<>(); + } + + @Override + public void onMethodCall(MethodCall call, Result result) { + switch (call.method) { + case "checkPermission": + result.success(checkPermission(call.argument("permission"))); + break; + case "requestPermission": + requestPermission(call.argument("permission"), result); + break; + default: + result.notImplemented(); + break; + } + } + + private String getManifestPermission(String permission) { + switch (permission) { + case "WRITE_EXTERNAL_STORAGE": + return Manifest.permission.WRITE_EXTERNAL_STORAGE; + default: + return permission; + } + } + + private boolean checkPermission(String permission) { + return ContextCompat.checkSelfPermission(registrar.activity(), getManifestPermission(permission)) == PackageManager.PERMISSION_GRANTED; + } + + private void requestPermission(String permission, Result result) { + permission = getManifestPermission(permission); + this.result.put(permission.hashCode(), result); + ActivityCompat.requestPermissions(registrar.activity(), new String[] {permission}, permission.hashCode()); + } + + @Override + public boolean onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { + Result result = this.result.get(requestCode); + if (result != null && permissions.length == 1 && permissions[0].hashCode() == requestCode) { + boolean value = grantResults[0] == PackageManager.PERMISSION_GRANTED; + result.success(value); + this.result.delete(requestCode); + return value; + } + return false; + } +} \ No newline at end of file diff --git a/ios/Podfile.lock b/ios/Podfile.lock index cbc13092e..0195d1f7b 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1,7 +1,11 @@ PODS: - device_info (0.0.1): - Flutter + - downloads_path_provider (0.0.1): + - Flutter - Flutter (1.0.0) + - flutter_downloader (0.0.1): + - Flutter - flutter_exif_rotation (0.0.1): - Flutter - flutter_secure_storage (3.2.0): @@ -39,7 +43,9 @@ PODS: DEPENDENCIES: - device_info (from `.symlinks/plugins/device_info/ios`) + - downloads_path_provider (from `.symlinks/plugins/downloads_path_provider/ios`) - Flutter (from `.symlinks/flutter/ios`) + - flutter_downloader (from `.symlinks/plugins/flutter_downloader/ios`) - flutter_exif_rotation (from `.symlinks/plugins/flutter_exif_rotation/ios`) - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`) - image_cropper (from `.symlinks/plugins/image_cropper/ios`) @@ -64,8 +70,12 @@ SPEC REPOS: EXTERNAL SOURCES: device_info: :path: ".symlinks/plugins/device_info/ios" + downloads_path_provider: + :path: ".symlinks/plugins/downloads_path_provider/ios" Flutter: :path: ".symlinks/flutter/ios" + flutter_downloader: + :path: ".symlinks/plugins/flutter_downloader/ios" flutter_exif_rotation: :path: ".symlinks/plugins/flutter_exif_rotation/ios" flutter_secure_storage: @@ -93,7 +103,9 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: device_info: 76ce0b32e13034d1883be4a382433648f9dcee63 + downloads_path_provider: d47f2d1ec01909f4c14643078120892270534966 Flutter: 9d0fac939486c9aba2809b7982dfdbb47a7b0296 + flutter_downloader: ff8f392c1b9d2a35f7c7578508e64504335fdab0 flutter_exif_rotation: 458778023267a1f0157ae8d9483474749990ce24 flutter_secure_storage: dbcc8ff35d99569c3a4d3b483afa3339416c1a78 FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 199bf340e..19a1bbee1 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -40,6 +40,8 @@ 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + A613FB172267A37400D5700D /* Permissions.m in Sources */ = {isa = PBXBuildFile; fileRef = A613FB162267A37400D5700D /* Permissions.m */; }; + A636FC3B2260E0C000572DD9 /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = A636FC3A2260E0BF00572DD9 /* libsqlite3.tbd */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -169,6 +171,27 @@ remoteGlobalIDString = 1CE8375F5D12E4C085E8D4CF09BCD280; remoteInfo = flutter_exif_rotation; }; + A613FB0D2267A32300D5700D /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 8980538D22047AAE00E47AD9 /* Pods.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = FA1F58DF894C00D1CB57E44CAC5E39D1; + remoteInfo = downloads_path_provider; + }; + A613FB0F2267A32300D5700D /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 8980538D22047AAE00E47AD9 /* Pods.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = AFCF19CC08266EA5356273B00796D3D7; + remoteInfo = flutter_downloader; + }; + A613FB112267A32300D5700D /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 8980538D22047AAE00E47AD9 /* Pods.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = D29DC8274C8441FD02113A00A303A14F; + remoteInfo = "flutter_downloader-FlutterDownloaderDatabase"; + }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -233,6 +256,9 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + A613FB162267A37400D5700D /* Permissions.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Permissions.m; sourceTree = ""; }; + A613FB182267A38700D5700D /* Permissions.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Permissions.h; sourceTree = ""; }; + A636FC3A2260E0BF00572DD9 /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = usr/lib/libsqlite3.tbd; sourceTree = SDKROOT; }; A6C34D3821BE816E00882F1E /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; AAF4B8238CF43C30122544EF /* Pods-OneSignalNotificationServiceExtension.release-production.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OneSignalNotificationServiceExtension.release-production.xcconfig"; path = "Pods/Target Support Files/Pods-OneSignalNotificationServiceExtension/Pods-OneSignalNotificationServiceExtension.release-production.xcconfig"; sourceTree = ""; }; B23196F4E53E7786037AC8B5 /* Pods-OneSignalNotificationServiceExtension.release-development.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OneSignalNotificationServiceExtension.release-development.xcconfig"; path = "Pods/Target Support Files/Pods-OneSignalNotificationServiceExtension/Pods-OneSignalNotificationServiceExtension.release-development.xcconfig"; sourceTree = ""; }; @@ -272,6 +298,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + A636FC3B2260E0C000572DD9 /* libsqlite3.tbd in Frameworks */, 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */, 3B80C3941E831B6300D905FE /* App.framework in Frameworks */, 26B90459017411DF809A7507 /* libPods-Runner.a in Frameworks */, @@ -298,6 +325,7 @@ 5ABC6138995F2182C962F35D /* Frameworks */ = { isa = PBXGroup; children = ( + A636FC3A2260E0BF00572DD9 /* libsqlite3.tbd */, 898053C422047AF700E47AD9 /* SystemConfiguration.framework */, 898053C222047AF100E47AD9 /* UserNotifications.framework */, 8980538D22047AAE00E47AD9 /* Pods.xcodeproj */, @@ -323,6 +351,9 @@ isa = PBXGroup; children = ( 8925094A222F0E0900455D87 /* libdevice_info.a */, + A613FB0E2267A32300D5700D /* libdownloads_path_provider.a */, + A613FB102267A32300D5700D /* libflutter_downloader.a */, + A613FB122267A32300D5700D /* FlutterDownloaderDatabase.bundle */, 89CF474422419BFD001BC50D /* libflutter_exif_rotation.a */, 8980539E22047AAF00E47AD9 /* libflutter_secure_storage.a */, 8928E31E22356450001DB32A /* libFMDB.a */, @@ -391,6 +422,7 @@ 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( + A613FB152267A32F00D5700D /* Plugins */, A6C34D3821BE816E00882F1E /* Runner.entitlements */, 37BDAB2A2170B9A900811BA5 /* development.plist */, 37BDAB2B2170B9AA00811BA5 /* production.plist */, @@ -415,6 +447,15 @@ name = "Supporting Files"; sourceTree = ""; }; + A613FB152267A32F00D5700D /* Plugins */ = { + isa = PBXGroup; + children = ( + A613FB162267A37400D5700D /* Permissions.m */, + A613FB182267A38700D5700D /* Permissions.h */, + ); + path = Plugins; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -650,6 +691,27 @@ remoteRef = 89CF474322419BFD001BC50D /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; + A613FB0E2267A32300D5700D /* libdownloads_path_provider.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libdownloads_path_provider.a; + remoteRef = A613FB0D2267A32300D5700D /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + A613FB102267A32300D5700D /* libflutter_downloader.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libflutter_downloader.a; + remoteRef = A613FB0F2267A32300D5700D /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + A613FB122267A32300D5700D /* FlutterDownloaderDatabase.bundle */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = FlutterDownloaderDatabase.bundle; + remoteRef = A613FB112267A32300D5700D /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; /* End PBXReferenceProxy section */ /* Begin PBXResourcesBuildPhase section */ @@ -684,12 +746,14 @@ "${PODS_ROOT}/Intercom/Intercom/Intercom.framework/Versions/A/Resources/Intercom.bundle", "${PODS_ROOT}/Intercom/Intercom/Intercom.framework/Versions/A/Resources/IntercomTranslations.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/TOCropViewController/TOCropViewControllerBundle.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/flutter_downloader/FlutterDownloaderDatabase.bundle", ); name = "[CP] Copy Pods Resources"; outputPaths = ( "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Intercom.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/IntercomTranslations.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/TOCropViewControllerBundle.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FlutterDownloaderDatabase.bundle", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -793,6 +857,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + A613FB172267A37400D5700D /* Permissions.m in Sources */, 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */, 97C146F31CF9000F007C117D /* main.m in Sources */, 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, diff --git a/ios/Runner/AppDelegate.m b/ios/Runner/AppDelegate.m index 00f589066..7c2da271e 100644 --- a/ios/Runner/AppDelegate.m +++ b/ios/Runner/AppDelegate.m @@ -1,12 +1,14 @@ #include "AppDelegate.h" #include "GeneratedPluginRegistrant.h" #import +#import "Plugins/Permissions.h" @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [GeneratedPluginRegistrant registerWithRegistry:self]; + [Permissions registerWithRegistrar:[self registrarForPlugin:@"Permission"]]; // Override point for customization after application launch. return [super application:application didFinishLaunchingWithOptions:launchOptions]; } diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 27fd7d079..68dba96ec 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -45,6 +45,7 @@ We use this permission to allow you to share photos/videos from your library. UIBackgroundModes + fetch remote-notification UILaunchStoryboardName diff --git a/ios/Runner/Plugins/Permissions.h b/ios/Runner/Plugins/Permissions.h new file mode 100644 index 000000000..d88c12a55 --- /dev/null +++ b/ios/Runner/Plugins/Permissions.h @@ -0,0 +1,4 @@ +#import + +@interface Permissions : NSObject +@end diff --git a/ios/Runner/Plugins/Permissions.m b/ios/Runner/Plugins/Permissions.m new file mode 100644 index 000000000..6512da42f --- /dev/null +++ b/ios/Runner/Plugins/Permissions.m @@ -0,0 +1,43 @@ +#import "Permissions.h" + +#import +#import + +@implementation Permissions + ++ (void) registerWithRegistrar:(NSObject*) registrar { + FlutterMethodChannel* channel = [FlutterMethodChannel methodChannelWithName:@"openbook.social/permissions" binaryMessenger:[registrar messenger]]; + Permissions* instance = [[Permissions alloc] init]; + [registrar addMethodCallDelegate:instance channel:channel]; +} + +- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { + NSString* method = [call method]; + if ([method isEqualToString:@"checkPermission"]) { + [self checkPermission:call.arguments[@"permission"] result:result]; + } else if ([method isEqualToString:@"requestPermission"]) { + [self requestPermission:call.arguments[@"permission"] result:result]; + } else { + result(FlutterMethodNotImplemented); + } +} + +- (void)checkPermission:(NSString*)permission result:(FlutterResult)result { + if ([permission isEqualToString:@"WRITE_EXTERNAL_STORAGE"]) { + // storage permission doesn't exist on iOS + result([NSNumber numberWithBool:YES]); + } else { + result(FlutterMethodNotImplemented); + } +} + +- (void)requestPermission:(NSString*)permission result:(FlutterResult)result { + if ([permission isEqualToString:@"WRITE_EXTERNAL_STORAGE"]) { + // storage permission doesn't exist on iOS + result([NSNumber numberWithBool:YES]); + } else { + result(FlutterMethodNotImplemented); + } +} + +@end diff --git a/lib/pages/home/modals/zoomable_photo.dart b/lib/pages/home/modals/zoomable_photo.dart index 6f557e87b..2223ccfe4 100644 --- a/lib/pages/home/modals/zoomable_photo.dart +++ b/lib/pages/home/modals/zoomable_photo.dart @@ -1,10 +1,12 @@ import 'package:Openbook/widgets/icon.dart'; import 'package:Openbook/widgets/page_scaffold.dart'; +import 'package:Openbook/plugins/permissions/permissions.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_advanced_networkimage/provider.dart'; import 'package:photo_view/photo_view.dart'; -import 'package:pigment/pigment.dart'; +import 'package:flutter_downloader/flutter_downloader.dart'; +import 'package:downloads_path_provider/downloads_path_provider.dart'; import "dart:math" show pi; class OBZoomablePhotoModal extends StatefulWidget { @@ -99,7 +101,27 @@ class OBZoomablePhotoModalState extends State _checkIsDismissible(); }, ), - _buildCloseButton() + Positioned( + bottom: 50, + left: 0, + right: 0, + child: AnimatedOpacity( + opacity: _isDismissible ? 1 : 0, + duration: Duration(milliseconds: 300), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.max, + children: [ + _buildDownloadButton(), + const SizedBox( + width: 20, + ), + _buildCloseButton(), + ], + ), + ), + ) ], )), onWillPop: _dismissModalNoPop, @@ -262,31 +284,47 @@ class OBZoomablePhotoModalState extends State } Widget _buildCloseButton() { - return Positioned( - bottom: 50, - left: 0, - right: 0, - child: SafeArea( - child: Column( - children: [ - GestureDetector( - onTapDown: (tap) { - Navigator.pop(context); - }, - child: Container( - padding: EdgeInsets.all(10), - decoration: BoxDecoration( - color: Colors.black87, - borderRadius: BorderRadius.circular(50)), - child: const OBIcon( - OBIcons.close, - size: OBIconSize.large, - color: Colors.white, - ), - ), - ) - ], - )), + return GestureDetector( + onTapDown: (tap) { + Navigator.pop(context); + }, + child: Container( + padding: EdgeInsets.all(10), + decoration: BoxDecoration( + color: Colors.black87, borderRadius: BorderRadius.circular(50)), + child: const OBIcon( + OBIcons.close, + size: OBIconSize.large, + color: Colors.white, + ), + ), + ); + } + + Widget _buildDownloadButton() { + return GestureDetector( + onTapDown: (tap) async { + bool hasPermission = await Permissions.checkOrRequestPermission( + Permission.WriteExternalStorage); + if (!hasPermission) { + return; + } + var directory = await DownloadsPathProvider.downloadsDirectory; + Uri uri = Uri.parse(widget.imageUrl); + await FlutterDownloader.enqueue( + url: widget.imageUrl, + savedDir: directory.path, + showNotification: true, + openFileFromNotification: true, + fileName: uri.pathSegments.last); + }, + child: Container( + padding: EdgeInsets.all(10), + decoration: BoxDecoration( + color: Colors.black87, borderRadius: BorderRadius.circular(50)), + child: const OBIcon(OBIcons.download, + size: OBIconSize.large, color: Colors.white), + ), ); } diff --git a/lib/plugins/permissions/permissions.dart b/lib/plugins/permissions/permissions.dart new file mode 100644 index 000000000..9e072495b --- /dev/null +++ b/lib/plugins/permissions/permissions.dart @@ -0,0 +1,33 @@ +import 'dart:async'; + +import 'package:flutter/services.dart'; + +class Permissions { + static const MethodChannel _channel = const MethodChannel('openbook.social/permissions'); + static Future checkPermission(Permission permission) { + return _channel.invokeMethod('checkPermission', { 'permission': getPermissionString(permission) }); + } + static Future requestPermission(Permission permission) { + return _channel.invokeMethod('requestPermission', { 'permission': getPermissionString(permission) }); + } + static Future checkOrRequestPermission(Permission permission) async { + if (await Permissions.checkPermission(permission)) { + return true; + } else { + return await Permissions.requestPermission(permission); + } + } +} + +enum Permission { + WriteExternalStorage, +} + +String getPermissionString(Permission permission) { + switch (permission) { + case Permission.WriteExternalStorage: + return "WRITE_EXTERNAL_STORAGE"; + default: + return "ERROR"; + } +} \ No newline at end of file diff --git a/lib/widgets/icon.dart b/lib/widgets/icon.dart index 686f0ecc1..bdb358a71 100644 --- a/lib/widgets/icon.dart +++ b/lib/widgets/icon.dart @@ -207,6 +207,8 @@ class OBIcons { static const slackChannel = OBIconData(nativeIcon: Icons.tag_faces); static const dashboard = OBIconData(nativeIcon: Icons.dashboard); static const themes = OBIconData(nativeIcon: Icons.format_paint); + static const angelBadge = OBIconData(nativeIcon: Icons.stars); + static const download = OBIconData(nativeIcon: Icons.file_download); static const chat = OBIconData(nativeIcon: Icons.chat_bubble); static const invite = OBIconData(nativeIcon: Icons.card_giftcard); static const success = OBIconData(filename: 'success-icon.png'); diff --git a/pubspec.lock b/pubspec.lock index 93ab8508e..a74cd8c1d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -115,6 +115,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.4.0+1" + downloads_path_provider: + dependency: "direct main" + description: + name: downloads_path_provider + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.0" flutter: dependency: "direct main" description: flutter @@ -141,6 +148,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.2.2" + flutter_downloader: + dependency: "direct main" + description: + name: flutter_downloader + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.7" flutter_exif_rotation: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index ef97322c4..267eb8e69 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -43,6 +43,8 @@ dependencies: image_picker: 0.5.0+3 image_cropper: ^1.0.0 shimmer: ^1.0.0 + flutter_downloader: ^1.1.7 + downloads_path_provider: ^0.1.0 share: ^0.6.1 flutter: sdk: flutter