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