diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist
index 0630271..2a113c4 100644
--- a/ios/Runner/Info.plist
+++ b/ios/Runner/Info.plist
@@ -58,6 +58,8 @@
NSCameraUsageDescription
We need access to your camera to take photos and videos for sharing in the app.
+ NSPhotoLibraryUsageDescription
+ We need access your photo library to allow you to upload, view, and manage photos within the app.
NSAppTransportSecurity
NSAllowsArbitraryLoads
diff --git a/lib/flavored/web_view.f.dart b/lib/flavored/web_view.f.dart
index e8e7de1..8e137a6 100644
--- a/lib/flavored/web_view.f.dart
+++ b/lib/flavored/web_view.f.dart
@@ -10,16 +10,15 @@ import 'package:humhub/app_flavored.dart';
import 'package:humhub/flavored/models/humhub.f.dart';
import 'package:humhub/util/auth_in_app_browser.dart';
import 'package:humhub/models/channel_message.dart';
+import 'package:humhub/util/black_list_rules.dart';
import 'package:humhub/util/const.dart';
import 'package:humhub/util/extensions.dart';
import 'package:humhub/util/loading_provider.dart';
import 'package:humhub/util/notifications/init_from_push.dart';
-import 'package:humhub/util/notifications/plugin.dart';
import 'package:humhub/util/push/provider.dart';
import 'package:humhub/util/show_dialog.dart';
import 'package:humhub/util/web_view_global_controller.dart';
import 'package:loggy/loggy.dart';
-import 'package:permission_handler/permission_handler.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:humhub/util/file_handler.dart';
@@ -37,8 +36,9 @@ class FlavoredWebViewState extends ConsumerState {
late AuthInAppBrowser _authBrowser;
HeadlessInAppWebView? headlessWebView;
late HumHubF instance;
-
+ final _scaffoldKey = GlobalKey();
late PullToRefreshController pullToRefreshController;
+ late double downloadProgress = 0;
@override
void initState() {
@@ -73,6 +73,7 @@ class FlavoredWebViewState extends ConsumerState {
return WillPopScope(
onWillPop: () => exitApp(context, ref),
child: Scaffold(
+ key: _scaffoldKey,
backgroundColor: HexColor(instance.manifest.themeColor),
body: SafeArea(
bottom: false,
@@ -121,11 +122,22 @@ class FlavoredWebViewState extends ConsumerState {
InAppWebViewController controller, NavigationAction action) async {
// 1st check if url is not def. app url and open it in a browser or inApp.
WebViewGlobalController.ajaxSetHeaders(headers: instance.customHeaders);
- final url = action.request.url!.origin;
+ final url = action.request.url!.rawValue;
+
+ /// First BLOCK everything that rules out as blocked.
+ if (BlackListRules.check(url)) {
+ return NavigationActionPolicy.CANCEL;
+ }
+ // For SSO
if (!url.startsWith(instance.manifest.baseUrl) && action.isForMainFrame) {
_authBrowser.launchUrl(action.request);
return NavigationActionPolicy.CANCEL;
}
+ // For all other external links
+ if (!url.startsWith(instance.manifest.baseUrl) && !action.isForMainFrame) {
+ await launchUrl(action.request.url!.uriValue, mode: LaunchMode.externalApplication);
+ return NavigationActionPolicy.CANCEL;
+ }
// 2nd Append customHeader if url is in app redirect and CANCEL the requests without custom headers
if (Platform.isAndroid ||
action.navigationType == NavigationType.LINK_ACTIVATED ||
@@ -162,7 +174,7 @@ class FlavoredWebViewState extends ConsumerState {
Future _onCreateWindow(InAppWebViewController controller, CreateWindowAction createWindowAction) async {
final urlToOpen = createWindowAction.request.url;
if (urlToOpen == null) return Future.value(false);
- if (WebViewGlobalController.openCreateWindowInWebView(ref, urlToOpen.rawValue)) {
+ if (WebViewGlobalController.openCreateWindowInWebView(url: urlToOpen.rawValue, manifest: instance.manifest)) {
controller.loadUrl(urlRequest: createWindowAction.request);
return Future.value(false);
}
@@ -219,12 +231,6 @@ class FlavoredWebViewState extends ConsumerState {
headers: instance.customHeaders,
);
}
- var status = await Permission.notification.status;
- // status.isDenied: The user has previously denied the notification permission
- // !status.isGranted: The user has never been asked for the notification permission
- bool wasAskedBefore = await NotificationPlugin.hasAskedPermissionBefore();
- // ignore: use_build_context_synchronously
- if (status != PermissionStatus.granted && !wasAskedBefore) ShowDialog.of(context).notificationPermission();
break;
case ChannelAction.updateNotificationCount:
if (message.count != null) FlutterAppBadger.updateBadgeCount(message.count!);
@@ -275,30 +281,111 @@ class FlavoredWebViewState extends ConsumerState {
}
void _onDownloadStartRequest(InAppWebViewController controller, DownloadStartRequest downloadStartRequest) async {
+ PersistentBottomSheetController? persistentController;
+ //bool isBottomSheetVisible = false;
+
+ // Initialize the download progress
+ downloadProgress = 0;
+
+ // Timer to control when to show the bottom sheet
+ Timer? downloadTimer;
+ bool isDone = false;
+
FileHandler(
- downloadStartRequest: downloadStartRequest,
- controller: controller,
- onSuccess: (File file, String filename) {
- scaffoldMessengerStateKey.currentState?.showSnackBar(
- SnackBar(
- content: Text('${AppLocalizations.of(context)!.file_download}: $filename'),
- action: SnackBarAction(
- label: AppLocalizations.of(context)!.open,
- onPressed: () {
- // Open the downloaded file
- OpenFile.open(file.path);
- },
- ),
- ),
- );
- },
- onError: (er) {
- scaffoldMessengerStateKey.currentState?.showSnackBar(
- SnackBar(
- content: Text(AppLocalizations.of(context)!.generic_error),
+ downloadStartRequest: downloadStartRequest,
+ controller: controller,
+ onSuccess: (File file, String filename) async {
+ // Hide the bottom sheet if it is visible
+ Navigator.popUntil(context, ModalRoute.withName(WebViewF.path));
+ isDone = true;
+ scaffoldMessengerStateKey.currentState?.showSnackBar(
+ SnackBar(
+ content: Text('${AppLocalizations.of(context)!.file_download}: $filename'),
+ action: SnackBarAction(
+ label: AppLocalizations.of(context)!.open,
+ onPressed: () {
+ OpenFile.open(file.path);
+ },
),
- );
- }).download();
+ ),
+ );
+ },
+ onStart: () async {
+ downloadProgress = 0;
+ // Start the timer for 1 second
+ downloadTimer = Timer(const Duration(seconds: 1), () {
+ // Show the persistent bottom sheet if not already shown
+ if (!isDone) {
+ persistentController = _scaffoldKey.currentState!.showBottomSheet((context) {
+ return Container(
+ width: MediaQuery.of(context).size.width,
+ height: 100,
+ color: const Color(0xff313033),
+ child: Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Text(
+ "${AppLocalizations.of(context)!.downloading}...",
+ style: const TextStyle(fontWeight: FontWeight.bold, color: Colors.white),
+ ),
+ Stack(
+ alignment: Alignment.center,
+ children: [
+ CircularProgressIndicator(
+ value: downloadProgress / 100,
+ backgroundColor: Colors.grey,
+ color: Colors.green,
+ ),
+ downloadProgress.toStringAsFixed(0) == "100"
+ ? const Icon(
+ Icons.check,
+ color: Colors.green,
+ size: 25,
+ )
+ : Text(
+ downloadProgress.toStringAsFixed(0),
+ style: const TextStyle(
+ fontWeight: FontWeight.bold,
+ color: Colors.white,
+ ),
+ ),
+ ],
+ ),
+ ],
+ ),
+ ),
+ );
+ });
+ }
+ });
+ },
+ onProgress: (progress) async {
+ downloadProgress = progress;
+ if (persistentController != null) {
+ persistentController!.setState!(() {});
+ }
+ },
+ onError: (er) {
+ downloadTimer?.cancel();
+ if (persistentController != null) {
+ Navigator.popUntil(context, ModalRoute.withName(WebViewF.path));
+ }
+ scaffoldMessengerStateKey.currentState?.showSnackBar(
+ SnackBar(
+ content: Text(AppLocalizations.of(context)!.generic_error),
+ ),
+ );
+ },
+ ).download();
+
+ // Ensure to cancel the timer if download finishes before 1 second
+ Future.delayed(const Duration(seconds: 1), () {
+ if (downloadProgress >= 100) {
+ downloadTimer?.cancel();
+ }
+ });
}
@override
diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb
index 670f860..8495482 100644
--- a/lib/l10n/app_de.arb
+++ b/lib/l10n/app_de.arb
@@ -23,9 +23,6 @@
"more_info_pro_edition": "Professional Edition",
"more_info_pro_edition_url": "https://www.humhub.com/de/professional-edition",
- "notification_permission_popup_title": "Benachrichtigungen empfangen",
- "notification_permission_popup_content": "Bitte aktiviere Benachrichtigungen für die HumHub App in deinen Geräteeinstellungen.",
-
"connectivity_popup_title": "Keine Verbindung möglich.",
"connectivity_popup_content": "Bitte überprüfe deine Internetverbindung und versuche es erneut.",
@@ -37,6 +34,7 @@
"enable_permissions": "Berechtigungen aktivieren.",
+ "downloading": "Herunterladen",
"settings": "Einstellungen",
"connect": "Verbinden",
"open": "Öffnen",
diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb
index 45d2fcb..59501ce 100644
--- a/lib/l10n/app_en.arb
+++ b/lib/l10n/app_en.arb
@@ -24,9 +24,6 @@
"more_info_pro_edition": "Professional Edition",
"more_info_pro_edition_url": "https://www.humhub.com/en/professional-edition",
- "notification_permission_popup_title": "Notification Permission",
- "notification_permission_popup_content": "Please enable notifications for HumHub in the device settings",
-
"connectivity_popup_title": "No Connection",
"connectivity_popup_content": "Please check your internet connection and try again.",
@@ -38,6 +35,7 @@
"enable_permissions": "Enable permissions.",
+ "downloading": "Downloading",
"settings": "Settings",
"connect": "Connect",
"open": "Open",
diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb
index 7b13c99..f1ca775 100644
--- a/lib/l10n/app_fr.arb
+++ b/lib/l10n/app_fr.arb
@@ -23,9 +23,6 @@
"more_info_pro_edition": "Édition Professionnelle",
"more_info_pro_edition_url": "https://www.humhub.com/fr/edition-professionnelle",
- "notification_permission_popup_title": "Permission de notification",
- "notification_permission_popup_content": "Veuillez activer les notifications pour HumHub dans les paramètres de l'appareil.",
-
"connectivity_popup_title": "Pas de connexion",
"connectivity_popup_content": "Veuillez vérifier votre connexion Internet et réessayer.",
@@ -37,6 +34,7 @@
"enable_permissions": "Activer les autorisations.",
+ "downloading": "Téléchargement",
"settings": "Paramètres",
"connect": "Connecter",
"open": "Ouvrir",
diff --git a/lib/pages/web_view.dart b/lib/pages/web_view.dart
index 1a0d81d..4451938 100644
--- a/lib/pages/web_view.dart
+++ b/lib/pages/web_view.dart
@@ -18,16 +18,13 @@ import 'package:humhub/util/extensions.dart';
import 'package:humhub/util/file_handler.dart';
import 'package:humhub/util/loading_provider.dart';
import 'package:humhub/util/notifications/init_from_push.dart';
-import 'package:humhub/util/notifications/plugin.dart';
import 'package:humhub/util/providers.dart';
import 'package:humhub/util/openers/universal_opener_controller.dart';
import 'package:humhub/util/push/provider.dart';
import 'package:humhub/util/router.dart';
-import 'package:humhub/util/show_dialog.dart';
import 'package:humhub/util/web_view_global_controller.dart';
import 'package:loggy/loggy.dart';
import 'package:open_file_plus/open_file_plus.dart';
-import 'package:permission_handler/permission_handler.dart';
import 'package:humhub/util/router.dart' as m;
import 'package:url_launcher/url_launcher.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
@@ -41,12 +38,14 @@ class WebView extends ConsumerStatefulWidget {
}
class WebViewAppState extends ConsumerState {
+ final _scaffoldKey = GlobalKey();
late AuthInAppBrowser _authBrowser;
late Manifest _manifest;
late URLRequest _initialRequest;
late PullToRefreshController _pullToRefreshController;
HeadlessInAppWebView? _headlessWebView;
bool _isInit = false;
+ late double downloadProgress = 0;
final _settings = InAppWebViewSettings(
useShouldOverrideUrlLoading: true,
@@ -91,6 +90,7 @@ class WebViewAppState extends ConsumerState {
@override
Widget build(BuildContext context) {
return Scaffold(
+ key: _scaffoldKey,
backgroundColor: HexColor(_manifest.themeColor),
body: SafeArea(
bottom: false,
@@ -144,17 +144,18 @@ class WebViewAppState extends ConsumerState {
InAppWebViewController controller, NavigationAction action) async {
WebViewGlobalController.ajaxSetHeaders(headers: ref.read(humHubProvider).customHeaders);
- //Open in external browser
final url = action.request.url!.rawValue;
/// First BLOCK everything that rules out as blocked.
if (BlackListRules.check(url)) {
return NavigationActionPolicy.CANCEL;
}
+ // For SSO
if (!url.startsWith(_manifest.baseUrl) && action.isForMainFrame) {
_authBrowser.launchUrl(action.request);
return NavigationActionPolicy.CANCEL;
}
- if (!action.isForMainFrame) {
+ // For all other external links
+ if (!url.startsWith(_manifest.baseUrl) && !action.isForMainFrame) {
await launchUrl(action.request.url!.uriValue, mode: LaunchMode.externalApplication);
return NavigationActionPolicy.CANCEL;
}
@@ -198,7 +199,10 @@ class WebViewAppState extends ConsumerState {
WebUri? urlToOpen = createWindowAction.request.url;
if (urlToOpen == null) return Future.value(false);
- if (WebViewGlobalController.openCreateWindowInWebView(ref, urlToOpen.rawValue)) {
+ if (WebViewGlobalController.openCreateWindowInWebView(
+ url: urlToOpen.rawValue,
+ manifest: ref.read(humHubProvider).manifest!,
+ )) {
controller.loadUrl(urlRequest: createWindowAction.request);
return Future.value(false);
}
@@ -265,12 +269,6 @@ class WebViewAppState extends ConsumerState {
headers: ref.read(humHubProvider).customHeaders,
);
}
- var status = await Permission.notification.status;
- // status.isDenied: The user has previously denied the notification permission
- // !status.isGranted: The user has never been asked for the notification permission
- bool wasAskedBefore = await NotificationPlugin.hasAskedPermissionBefore();
- // ignore: use_build_context_synchronously
- if (status != PermissionStatus.granted && !wasAskedBefore) ShowDialog.of(context).notificationPermission();
break;
case ChannelAction.updateNotificationCount:
if (message.count != null) FlutterAppBadger.updateBadgeCount(message.count!);
@@ -324,30 +322,112 @@ class WebViewAppState extends ConsumerState {
}
void _onDownloadStartRequest(InAppWebViewController controller, DownloadStartRequest downloadStartRequest) async {
+ PersistentBottomSheetController? persistentController;
+ //bool isBottomSheetVisible = false;
+
+ // Initialize the download progress
+ downloadProgress = 0;
+
+ // Timer to control when to show the bottom sheet
+ Timer? downloadTimer;
+ bool isDone = false;
+
FileHandler(
- downloadStartRequest: downloadStartRequest,
- controller: controller,
- onSuccess: (File file, String filename) {
- scaffoldMessengerStateKey.currentState?.showSnackBar(
- SnackBar(
- content: Text('${AppLocalizations.of(context)!.file_download}: $filename'),
- action: SnackBarAction(
- label: AppLocalizations.of(context)!.open,
- onPressed: () {
- // Open the downloaded file
- OpenFile.open(file.path);
- },
- ),
- ),
- );
- },
- onError: (er) {
- scaffoldMessengerStateKey.currentState?.showSnackBar(
- SnackBar(
- content: Text(AppLocalizations.of(context)!.generic_error),
+ downloadStartRequest: downloadStartRequest,
+ controller: controller,
+ onSuccess: (File file, String filename) async {
+ // Hide the bottom sheet if it is visible
+ Navigator.popUntil(context, ModalRoute.withName(WebView.path));
+ isDone = true;
+ scaffoldMessengerStateKey.currentState?.showSnackBar(
+ SnackBar(
+ content: Text('${AppLocalizations.of(context)!.file_download}: $filename'),
+ action: SnackBarAction(
+ label: AppLocalizations.of(context)!.open,
+ onPressed: () {
+ //file.open();
+ OpenFile.open(file.path);
+ },
),
- );
- }).download();
+ ),
+ );
+ },
+ onStart: () async {
+ downloadProgress = 0;
+ // Start the timer for 1 second
+ downloadTimer = Timer(const Duration(seconds: 1), () {
+ // Show the persistent bottom sheet if not already shown
+ if (!isDone) {
+ persistentController = _scaffoldKey.currentState!.showBottomSheet((context) {
+ return Container(
+ width: MediaQuery.of(context).size.width,
+ height: 100,
+ color: const Color(0xff313033),
+ child: Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Text(
+ "${AppLocalizations.of(context)!.downloading}...",
+ style: const TextStyle(fontWeight: FontWeight.bold, color: Colors.white),
+ ),
+ Stack(
+ alignment: Alignment.center,
+ children: [
+ CircularProgressIndicator(
+ value: downloadProgress / 100,
+ backgroundColor: Colors.grey,
+ color: Colors.green,
+ ),
+ downloadProgress.toStringAsFixed(0) == "100"
+ ? const Icon(
+ Icons.check,
+ color: Colors.green,
+ size: 25,
+ )
+ : Text(
+ downloadProgress.toStringAsFixed(0),
+ style: const TextStyle(
+ fontWeight: FontWeight.bold,
+ color: Colors.white,
+ ),
+ ),
+ ],
+ ),
+ ],
+ ),
+ ),
+ );
+ });
+ }
+ });
+ },
+ onProgress: (progress) async {
+ downloadProgress = progress;
+ if (persistentController != null) {
+ persistentController!.setState!(() {});
+ }
+ },
+ onError: (er) {
+ downloadTimer?.cancel();
+ if (persistentController != null) {
+ Navigator.popUntil(context, ModalRoute.withName(WebView.path));
+ }
+ scaffoldMessengerStateKey.currentState?.showSnackBar(
+ SnackBar(
+ content: Text(AppLocalizations.of(context)!.generic_error),
+ ),
+ );
+ },
+ ).download();
+
+ // Ensure to cancel the timer if download finishes before 1 second
+ Future.delayed(const Duration(seconds: 1), () {
+ if (downloadProgress >= 100) {
+ downloadTimer?.cancel();
+ }
+ });
}
@override
diff --git a/lib/util/file_handler.dart b/lib/util/file_handler.dart
index 0f9e3ed..bc0f9aa 100644
--- a/lib/util/file_handler.dart
+++ b/lib/util/file_handler.dart
@@ -12,35 +12,68 @@ class FileHandler {
final String? filename;
final Function(Exception ex)? onError;
final Function(File file, String filename) onSuccess;
+ final Function(double progress)? onProgress;
+ final Function()? onStart;
static const String _jsCode = """
- function downloadFile(url) {
- fetch(url, {
- headers: {
- // Include necessary headers for authentication if needed
- 'Authorization': 'Bearer '
- }
- })
- .then(response => response.blob())
- .then(blob => {
- const reader = new FileReader();
- reader.readAsDataURL(blob);
- reader.onloadend = function() {
- const base64data = reader.result;
- // Send the base64 content to Flutter
- window.flutter_inappwebview.callHandler('downloadFile', base64data);
- }
- })
- .catch(error => console.error('Error downloading file:', error));
- }
+ function downloadFile(url) {
+ fetch(url, {
+ headers: {
+ // Include necessary headers for authentication if needed
+ 'Authorization': 'Bearer '
+ }
+ })
+ .then(response => {
+ const reader = response.body.getReader();
+ const contentLength = response.headers.get('Content-Length');
+ let receivedLength = 0;
+ let chunks = []; // array of received binary chunks (comprises the body)
+
+ return new ReadableStream({
+ start(controller) {
+ function push() {
+ reader.read().then(({ done, value }) => {
+ if (done) {
+ const blob = new Blob(chunks); // Create blob from chunks
+ const fileReader = new FileReader();
+ fileReader.readAsDataURL(blob);
+ fileReader.onloadend = function() {
+ const base64data = fileReader.result;
+ // Send the base64 content to Flutter
+ window.flutter_inappwebview.callHandler('downloadFile', base64data);
+ }
+ return;
+ }
+
+ chunks.push(value); // Store received chunk
+ receivedLength += value.length;
+
+ if (contentLength) {
+ // Calculate progress percentage
+ const progress = (receivedLength / contentLength) * 100;
+ // Send the progress to Flutter
+ window.flutter_inappwebview.callHandler('onProgress', progress);
+ }
+
+ push(); // Call the push function again to read the next chunk
+ });
+ }
+
+ push(); // Start reading the data
+ }
+ });
+ })
+ .catch(error => console.error('Error downloading file:', error));
+ }
""";
- const FileHandler({
- required this.controller,
- required this.downloadStartRequest,
- required this.onSuccess,
- this.filename,
- this.onError,
- });
+ const FileHandler(
+ {required this.controller,
+ required this.downloadStartRequest,
+ required this.onSuccess,
+ this.filename,
+ this.onError,
+ this.onProgress,
+ this.onStart});
download() {
PermissionHandler.runWithPermissionCheck(
@@ -51,6 +84,9 @@ class FileHandler {
_download() async {
try {
+ if (onStart != null) {
+ onStart!();
+ }
await controller.evaluateJavascript(source: _jsCode);
await controller.evaluateJavascript(source: "downloadFile('${downloadStartRequest.url.toString()}');");
controller.addJavaScriptHandler(
@@ -60,11 +96,19 @@ class FileHandler {
var (file, filename) = await _saveFile(base64Data);
onSuccess(file, filename);
});
+
+ controller.addJavaScriptHandler(
+ handlerName: 'onProgress',
+ callback: (args) {
+ double progress = double.parse(args[0].toString());
+ if (onProgress != null) {
+ onProgress!(progress);
+ }
+ });
} catch (er) {
if (er is Exception && onError != null) {
onError!(er);
}
- //rethrow;
}
}
diff --git a/lib/util/notifications/plugin.dart b/lib/util/notifications/plugin.dart
index 257a1f9..f79ed02 100644
--- a/lib/util/notifications/plugin.dart
+++ b/lib/util/notifications/plugin.dart
@@ -1,7 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:humhub/util/notifications/service.dart';
-import 'package:shared_preferences/shared_preferences.dart';
class NotificationPlugin extends StatefulWidget {
final Widget child;
@@ -18,14 +17,6 @@ class NotificationPlugin extends StatefulWidget {
return plugin!;
}
- static Future hasAskedPermissionBefore() async {
- String key = 'was_asked_before';
- SharedPreferences prefs = await SharedPreferences.getInstance();
- var data = prefs.getBool(key) ?? false;
- prefs.setBool(key, true);
- return data;
- }
-
@override
NotificationPluginState createState() => NotificationPluginState();
}
diff --git a/lib/util/permission_handler.dart b/lib/util/permission_handler.dart
index 0217e21..786957c 100644
--- a/lib/util/permission_handler.dart
+++ b/lib/util/permission_handler.dart
@@ -6,10 +6,37 @@ import 'package:permission_handler/permission_handler.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class PermissionHandler {
- // Static method that takes a list of permissions and handles requests
- static Future requestPermissions(List permissions) async => await permissions.request();
+ /// A static method that takes a list of permissions and handles permission requests.
+ /// It only requests permissions that have not yet been granted ([permission.isGranted]) and are not permanently denied ([permission.isPermanentlyDenied]).
+ ///
+ /// **Android:** This will be triggered every time if the user closes the permission modal without providing input.
+ /// **iOS:** Permissions will only be requested once, as the modal is required before continuing.
+ /// [permissions] - A list of [Permission] objects to be requested.
+ /// This method ensures that unnecessary permission requests are avoided, improving user experience.
+ static Future requestPermissions(List permissions) async {
+ // Filter permissions asynchronously
+ List toRequest = [];
+ for (var permission in permissions) {
+ if (!(await permission.isGranted) && !(await permission.isPermanentlyDenied)) {
+ toRequest.add(permission);
+ }
+ }
+ // Request the permissions that are not granted
+ if (toRequest.isNotEmpty) {
+ await toRequest.request();
+ }
+ }
- // Static method to check permissions before executing a function
+ /// A static method to check if all the necessary permissions are granted before executing a given action.
+ ///
+ /// If all required permissions are granted, the provided [action] is executed.
+ /// If any permissions are missing on Android, a [SnackBar] is shown informing the user to enable the required permissions.
+ /// **Parameters:**
+ /// - [permissions]: A list of [Permission] objects that need to be checked.
+ /// - [action]: A callback function that will be executed if the required permissions are granted.
+ /// **Android:** If the user closes the permissions dialog without providing input, the method won't proceed with the action.
+ /// **iOS:** Permissions are required before continuing, so the action will proceed automatically once permissions are granted.
+ /// If permissions are not granted, the user will be prompted to open the app settings.
static Future runWithPermissionCheck({
required List permissions,
required Function action,
diff --git a/lib/util/show_dialog.dart b/lib/util/show_dialog.dart
index 19a20cd..e4c0b18 100644
--- a/lib/util/show_dialog.dart
+++ b/lib/util/show_dialog.dart
@@ -1,6 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
-import 'package:app_settings/app_settings.dart';
class ShowDialog {
final BuildContext context;
@@ -11,31 +10,6 @@ class ShowDialog {
return ShowDialog(context);
}
- void notificationPermission() {
- showDialog(
- context: context,
- builder: (context) => AlertDialog(
- title: Text(AppLocalizations.of(context)!.notification_permission_popup_title),
- content: Text(AppLocalizations.of(context)!.notification_permission_popup_content),
- actions: [
- TextButton(
- child: Text(AppLocalizations.of(context)!.enable),
- onPressed: () {
- AppSettings.openAppSettings();
- Navigator.pop(context);
- },
- ),
- TextButton(
- child: Text(AppLocalizations.of(context)!.skip),
- onPressed: () {
- Navigator.pop(context);
- },
- ),
- ],
- ),
- );
- }
-
noInternetPopup() {
showDialog(
context: context,
diff --git a/lib/util/web_view_global_controller.dart b/lib/util/web_view_global_controller.dart
index 25d5176..8bd7ba1 100644
--- a/lib/util/web_view_global_controller.dart
+++ b/lib/util/web_view_global_controller.dart
@@ -1,8 +1,6 @@
import 'dart:convert';
-
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
-import 'package:flutter_riverpod/flutter_riverpod.dart';
-import 'package:humhub/util/providers.dart';
+import 'package:humhub/models/manifest.dart';
class WebViewGlobalController {
static InAppWebViewController? _value;
@@ -21,8 +19,8 @@ class WebViewGlobalController {
/// [ref] is reference to the app state.
/// [url] is the URL to evaluate.
/// @return `true` if the URL should open in a new window, `false` otherwise.
- static bool openCreateWindowInWebView(WidgetRef ref, String url) {
- String? baseUrl = ref.read(humHubProvider).manifest?.baseUrl;
+ static bool openCreateWindowInWebView({required String url, required Manifest manifest}) {
+ String? baseUrl = manifest.baseUrl;
if (url.startsWith('$baseUrl/file/file/download')) return true;
if (url.startsWith('$baseUrl/u')) return true;
if (url.startsWith('$baseUrl/s')) return true;