diff --git a/.github/sync.yml b/.github/sync.yml
new file mode 100644
index 0000000..cd8991d
--- /dev/null
+++ b/.github/sync.yml
@@ -0,0 +1,30 @@
+humhub/app@master:
+ - source: assets/
+ deleteOrphaned: true
+ exclude: |
+ .env
+ - source: lib/
+ deleteOrphaned: true
+ - source: test/
+ deleteOrphaned: true
+ - .gitignore
+ - .metadata
+ - analysis_options.yaml
+ - l10n.yaml
+ - pubspec.lock
+ - pubspec.yaml
+humhub/app-flavored@master:
+ - source: assets/
+ deleteOrphaned: true
+ exclude: |
+ .env
+ - source: lib/
+ deleteOrphaned: true
+ - source: test/
+ deleteOrphaned: true
+ - .gitignore
+ - .metadata
+ - analysis_options.yaml
+ - l10n.yaml
+ - pubspec.lock
+ - pubspec.yaml
\ No newline at end of file
diff --git a/.github/workflows/sync-to-flavored-repos.yml b/.github/workflows/sync-to-flavored-repos.yml
new file mode 100644
index 0000000..2047c2c
--- /dev/null
+++ b/.github/workflows/sync-to-flavored-repos.yml
@@ -0,0 +1,17 @@
+name: Sync HumHub app with app-flavored
+
+on:
+ push:
+ branches:
+ - 'master'
+
+jobs:
+ sync:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout Repository
+ uses: actions/checkout@v2
+ - name: Run GitHub File Sync
+ uses: BetaHuhn/repo-file-sync-action@v1
+ with:
+ GH_PAT: ${{ secrets.GH_PAT }}
\ No newline at end of file
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 09fba76..69ae072 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -6,6 +6,9 @@
+
Bool {
+ if #available(iOS 10.0, *) {
+ UNUserNotificationCenter.current().delegate = self as UNUserNotificationCenterDelegate
+ }
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
diff --git a/lib/app_flavored.dart b/lib/app_flavored.dart
new file mode 100644
index 0000000..759f132
--- /dev/null
+++ b/lib/app_flavored.dart
@@ -0,0 +1,53 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:humhub/flavored/models/humhub.f.dart';
+import 'package:humhub/flavored/util/intent_plugin.f.dart';
+import 'package:humhub/flavored/util/router.f.dart';
+import 'package:humhub/util/loading_provider.dart';
+import 'package:humhub/util/notifications/plugin.dart';
+import 'package:humhub/util/override_locale.dart';
+import 'package:humhub/util/push/push_plugin.dart';
+import 'package:humhub/util/storage_service.dart';
+import 'package:flutter_gen/gen_l10n/app_localizations.dart';
+
+class FlavoredApp extends ConsumerStatefulWidget {
+ const FlavoredApp({super.key});
+
+ @override
+ FlavoredAppState createState() => FlavoredAppState();
+}
+
+class FlavoredAppState extends ConsumerState {
+ @override
+ Widget build(BuildContext context) {
+ SecureStorageService.clearSecureStorageOnReinstall();
+ return IntentPluginF(
+ child: NotificationPlugin(
+ child: PushPlugin(
+ child: OverrideLocale(
+ builder: (overrideLocale) => Builder(
+ builder: (context) => MaterialApp(
+ debugShowCheckedModeBanner: false,
+ initialRoute: RouterF.initRoute,
+ routes: RouterF.routes,
+ localizationsDelegates: AppLocalizations.localizationsDelegates,
+ supportedLocales: AppLocalizations.supportedLocales,
+ navigatorKey: navigatorKeyF,
+ builder: (context, child) => LoadingProvider(
+ child: child!,
+ ),
+ theme: ThemeData(
+ fontFamily: 'OpenSans',
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ );
+ }
+}
+
+final humHubFProvider = FutureProvider((ref) {
+ return HumHubF.initialize();
+});
diff --git a/lib/app_opener.dart b/lib/app_opener.dart
new file mode 100644
index 0000000..71e7462
--- /dev/null
+++ b/lib/app_opener.dart
@@ -0,0 +1,57 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:humhub/util/intent/intent_plugin.dart';
+import 'package:humhub/util/loading_provider.dart';
+import 'package:humhub/util/notifications/plugin.dart';
+import 'package:humhub/util/override_locale.dart';
+import 'package:humhub/util/push/push_plugin.dart';
+import 'package:humhub/util/router.dart';
+import 'package:humhub/util/storage_service.dart';
+import 'package:flutter_gen/gen_l10n/app_localizations.dart';
+
+class OpenerApp extends ConsumerStatefulWidget {
+ const OpenerApp({super.key});
+
+ @override
+ OpenerAppState createState() => OpenerAppState();
+}
+
+class OpenerAppState extends ConsumerState {
+ @override
+ Widget build(BuildContext context) {
+ SecureStorageService.clearSecureStorageOnReinstall();
+ return IntentPlugin(
+ child: NotificationPlugin(
+ child: PushPlugin(
+ child: OverrideLocale(
+ builder: (overrideLocale) => Builder(
+ builder: (context) => FutureBuilder(
+ future: MyRouter.getInitialRoute(ref),
+ builder: (context, snap) {
+ if (snap.hasData) {
+ return MaterialApp(
+ debugShowCheckedModeBanner: false,
+ initialRoute: snap.data,
+ routes: MyRouter.routes,
+ navigatorKey: navigatorKey,
+ localizationsDelegates: AppLocalizations.localizationsDelegates,
+ supportedLocales: AppLocalizations.supportedLocales,
+ locale: overrideLocale,
+ builder: (context, child) => LoadingProvider(
+ child: child!,
+ ),
+ theme: ThemeData(
+ fontFamily: 'OpenSans',
+ ),
+ );
+ }
+ return const SizedBox.shrink();
+ },
+ ),
+ ),
+ ),
+ ),
+ ),
+ );
+ }
+}
\ No newline at end of file
diff --git a/lib/components/animated_padding_component.dart b/lib/components/animated_padding_component.dart
index e055ee9..44d4e6d 100644
--- a/lib/components/animated_padding_component.dart
+++ b/lib/components/animated_padding_component.dart
@@ -11,13 +11,8 @@ class AnimatedPaddingComponent extends StatefulWidget {
}
class AnimatedPaddingComponentState extends State {
-
@override
Widget build(BuildContext context) {
- return AnimatedPadding(
- duration: const Duration(milliseconds: 500),
- padding: widget.padding,
- child: widget.child
- );
+ return AnimatedPadding(duration: const Duration(milliseconds: 500), padding: widget.padding, child: widget.child);
}
}
diff --git a/lib/components/bottom_navigation_bar.dart b/lib/components/bottom_navigation_bar.dart
index f489a3d..925dea2 100644
--- a/lib/components/bottom_navigation_bar.dart
+++ b/lib/components/bottom_navigation_bar.dart
@@ -84,7 +84,9 @@ class BottomNavigationState extends State with TickerProviderS
child: TextButton(
onPressed: () => navigateForth(),
child: Text(
- selectedIndex != widget.pageCount - 1 ? AppLocalizations.of(context)!.next : AppLocalizations.of(context)!.connect_now,
+ selectedIndex != widget.pageCount - 1
+ ? AppLocalizations.of(context)!.next
+ : AppLocalizations.of(context)!.connect_now,
style: const TextStyle(color: Colors.grey),
),
),
diff --git a/lib/components/language_switcher.dart b/lib/components/language_switcher.dart
index c07ffe2..8eaf647 100644
--- a/lib/components/language_switcher.dart
+++ b/lib/components/language_switcher.dart
@@ -35,7 +35,7 @@ class _LanguageSwitcherState extends State {
const SizedBox(width: 20),
Text(
locale.toUpperCase(),
- style: TextStyle(color: primaryColor, fontSize: 16),
+ style: TextStyle(color: HumhubTheme.primaryColor, fontSize: 16),
),
],
),
@@ -56,7 +56,7 @@ class _LanguageSwitcherState extends State {
focusedBorder: InputBorder.none,
),
value: _value(context),
- icon: Icon(Icons.arrow_drop_down, color: primaryColor),
+ icon: Icon(Icons.arrow_drop_down, color: HumhubTheme.primaryColor),
items: _items
.mapIndexed(
(localeString, index) => DropdownMenuItem(
@@ -67,7 +67,7 @@ class _LanguageSwitcherState extends State {
const SizedBox(width: 20),
Text(
localeString.toUpperCase(),
- style: TextStyle(color: primaryColor, fontSize: 16),
+ style: TextStyle(color: HumhubTheme.primaryColor, fontSize: 16),
),
],
),
diff --git a/lib/components/rotating_globe.dart b/lib/components/rotating_globe.dart
index bdc5eed..da77b25 100644
--- a/lib/components/rotating_globe.dart
+++ b/lib/components/rotating_globe.dart
@@ -29,7 +29,7 @@ class _RotatingGlobeState extends State with TickerProviderStateM
@override
Widget build(BuildContext context) {
- if(_controller.isCompleted){
+ if (_controller.isCompleted) {
_animation = widget.rotationDirection == Direction.left ? _animationSec : _animationFir;
_controller.reset();
}
diff --git a/lib/flavored/models/humhub.f.dart b/lib/flavored/models/humhub.f.dart
new file mode 100644
index 0000000..1250a32
--- /dev/null
+++ b/lib/flavored/models/humhub.f.dart
@@ -0,0 +1,37 @@
+import 'package:flutter_dotenv/flutter_dotenv.dart';
+import 'package:humhub/flavored/models/manifest.f.dart';
+import 'package:humhub/models/hum_hub.dart';
+import 'package:package_info_plus/package_info_plus.dart';
+
+class HumHubF extends HumHub {
+ @override
+ ManifestF get manifest => ManifestF.fromEnv();
+ @override
+ String get manifestUrl => dotenv.env['MANIFEST_URL']!;
+ final String bundleId;
+
+ HumHubF({
+ bool isHideOpener = false,
+ String? randomHash,
+ String? appVersion,
+ String? pushToken,
+ required this.bundleId,
+ }) : super(
+ isHideOpener: isHideOpener,
+ randomHash: HumHub.generateHash(32),
+ appVersion: appVersion,
+ pushToken: pushToken);
+
+ @override
+ Map get customHeaders => {
+ 'x-humhub-app-token': randomHash!,
+ 'x-humhub-app': appVersion ?? '1.0.0',
+ 'x-humhub-app-bundle-id': bundleId,
+ 'x-humhub-app-ostate': isHideOpener ? '1' : '0'
+ };
+
+ static Future initialize() async {
+ PackageInfo packageInfo = await PackageInfo.fromPlatform();
+ return HumHubF(bundleId: packageInfo.packageName);
+ }
+}
diff --git a/lib/flavored/models/manifest.f.dart b/lib/flavored/models/manifest.f.dart
new file mode 100644
index 0000000..677dbf5
--- /dev/null
+++ b/lib/flavored/models/manifest.f.dart
@@ -0,0 +1,30 @@
+import 'package:flutter_dotenv/flutter_dotenv.dart';
+import 'package:humhub/models/manifest.dart';
+
+class ManifestF extends Manifest {
+ ManifestF(
+ {required String display,
+ required String startUrl,
+ required String shortName,
+ required String name,
+ required String backgroundColor,
+ required String themeColor})
+ : super(
+ display: display,
+ startUrl: startUrl,
+ shortName: shortName,
+ name: name,
+ backgroundColor: backgroundColor,
+ themeColor: themeColor);
+
+ factory ManifestF.fromEnv() {
+ return ManifestF(
+ display: dotenv.env['DISPLAY']!,
+ startUrl: dotenv.env['START_URL']!,
+ shortName: dotenv.env['SHORT_NAME']!,
+ name: dotenv.env['NAME']!,
+ backgroundColor: dotenv.env['BACKGROUND_COLOR']!,
+ themeColor: dotenv.env['THEME_COLOR']!,
+ );
+ }
+}
diff --git a/lib/flavored/util/intent_plugin.f.dart b/lib/flavored/util/intent_plugin.f.dart
new file mode 100644
index 0000000..240b2a9
--- /dev/null
+++ b/lib/flavored/util/intent_plugin.f.dart
@@ -0,0 +1,137 @@
+import 'dart:async';
+
+import 'package:flutter/foundation.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:humhub/flavored/util/router.f.dart';
+import 'package:humhub/flavored/web_view.f.dart';
+import 'package:humhub/util/loading_provider.dart';
+import 'package:loggy/loggy.dart';
+import 'package:receive_sharing_intent/receive_sharing_intent.dart';
+import 'package:uni_links/uni_links.dart';
+
+bool _initialUriIsHandled = false;
+
+class IntentPluginF extends ConsumerStatefulWidget {
+ final Widget child;
+
+ const IntentPluginF({
+ Key? key,
+ required this.child,
+ }) : super(key: key);
+
+ @override
+ IntentPluginFState createState() => IntentPluginFState();
+}
+
+class IntentPluginFState extends ConsumerState {
+ StreamSubscription? intentDataStreamSubscription;
+ List? sharedFiles;
+ Object? _err;
+ Uri? _initialUri;
+ Uri? _latestUri;
+ StreamSubscription? _sub;
+
+ @override
+ void initState() {
+ logInfo([_err, _initialUri, _latestUri, _sub]);
+ super.initState();
+ intentDataStreamSubscription = ReceiveSharingIntent.getMediaStream().listen((List value) {
+ setState(() {
+ sharedFiles = value;
+ });
+ });
+
+ // For sharing images coming from outside the app while the app is closed
+ ReceiveSharingIntent.getInitialMedia().then((List value) {
+ setState(() {
+ sharedFiles = value;
+ });
+ });
+ _handleInitialUri();
+ _handleIncomingLinks();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return widget.child;
+ }
+
+ /// Handle incoming links - the ones that the app will recieve from the OS
+ /// while already started.
+ Future _handleIncomingLinks() async {
+ if (!kIsWeb) {
+ // It will handle app links while the app is already started - be it in
+ // the foreground or in the background.
+ _sub = uriLinkStream.listen((Uri? uri) async {
+ if (!mounted) return;
+ _latestUri = uri;
+ String? redirectUrl = uri?.toString();
+ if (redirectUrl != null && navigatorKeyF.currentState != null) {
+ tryNavigateWithOpener(redirectUrl);
+ }
+ _err = null;
+ }, onError: (err) {
+ if (kDebugMode) {
+ print(err);
+ }
+ });
+ }
+ }
+
+ /// Handle the initial Uri - the one the app was started with
+ ///
+ /// **ATTENTION**: `getInitialLink`/`getInitialUri` should be handled
+ /// ONLY ONCE in your app's lifetime, since it is not meant to change
+ /// throughout your app's life.
+ ///
+ /// We handle all exceptions, since it is called from initState.
+ Future _handleInitialUri() async {
+ // In this example app this is an almost useless guard, but it is here to
+ // show we are not going to call getInitialUri multiple times, even if this
+ // was a widget that will be disposed of (ex. a navigation route change).
+
+ if (!_initialUriIsHandled) {
+ _initialUriIsHandled = true;
+ try {
+ final uri = await getInitialUri();
+ if (uri == null || !mounted) return;
+ setState(() => _initialUri = uri);
+ if (!mounted) {
+ return;
+ }
+ _latestUri = uri;
+ String? redirectUrl = uri.queryParameters['url'];
+ if (redirectUrl != null && navigatorKeyF.currentState != null) {
+ tryNavigateWithOpener(redirectUrl);
+ } else {
+ if (redirectUrl != null) {
+ navigatorKeyF.currentState!.pushNamed(WebViewF.path, arguments: redirectUrl);
+ return;
+ }
+ }
+ } on PlatformException {
+ // Platform messages may fail but we ignore the exception
+ logError('Failed to get initial uri');
+ } on FormatException catch (err) {
+ if (!mounted) return;
+ logError('Malformed initial uri');
+ setState(() => _err = err);
+ }
+ }
+ }
+
+ Future tryNavigateWithOpener(String redirectUrl) async {
+ LoadingProvider.of(ref).showLoading();
+ bool isNewRouteSameAsCurrent = false;
+ navigatorKeyF.currentState!.popUntil((route) {
+ if (route.settings.name == WebViewF.path) {
+ isNewRouteSameAsCurrent = true;
+ }
+ return true;
+ });
+ navigatorKeyF.currentState!.pushNamed(WebViewF.path, arguments: redirectUrl);
+ return isNewRouteSameAsCurrent;
+ }
+}
diff --git a/lib/flavored/util/notifications/channel.dart b/lib/flavored/util/notifications/channel.dart
new file mode 100644
index 0000000..196d769
--- /dev/null
+++ b/lib/flavored/util/notifications/channel.dart
@@ -0,0 +1,36 @@
+import 'package:humhub/flavored/util/router.f.dart';
+import 'package:humhub/flavored/web_view.f.dart';
+import 'package:humhub/util/notifications/channel.dart';
+import 'package:humhub/util/notifications/init_from_push.dart';
+
+class NotificationChannelF extends NotificationChannel {
+ const NotificationChannelF(
+ {super.id = 'redirect',
+ super.name = 'Redirect flavored app notifications',
+ super.description = 'These notifications redirect the user to specific url in a payload.'});
+
+ /// If the WebView is not opened yet or the app is not running the onTap will wake up the app or redirect to the WebView.
+ /// If app is already running in WebView mode then the state of [WebViewApp] will be updated with new url.
+ ///
+ @override
+ Future onTap(String? payload) async {
+ if (payload != null && navigatorKeyF.currentState != null) {
+ bool isNewRouteSameAsCurrent = false;
+ navigatorKeyF.currentState!.popUntil((route) {
+ if (route.settings.name == WebViewF.path) {
+ isNewRouteSameAsCurrent = true;
+ }
+ return true;
+ });
+ if (isNewRouteSameAsCurrent) {
+ navigatorKeyF.currentState!.pushNamed(WebViewF.path, arguments: payload);
+ return;
+ }
+ navigatorKeyF.currentState!.pushNamed(WebViewF.path, arguments: payload);
+ } else {
+ if (payload != null) {
+ InitFromPush.setPayload(payload);
+ }
+ }
+ }
+}
diff --git a/lib/flavored/util/router.f.dart b/lib/flavored/util/router.f.dart
new file mode 100644
index 0000000..ca1eeb1
--- /dev/null
+++ b/lib/flavored/util/router.f.dart
@@ -0,0 +1,15 @@
+import 'package:flutter/cupertino.dart';
+import 'package:humhub/flavored/web_view.f.dart';
+
+final GlobalKey navigatorKeyF = GlobalKey();
+
+NavigatorState? get navigator => navigatorKeyF.currentState;
+
+class RouterF {
+ static String? initRoute = WebViewF.path;
+ static dynamic initParams;
+
+ static var routes = {
+ WebViewF.path: (context) => const WebViewF(),
+ };
+}
diff --git a/lib/flavored/web_view.f.dart b/lib/flavored/web_view.f.dart
new file mode 100644
index 0000000..38a4cdb
--- /dev/null
+++ b/lib/flavored/web_view.f.dart
@@ -0,0 +1,287 @@
+import 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+import 'package:flutter_app_badger/flutter_app_badger.dart';
+import 'package:flutter_inappwebview/flutter_inappwebview.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+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/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';
+
+class WebViewF extends ConsumerStatefulWidget {
+ static const String path = '/web_view_f';
+ const WebViewF({super.key});
+
+ @override
+ FlavoredWebViewState createState() => FlavoredWebViewState();
+}
+
+class FlavoredWebViewState extends ConsumerState {
+ late AuthInAppBrowser _authBrowser;
+ HeadlessInAppWebView? headlessWebView;
+ late HumHubF instance;
+
+ late PullToRefreshController pullToRefreshController;
+
+ @override
+ void initState() {
+ instance = ref.read(humHubFProvider).value!;
+ _authBrowser = AuthInAppBrowser(
+ manifest: ref.read(humHubFProvider).value!.manifest,
+ concludeAuth: (URLRequest request) {
+ _concludeAuth(request);
+ },
+ );
+ super.initState();
+
+ pullToRefreshController = PullToRefreshController(
+ options: PullToRefreshOptions(
+ color: HexColor(instance.manifest.themeColor),
+ ),
+ onRefresh: () async {
+ if (Platform.isAndroid) {
+ WebViewGlobalController.value?.reload();
+ } else if (Platform.isIOS) {
+ WebViewGlobalController.value
+ ?.loadUrl(urlRequest: URLRequest(url: await WebViewGlobalController.value?.getUrl()));
+ }
+ },
+ );
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ // ignore: deprecated_member_use
+ return WillPopScope(
+ onWillPop: () => exitApp(context, ref),
+ child: Scaffold(
+ backgroundColor: HexColor(instance.manifest.themeColor),
+ body: SafeArea(
+ bottom: false,
+ child: InAppWebView(
+ initialUrlRequest: _initialRequest,
+ initialOptions: _options,
+ pullToRefreshController: pullToRefreshController,
+ shouldOverrideUrlLoading: _shouldOverrideUrlLoading,
+ shouldInterceptFetchRequest: _shouldInterceptFetchRequest,
+ onWebViewCreated: _onWebViewCreated,
+ onCreateWindow: _onCreateWindow,
+ onLoadStop: _onLoadStop,
+ onLoadStart: _onLoadStart,
+ onLoadError: _onLoadError,
+ onProgressChanged: _onProgressChanged,
+ ),
+ ),
+ ),
+ );
+ }
+
+ URLRequest get _initialRequest {
+ var payload = ModalRoute.of(context)!.settings.arguments;
+ String? url = instance.manifest.startUrl;
+ String? payloadForInitFromPush = InitFromPush.usePayload();
+ String? payloadFromPush;
+ if (payload is String) payloadFromPush = payload;
+ if (payloadForInitFromPush != null) url = payloadForInitFromPush;
+ if (payloadFromPush != null) url = payloadFromPush;
+ return URLRequest(url: Uri.parse(url), headers: instance.customHeaders);
+ }
+
+ InAppWebViewGroupOptions get _options => InAppWebViewGroupOptions(
+ crossPlatform: InAppWebViewOptions(
+ useShouldOverrideUrlLoading: true,
+ useShouldInterceptFetchRequest: true,
+ javaScriptEnabled: true,
+ supportZoom: false,
+ javaScriptCanOpenWindowsAutomatically: true,
+ ),
+ android: AndroidInAppWebViewOptions(
+ supportMultipleWindows: true,
+ useHybridComposition: true,
+ ),
+ ios: IOSInAppWebViewOptions(
+ allowsInlineMediaPlayback: true,
+ ),
+ );
+
+ Future _shouldOverrideUrlLoading(
+ InAppWebViewController controller, NavigationAction action) async {
+ // 1st check if url is not def. app url and open it in a browser or inApp.
+ _setAjaxHeadersJQuery(controller);
+ final url = action.request.url!.origin;
+ if (!url.startsWith(instance.manifest.baseUrl) && action.isForMainFrame) {
+ _authBrowser.launchUrl(action.request);
+ return NavigationActionPolicy.CANCEL;
+ }
+ // 2nd Append customHeader if url is in app redirect and CANCEL the requests without custom headers
+ if (Platform.isAndroid ||
+ action.iosWKNavigationType == IOSWKNavigationType.LINK_ACTIVATED ||
+ action.iosWKNavigationType == IOSWKNavigationType.FORM_SUBMITTED) {
+ Map mergedMap = {...instance.customHeaders, ...?action.request.headers};
+ URLRequest newRequest = action.request.copyWith(headers: mergedMap);
+ controller.loadUrl(urlRequest: newRequest);
+ return NavigationActionPolicy.CANCEL;
+ }
+ return NavigationActionPolicy.ALLOW;
+ }
+
+ Future _onWebViewCreated(InAppWebViewController controller) async {
+ LoadingProvider.of(ref).showLoading();
+ headlessWebView = HeadlessInAppWebView();
+ headlessWebView!.run();
+ await controller.addWebMessageListener(
+ WebMessageListener(
+ jsObjectName: "flutterChannel",
+ onPostMessage: (inMessage, sourceOrigin, isMainFrame, replyProxy) async {
+ ChannelMessage message = ChannelMessage.fromJson(inMessage!);
+ await _handleJSMessage(message, headlessWebView!);
+ },
+ ),
+ );
+ WebViewGlobalController.setValue(controller);
+ }
+
+ Future _shouldInterceptFetchRequest(InAppWebViewController controller, FetchRequest request) async {
+ request.headers!.addAll(_initialRequest.headers!);
+ return request;
+ }
+
+ Future _onCreateWindow(inAppWebViewController, createWindowAction) async {
+ logDebug("onCreateWindow");
+ final urlToOpen = createWindowAction.request.url;
+ if (urlToOpen == null) return Future.value(false);
+ if (await canLaunchUrl(urlToOpen)) {
+ await launchUrl(urlToOpen, mode: LaunchMode.externalApplication);
+ } else {
+ logError('Could not launch $urlToOpen');
+ }
+ return Future.value(true); // Allow creating a new window.
+ }
+
+ Future _onLoadStop(InAppWebViewController controller, Uri? url) async {
+ // Disable remember me checkbox on login and set def. value to true: check if the page is actually login page, if it is inject JS that hides element
+ if (url!.path.contains('/user/auth/login')) {
+ WebViewGlobalController.value!
+ .evaluateJavascript(source: "document.querySelector('#login-rememberme').checked=true");
+ WebViewGlobalController.value!.evaluateJavascript(
+ source:
+ "document.querySelector('#account-login-form > div.form-group.field-login-rememberme').style.display='none';");
+ }
+ _setAjaxHeadersJQuery(controller);
+ pullToRefreshController.endRefreshing();
+ LoadingProvider.of(ref).dismissAll();
+ setState(() {});
+ }
+
+ void _onLoadStart(InAppWebViewController controller, Uri? url) async {
+ _setAjaxHeadersJQuery(controller);
+ }
+
+ void _onLoadError(InAppWebViewController controller, Uri? url, int code, String message) async {
+ if (code == -1009) ShowDialog.of(context).noInternetPopup();
+ pullToRefreshController.endRefreshing();
+ setState(() {});
+ }
+
+ void _onProgressChanged(controller, progress) async {
+ if (progress == 100) {
+ pullToRefreshController.endRefreshing();
+ setState(() {});
+ }
+ }
+
+ void _concludeAuth(URLRequest request) {
+ _authBrowser.close();
+ WebViewGlobalController.value!.loadUrl(urlRequest: request);
+ }
+
+ Future _setAjaxHeadersJQuery(InAppWebViewController controller) async {
+ String jsCode = "\$.ajaxSetup({headers: ${jsonEncode(instance.customHeaders).toString()}});";
+ dynamic jsResponse = await controller.evaluateJavascript(source: jsCode);
+ logInfo(jsResponse != null ? jsResponse.toString() : "Script returned null value");
+ }
+
+ Future _handleJSMessage(ChannelMessage message, HeadlessInAppWebView headlessWebView) async {
+ switch (message.action) {
+ case ChannelAction.registerFcmDevice:
+ String? token = ref.read(pushTokenProvider).value;
+ if (token != null) {
+ var postData = Uint8List.fromList(utf8.encode("token=$token"));
+ await headlessWebView.webViewController.postUrl(url: Uri.parse(message.url!), postData: postData);
+ }
+ 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!);
+ break;
+ case ChannelAction.unregisterFcmDevice:
+ String? token = ref.read(pushTokenProvider).value;
+ if (token != null) {
+ var postData = Uint8List.fromList(utf8.encode("token=$token"));
+ URLRequest request = URLRequest(url: Uri.parse(message.url!), method: "POST", body: postData);
+ // Works but for admin to see the changes it need to reload a page because a request is called on separate instance.
+ await headlessWebView.webViewController.loadUrl(urlRequest: request);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ Future exitApp(context, ref) async {
+ bool canGoBack = await WebViewGlobalController.value!.canGoBack();
+ if (canGoBack) {
+ WebViewGlobalController.value!.goBack();
+ return Future.value(false);
+ } else {
+ final exitConfirmed = await showDialog(
+ context: context,
+ builder: (context) => AlertDialog(
+ shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(10.0))),
+ title: Text(AppLocalizations.of(context)!.web_view_exit_popup_title),
+ content: Text(AppLocalizations.of(context)!.web_view_exit_popup_content),
+ actions: [
+ TextButton(
+ onPressed: () => Navigator.of(context).pop(false),
+ child: Text(AppLocalizations.of(context)!.no),
+ ),
+ TextButton(
+ onPressed: () {
+ SystemNavigator.pop();
+ },
+ child: Text(AppLocalizations.of(context)!.yes),
+ ),
+ ],
+ ),
+ );
+ return exitConfirmed ?? false;
+ }
+ }
+
+ @override
+ void dispose() {
+ super.dispose();
+ if (headlessWebView != null) {
+ headlessWebView!.dispose();
+ }
+ }
+}
diff --git a/lib/main.dart b/lib/main.dart
index b6557da..2ad0a1c 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -1,83 +1,21 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
-import 'package:flutter_secure_storage/flutter_secure_storage.dart';
-import 'package:humhub/util/intent/intent_plugin.dart';
-import 'package:humhub/util/loading_provider.dart';
+import 'package:humhub/models/hum_hub.dart';
import 'package:humhub/util/log.dart';
-import 'package:humhub/util/notifications/plugin.dart';
-import 'package:humhub/util/override_locale.dart';
-import 'package:humhub/util/push/push_plugin.dart';
-import 'package:humhub/util/router.dart';
+import 'package:humhub/util/storage_service.dart';
import 'package:loggy/loggy.dart';
-import 'package:shared_preferences/shared_preferences.dart';
-import 'package:flutter_gen/gen_l10n/app_localizations.dart';
+import 'package:package_info_plus/package_info_plus.dart';
main() async {
Loggy.initLoggy(
logPrinter: const GlobalLog(),
);
WidgetsFlutterBinding.ensureInitialized();
- await clearSecureStorageOnReinstall();
- SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]).then((_) {
- runApp(const ProviderScope(child: MyApp()));
+ await SecureStorageService.clearSecureStorageOnReinstall();
+ PackageInfo packageInfo = await PackageInfo.fromPlatform();
+ final app = await HumHub.app(packageInfo.packageName);
+ SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]).then((_) async {
+ runApp(ProviderScope(child: app));
});
}
-
-class MyApp extends ConsumerStatefulWidget {
- const MyApp({super.key});
-
- @override
- MyAppState createState() => MyAppState();
-}
-
-class MyAppState extends ConsumerState {
- @override
- Widget build(BuildContext context) {
- clearSecureStorageOnReinstall();
- return IntentPlugin(
- child: NotificationPlugin(
- child: PushPlugin(
- child: OverrideLocale(
- builder: (overrideLocale) => Builder(
- builder: (context) => FutureBuilder(
- future: MyRouter.getInitialRoute(ref),
- builder: (context, snap) {
- if (snap.hasData) {
- return MaterialApp(
- debugShowCheckedModeBanner: false,
- initialRoute: snap.data,
- routes: MyRouter.routes,
- navigatorKey: navigatorKey,
- localizationsDelegates: AppLocalizations.localizationsDelegates,
- supportedLocales: AppLocalizations.supportedLocales,
- locale: overrideLocale,
- builder: (context, child) => LoadingProvider(
- child: child!,
- ),
- theme: ThemeData(
- fontFamily: 'OpenSans',
- ),
- );
- }
- return const SizedBox.shrink();
- },
- ),
- ),
- ),
- ),
- ),
- );
- }
-}
-
-clearSecureStorageOnReinstall() async {
- String key = 'hasRunBefore';
- SharedPreferences prefs = await SharedPreferences.getInstance();
- bool hasRunBefore = prefs.getBool(key) ?? false;
- if (!hasRunBefore) {
- FlutterSecureStorage storage = const FlutterSecureStorage();
- await storage.deleteAll();
- prefs.setBool(key, true);
- }
-}
diff --git a/lib/models/channel_message.g.dart b/lib/models/channel_message.g.dart
index 9c5290d..dff8270 100644
--- a/lib/models/channel_message.g.dart
+++ b/lib/models/channel_message.g.dart
@@ -1,30 +1,24 @@
-// GENERATED CODE - DO NOT MODIFY BY HAND
-
-part of 'channel_message.dart';
-
-// **************************************************************************
-// JsonSerializableGenerator
-// **************************************************************************
-
-ChannelMessage _$ChannelMessageFromJson(String json) {
- if (json == "humhub.mobile.hideOpener") {
- return ChannelMessage(
- "hideOpener",
- null,
- null,
- );
- } else if (json == "humhub.mobile.showOpener") {
- return ChannelMessage(
- "showOpener",
- null,
- null,
- );
- } else {
- var jsonMap = jsonDecode(json) as Map;
- return ChannelMessage(
- jsonMap['type'] as String,
- jsonMap['url'] as String?,
- jsonMap['count'] as int?,
- );
- }
-}
+part of 'channel_message.dart';
+
+ChannelMessage _$ChannelMessageFromJson(String json) {
+ if (json == "humhub.mobile.hideOpener") {
+ return ChannelMessage(
+ "hideOpener",
+ null,
+ null,
+ );
+ } else if (json == "humhub.mobile.showOpener") {
+ return ChannelMessage(
+ "showOpener",
+ null,
+ null,
+ );
+ } else {
+ var jsonMap = jsonDecode(json) as Map;
+ return ChannelMessage(
+ jsonMap['type'] as String,
+ jsonMap['url'] as String?,
+ jsonMap['count'] as int?,
+ );
+ }
+}
diff --git a/lib/models/event.dart b/lib/models/event.dart
index 2f1935d..1b425f0 100644
--- a/lib/models/event.dart
+++ b/lib/models/event.dart
@@ -7,20 +7,20 @@ class PushEvent extends RemoteMessage {
PushEvent(RemoteMessage message)
: parsedData = PushEventData.fromJson(message.data),
super(
- senderId: message.senderId,
- category: message.category,
- collapseKey: message.collapseKey,
- contentAvailable: message.contentAvailable,
- data: message.data,
- from: message.from,
- messageId: message.messageId,
- messageType: message.messageType,
- mutableContent: message.mutableContent,
- notification: message.notification,
- sentTime: message.sentTime,
- threadId: message.threadId,
- ttl: message.ttl,
- );
+ senderId: message.senderId,
+ category: message.category,
+ collapseKey: message.collapseKey,
+ contentAvailable: message.contentAvailable,
+ data: message.data,
+ from: message.from,
+ messageId: message.messageId,
+ messageType: message.messageType,
+ mutableContent: message.mutableContent,
+ notification: message.notification,
+ sentTime: message.sentTime,
+ threadId: message.threadId,
+ ttl: message.ttl,
+ );
}
class PushEventData {
@@ -32,13 +32,13 @@ class PushEventData {
final String? notificationCount;
PushEventData(
- this.notificationTitle,
- this.notificationBody,
- this.channel,
- this.channelPayload,
- this.redirectUrl,
- this.notificationCount,
- );
+ this.notificationTitle,
+ this.notificationBody,
+ this.channel,
+ this.channelPayload,
+ this.redirectUrl,
+ this.notificationCount,
+ );
factory PushEventData.fromJson(Map json) => _$PushEventDataFromJson(json);
}
diff --git a/lib/models/event.g.dart b/lib/models/event.g.dart
index 374d64b..becfc11 100644
--- a/lib/models/event.g.dart
+++ b/lib/models/event.g.dart
@@ -1,14 +1,13 @@
-// GENERATED CODE - DO NOT MODIFY BY HAND
-
part of 'event.dart';
-// **************************************************************************
-// JsonSerializableGenerator
-// **************************************************************************
-
PushEventData _$PushEventDataFromJson(Map json) {
- return PushEventData(json['notification_title'] as String?, json['notification_body'] as String?, json['channel'] as String?,
- json['channel_payload'] as String?, json['url'] as String?, json['notification_count'] as String?);
+ return PushEventData(
+ json['notification_title'] as String?,
+ json['notification_body'] as String?,
+ json['channel'] as String?,
+ json['channel_payload'] as String?,
+ json['url'] as String?,
+ json['notification_count'] as String?);
}
SimpleNotification _$SimpleNotificationFromJson(Map json) {
diff --git a/lib/models/hum_hub.dart b/lib/models/hum_hub.dart
index 19873a0..cafb177 100644
--- a/lib/models/hum_hub.dart
+++ b/lib/models/hum_hub.dart
@@ -1,6 +1,10 @@
import 'dart:math';
+import 'package:flutter/material.dart';
+import 'package:flutter_dotenv/flutter_dotenv.dart';
+import 'package:humhub/app_flavored.dart';
+import 'package:humhub/app_opener.dart';
import 'package:humhub/models/manifest.dart';
-import 'package:humhub/util/universal_opener_controller.dart';
+import 'package:humhub/util/openers/universal_opener_controller.dart';
enum RedirectAction { opener, webView }
@@ -63,4 +67,14 @@ class HumHub {
'x-humhub-app': appVersion ?? '1.0.0',
'x-humhub-app-ostate': isHideOpener ? '1' : '0'
};
+
+ static Future app(String bundleId) async {
+ switch (bundleId) {
+ case 'com.humhub.app':
+ return const OpenerApp();
+ default:
+ await dotenv.load(fileName: "assets/.env");
+ return const FlavoredApp();
+ }
+ }
}
diff --git a/lib/models/manifest.dart b/lib/models/manifest.dart
index f71c3d0..2ce792f 100644
--- a/lib/models/manifest.dart
+++ b/lib/models/manifest.dart
@@ -8,7 +8,7 @@ class Manifest {
final String backgroundColor;
final String themeColor;
- Manifest(this.display, this.startUrl, this.shortName, this.name, this.backgroundColor, this.themeColor);
+ Manifest({required this.display, required this.startUrl, required this.shortName, required this.name, required this.backgroundColor, required this.themeColor});
String get baseUrl {
Uri url = Uri.parse(startUrl);
@@ -17,12 +17,12 @@ class Manifest {
factory Manifest.fromJson(Map json) {
return Manifest(
- json['display'] as String,
- json['start_url'] as String,
- json['short_name'] as String,
- json['name'] as String,
- json['background_color'] as String,
- json['theme_color'] as String,
+ display: json['display'] as String,
+ startUrl: json['start_url'] as String,
+ shortName: json['short_name'] as String,
+ name: json['name'] as String,
+ backgroundColor: json['background_color'] as String,
+ themeColor: json['theme_color'] as String,
);
}
diff --git a/lib/pages/help/components/first_page.dart b/lib/pages/help/components/first_page.dart
index cb3177a..13eb670 100644
--- a/lib/pages/help/components/first_page.dart
+++ b/lib/pages/help/components/first_page.dart
@@ -3,10 +3,12 @@ import 'package:humhub/components/rotating_globe.dart';
import 'package:humhub/util/const.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
-
class FirstPage extends StatelessWidget {
final bool fadeIn;
- const FirstPage({Key? key, required this.fadeIn,}) : super(key: key);
+ const FirstPage({
+ Key? key,
+ required this.fadeIn,
+ }) : super(key: key);
@override
Widget build(BuildContext context) {
@@ -22,7 +24,7 @@ class FirstPage extends StatelessWidget {
alignment: Alignment.centerLeft,
child: Text(
AppLocalizations.of(context)!.help_title,
- style: getHeaderStyle(context),
+ style: HumhubTheme.getHeaderStyle(context),
),
),
),
@@ -30,12 +32,12 @@ class FirstPage extends StatelessWidget {
padding: const EdgeInsets.symmetric(vertical: 10),
child: Text(
AppLocalizations.of(context)!.help_first_par,
- style: paragraphStyle,
+ style: HumhubTheme.paragraphStyle,
),
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 10),
- child: Text(AppLocalizations.of(context)!.help_second_par, style: paragraphStyle),
+ child: Text(AppLocalizations.of(context)!.help_second_par, style: HumhubTheme.paragraphStyle),
),
Center(
child: RotatingGlobe(
diff --git a/lib/pages/help/components/second_page.dart b/lib/pages/help/components/second_page.dart
index 94b5a9e..2d9deb4 100644
--- a/lib/pages/help/components/second_page.dart
+++ b/lib/pages/help/components/second_page.dart
@@ -21,13 +21,13 @@ class SecondPage extends StatelessWidget {
alignment: Alignment.centerLeft,
child: Text(
AppLocalizations.of(context)!.how_to_connect_title,
- style: getHeaderStyle(context),
+ style: HumhubTheme.getHeaderStyle(context),
),
),
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 10),
- child: Text(AppLocalizations.of(context)!.how_to_connect_first_par, style: paragraphStyle),
+ child: Text(AppLocalizations.of(context)!.how_to_connect_first_par, style: HumhubTheme.paragraphStyle),
),
EaseOutContainer(
fadeIn: fadeIn,
@@ -79,7 +79,7 @@ class SecondPage extends StatelessWidget {
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 10),
- child: Text(AppLocalizations.of(context)!.how_to_connect_sec_par, style: paragraphStyle),
+ child: Text(AppLocalizations.of(context)!.how_to_connect_sec_par, style: HumhubTheme.paragraphStyle),
),
EaseOutContainer(
fadeIn: fadeIn,
@@ -94,7 +94,7 @@ class SecondPage extends StatelessWidget {
style: ButtonStyle(
backgroundColor: MaterialStateProperty.resolveWith(
(Set states) {
- return primaryColor;
+ return HumhubTheme.primaryColor;
},
),
),
@@ -114,7 +114,7 @@ class SecondPage extends StatelessWidget {
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 20),
- child: Text(AppLocalizations.of(context)!.how_to_connect_third_par, style: paragraphStyle),
+ child: Text(AppLocalizations.of(context)!.how_to_connect_third_par, style: HumhubTheme.paragraphStyle),
)
],
),
diff --git a/lib/pages/help/components/third_page.dart b/lib/pages/help/components/third_page.dart
index 6781c1f..465d551 100644
--- a/lib/pages/help/components/third_page.dart
+++ b/lib/pages/help/components/third_page.dart
@@ -22,21 +22,21 @@ class ThirdPage extends StatelessWidget {
alignment: Alignment.centerLeft,
child: Text(
AppLocalizations.of(context)!.more_info_title,
- style: getHeaderStyle(context),
+ style: HumhubTheme.getHeaderStyle(context),
),
),
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 10),
- child: Text(AppLocalizations.of(context)!.more_info_first_par, style: paragraphStyle),
+ child: Text(AppLocalizations.of(context)!.more_info_first_par, style: HumhubTheme.paragraphStyle),
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 10),
- child: Text(AppLocalizations.of(context)!.more_info_second_par, style: paragraphStyle),
+ child: Text(AppLocalizations.of(context)!.more_info_second_par, style: HumhubTheme.paragraphStyle),
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 10),
- child: Text(AppLocalizations.of(context)!.more_info_third_par, style: paragraphStyle),
+ child: Text(AppLocalizations.of(context)!.more_info_third_par, style: HumhubTheme.paragraphStyle),
),
const SizedBox(
height: 40,
@@ -54,12 +54,13 @@ class ThirdPage extends StatelessWidget {
style: ButtonStyle(
backgroundColor: MaterialStateProperty.resolveWith(
(Set states) {
- return primaryColor;
+ return HumhubTheme.primaryColor;
},
),
),
onPressed: () {
- launchUrl(Uri.parse(AppLocalizations.of(context)!.more_info_pro_edition_url), mode: LaunchMode.platformDefault);
+ launchUrl(Uri.parse(AppLocalizations.of(context)!.more_info_pro_edition_url),
+ mode: LaunchMode.platformDefault);
},
child: Center(
child: Text(
diff --git a/lib/pages/opener.dart b/lib/pages/opener.dart
index ddbcf7c..028283c 100644
--- a/lib/pages/opener.dart
+++ b/lib/pages/opener.dart
@@ -8,7 +8,7 @@ import 'package:humhub/util/const.dart';
import 'package:humhub/util/form_helper.dart';
import 'package:humhub/util/intent/intent_plugin.dart';
import 'package:humhub/util/notifications/channel.dart';
-import 'package:humhub/util/opener_controller.dart';
+import 'package:humhub/util/openers/opener_controller.dart';
import 'package:humhub/util/providers.dart';
import 'package:rive/rive.dart';
import 'help/help_android.dart';
@@ -53,7 +53,7 @@ class OpenerState extends ConsumerState with SingleTickerProviderStateMi
String? urlIntent = InitFromIntent.usePayloadForInit();
if (urlIntent != null) {
- await RedirectNotificationChannel().onTap(urlIntent);
+ await ref.read(notificationChannelProvider).value!.onTap(urlIntent);
}
});
}
@@ -171,7 +171,7 @@ class OpenerState extends ConsumerState with SingleTickerProviderStateMi
onPressed: _connectInstance,
child: Text(
AppLocalizations.of(context)!.connect,
- style: TextStyle(color: primaryColor, fontSize: 20),
+ style: TextStyle(color: HumhubTheme.primaryColor, fontSize: 20),
),
),
),
@@ -246,7 +246,7 @@ class OpenerState extends ConsumerState with SingleTickerProviderStateMi
await controlLer.initHumHub();
if (controlLer.allOk) {
ref.read(humHubProvider).getInstance().then((value) {
- Navigator.pushNamed(ref.context, WebViewApp.path, arguments: value.manifest);
+ Navigator.pushNamed(ref.context, WebView.path, arguments: value.manifest);
});
}
}
diff --git a/lib/pages/web_view.dart b/lib/pages/web_view.dart
index bba9395..087bde4 100644
--- a/lib/pages/web_view.dart
+++ b/lib/pages/web_view.dart
@@ -2,21 +2,23 @@ import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:app_settings/app_settings.dart';
-import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
+import 'package:flutter/scheduler.dart';
+import 'package:flutter/services.dart';
import 'package:flutter_app_badger/flutter_app_badger.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
-import 'package:humhub/components/auth_in_app_browser.dart';
+import 'package:humhub/util/auth_in_app_browser.dart';
import 'package:humhub/models/channel_message.dart';
import 'package:humhub/models/hum_hub.dart';
import 'package:humhub/models/manifest.dart';
import 'package:humhub/pages/opener.dart';
import 'package:humhub/util/connectivity_plugin.dart';
import 'package:humhub/util/extensions.dart';
-import 'package:humhub/util/notifications/channel.dart';
+import 'package:humhub/util/notifications/init_from_push.dart';
import 'package:humhub/util/providers.dart';
-import 'package:humhub/util/universal_opener_controller.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:loggy/loggy.dart';
import 'package:permission_handler/permission_handler.dart';
@@ -24,25 +26,17 @@ import 'package:humhub/util/router.dart' as m;
import 'package:url_launcher/url_launcher.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
-class WebViewGlobalController {
- static InAppWebViewController? _value;
+import '../util/web_view_global_controller.dart';
- static InAppWebViewController? get value => _value;
-
- static void setValue(InAppWebViewController newValue) {
- _value = newValue;
- }
-}
-
-class WebViewApp extends ConsumerStatefulWidget {
- const WebViewApp({super.key});
+class WebView extends ConsumerStatefulWidget {
+ const WebView({super.key});
static const String path = '/web_view';
@override
WebViewAppState createState() => WebViewAppState();
}
-class WebViewAppState extends ConsumerState {
+class WebViewAppState extends ConsumerState {
late AuthInAppBrowser authBrowser;
late Manifest manifest;
late URLRequest _initialRequest;
@@ -59,28 +53,43 @@ class WebViewAppState extends ConsumerState {
),
);
- PullToRefreshController? _pullToRefreshController;
- late PullToRefreshOptions _pullToRefreshOptions;
+ late PullToRefreshController _pullToRefreshController;
+
HeadlessInAppWebView? headlessWebView;
@override
void initState() {
super.initState();
+ SchedulerBinding.instance.addPostFrameCallback((_) {
+ _initialRequest = _initRequest;
+ _pullToRefreshController = PullToRefreshController(
+ options: PullToRefreshOptions(
+ color: HexColor(manifest.themeColor),
+ ),
+ onRefresh: () async {
+ if (Platform.isAndroid) {
+ WebViewGlobalController.value?.reload();
+ } else if (Platform.isIOS) {
+ WebViewGlobalController.value
+ ?.loadUrl(urlRequest: URLRequest(url: await WebViewGlobalController.value?.getUrl()));
+ }
+ },
+ );
+ authBrowser = AuthInAppBrowser(
+ manifest: manifest,
+ concludeAuth: (URLRequest request) {
+ _concludeAuth(request);
+ },
+ );
+ setState(() {});
+ });
}
@override
Widget build(BuildContext context) {
- _initialRequest = _initRequest;
- _pullToRefreshController = initPullToRefreshController;
- authBrowser = AuthInAppBrowser(
- manifest: manifest,
- concludeAuth: (URLRequest request) {
- _concludeAuth(request);
- },
- );
// ignore: deprecated_member_use
return WillPopScope(
- onWillPop: () => WebViewGlobalController.value!.exitApp(context, ref),
+ onWillPop: () => exitApp(context, ref),
child: Scaffold(
backgroundColor: HexColor(manifest.themeColor),
body: SafeArea(
@@ -108,6 +117,7 @@ class WebViewAppState extends ConsumerState {
},
onLoadStop: _onLoadStop,
onLoadStart: (controller, uri) async {
+ logDebug("onLoadStart");
_setAjaxHeadersJQuery(controller);
},
onProgressChanged: _onProgressChanged,
@@ -156,6 +166,7 @@ class WebViewAppState extends ConsumerState {
logInfo(inMessage);
ChannelMessage message = ChannelMessage.fromJson(inMessage!);
await _handleJSMessage(message, headlessWebView!);
+ logDebug('flutterChannel triggered: ${message.type}');
},
),
);
@@ -163,6 +174,7 @@ class WebViewAppState extends ConsumerState {
}
Future _shouldInterceptFetchRequest(InAppWebViewController controller, FetchRequest request) async {
+ logDebug("_shouldInterceptFetchRequest");
request.headers!.addAll(_initialRequest.headers!);
return request;
}
@@ -202,38 +214,15 @@ class WebViewAppState extends ConsumerState {
"document.querySelector('#account-login-form > div.form-group.field-login-rememberme').style.display='none';");
}
_setAjaxHeadersJQuery(controller);
- _pullToRefreshController?.endRefreshing();
+ _pullToRefreshController.endRefreshing();
}
_onProgressChanged(InAppWebViewController controller, int progress) {
if (progress == 100) {
- _pullToRefreshController?.endRefreshing();
+ _pullToRefreshController.endRefreshing();
}
}
- PullToRefreshController? get initPullToRefreshController {
- _pullToRefreshOptions = PullToRefreshOptions(
- color: HexColor(manifest.themeColor),
- );
- return kIsWeb
- ? null
- : PullToRefreshController(
- options: _pullToRefreshOptions,
- onRefresh: () async {
- Uri? url = await WebViewGlobalController.value!.getUrl();
- if (url != null) {
- WebViewGlobalController.value!.loadUrl(
- urlRequest: URLRequest(
- url: await WebViewGlobalController.value!.getUrl(),
- headers: ref.read(humHubProvider).customHeaders),
- );
- } else {
- WebViewGlobalController.value!.reload();
- }
- },
- );
- }
-
askForNotificationPermissions() {
showDialog(
context: context,
@@ -306,6 +295,39 @@ class WebViewAppState extends ConsumerState {
}
}
+ Future exitApp(context, ref) async {
+ bool canGoBack = await WebViewGlobalController.value!.canGoBack();
+ if (canGoBack) {
+ WebViewGlobalController.value!.goBack();
+ return Future.value(false);
+ } else {
+ final exitConfirmed = await showDialog(
+ context: context,
+ builder: (context) => AlertDialog(
+ shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(10.0))),
+ title: Text(AppLocalizations.of(context)!.web_view_exit_popup_title),
+ content: Text(AppLocalizations.of(context)!.web_view_exit_popup_content),
+ actions: [
+ TextButton(
+ onPressed: () => Navigator.of(context).pop(false),
+ child: Text(AppLocalizations.of(context)!.no),
+ ),
+ TextButton(
+ onPressed: () {
+ var isHide = ref.read(humHubProvider).isHideDialog;
+ isHide
+ ? SystemNavigator.pop()
+ : Navigator.of(context).pushNamedAndRemoveUntil(Opener.path, (Route route) => false);
+ },
+ child: Text(AppLocalizations.of(context)!.yes),
+ ),
+ ],
+ ),
+ );
+ return exitConfirmed ?? false;
+ }
+ }
+
@override
void dispose() {
super.dispose();
diff --git a/lib/components/auth_in_app_browser.dart b/lib/util/auth_in_app_browser.dart
similarity index 100%
rename from lib/components/auth_in_app_browser.dart
rename to lib/util/auth_in_app_browser.dart
diff --git a/lib/util/connectivity_plugin.dart b/lib/util/connectivity_plugin.dart
index 6052083..1059957 100644
--- a/lib/util/connectivity_plugin.dart
+++ b/lib/util/connectivity_plugin.dart
@@ -15,11 +15,11 @@ class NoConnectionDialog extends StatelessWidget {
@override
Widget build(BuildContext context) {
return AlertDialog(
- title: Text(AppLocalizations.of(context)!.connectivity_popup_title),
- content: Text(AppLocalizations.of(context)!.connectivity_popup_content),
+ title: Text(AppLocalizations.of(context)!.connectivity_popup_title),
+ content: Text(AppLocalizations.of(context)!.connectivity_popup_content),
actions: [
TextButton(
- child: Text(AppLocalizations.of(context)!.ok.toUpperCase()),
+ child: Text(AppLocalizations.of(context)!.ok.toUpperCase()),
onPressed: () {
Navigator.of(context).pop(); // Close the dialog
},
@@ -32,7 +32,9 @@ class NoConnectionDialog extends StatelessWidget {
showDialog(
context: context,
builder: (BuildContext context) {
- return const NoConnectionDialog();
+ return NoConnectionDialog(
+ key: context.widget.key,
+ );
},
);
}
diff --git a/lib/util/const.dart b/lib/util/const.dart
index dfb7957..8689c0e 100644
--- a/lib/util/const.dart
+++ b/lib/util/const.dart
@@ -3,21 +3,22 @@ import 'package:flutter/material.dart';
class StorageKeys {
static String humhubInstance = "humHubInstance";
static String lastInstanceUrl = "humHubLastUrl";
-
}
class Assets {
static String logo = "assets/images/logo.png";
static String helpImg = "assets/images/help.png";
- static String openerAnimationForward = "assets/opener_animation.riv";
- static String openerAnimationReverse = "assets/opener_animation_reverse.riv";
+ static String openerAnimationForward = "assets/animations/opener_animation.riv";
+ static String openerAnimationReverse = "assets/animations/opener_animation_reverse.riv";
}
-Color primaryColor = const Color(0xFF21a1b3);
+class HumhubTheme {
+ static Color primaryColor = const Color(0xFF21a1b3);
-TextStyle? getHeaderStyle(context) {
- return Theme.of(context).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.w600);
-}
+ static TextStyle? getHeaderStyle(context) {
+ return Theme.of(context).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.w600);
+ }
-TextStyle paragraphStyle =
- const TextStyle(letterSpacing: 0.5, fontWeight: FontWeight.normal, color: Colors.black, fontSize: 15);
+ static TextStyle paragraphStyle =
+ const TextStyle(letterSpacing: 0.5, fontWeight: FontWeight.normal, color: Colors.black, fontSize: 15);
+}
diff --git a/lib/util/extensions.dart b/lib/util/extensions.dart
index f752787..524afc8 100644
--- a/lib/util/extensions.dart
+++ b/lib/util/extensions.dart
@@ -1,52 +1,8 @@
-import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
-import 'package:humhub/pages/opener.dart';
import 'package:humhub/util/const.dart';
-import 'package:humhub/util/providers.dart';
import 'package:loggy/loggy.dart';
-import 'package:flutter_gen/gen_l10n/app_localizations.dart';
-
-extension MyWebViewController on InAppWebViewController {
- Future exitApp(context, ref) async {
- bool canGoBack = await this.canGoBack();
- if (canGoBack) {
- goBack();
- return Future.value(false);
- } else {
- final exitConfirmed = await showDialog(
- context: context,
- builder: (context) => AlertDialog(
- shape: const RoundedRectangleBorder(
- borderRadius: BorderRadius.all(Radius.circular(10.0))),
- title: Text(AppLocalizations.of(context)!.web_view_exit_popup_title),
- content: Text(AppLocalizations.of(context)!.web_view_exit_popup_content),
- actions: [
- TextButton(
- onPressed: () => Navigator.of(context).pop(false),
- child: Text(AppLocalizations.of(context)!.no),
- ),
- TextButton(
- onPressed: () {
- closeOrOpenDialog(context, ref);
- },
- child: Text(AppLocalizations.of(context)!.yes),
- ),
- ],
- ),
- );
- return exitConfirmed ?? false;
- }
- }
-
- closeOrOpenDialog(BuildContext context, WidgetRef ref) {
- var isHide = ref.read(humHubProvider).isHideDialog;
- isHide
- ? SystemNavigator.pop()
- : Navigator.of(context).pushNamedAndRemoveUntil(Opener.path, (Route route) => false);
- }
-}
class HexColor extends Color {
static int _getColorFromHex(String hexColor) {
@@ -58,7 +14,7 @@ class HexColor extends Color {
return int.parse(hexColor, radix: 16);
} catch (e) {
logError("Color from manifest is not valid use primary color");
- return primaryColor.value;
+ return HumhubTheme.primaryColor.value;
}
}
diff --git a/lib/util/form_helper.dart b/lib/util/form_helper.dart
index bfd0b2b..fd93f41 100644
--- a/lib/util/form_helper.dart
+++ b/lib/util/form_helper.dart
@@ -11,4 +11,4 @@ class FormHelper {
bool validate() => key.currentState?.validate() ?? false;
void save() => key.currentState?.save();
-}
\ No newline at end of file
+}
diff --git a/lib/util/intent/intent_plugin.dart b/lib/util/intent/intent_plugin.dart
index 245a852..84668ca 100644
--- a/lib/util/intent/intent_plugin.dart
+++ b/lib/util/intent/intent_plugin.dart
@@ -7,7 +7,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:humhub/pages/web_view.dart';
import 'package:humhub/util/loading_provider.dart';
import 'package:humhub/util/router.dart';
-import 'package:humhub/util/universal_opener_controller.dart';
+import 'package:humhub/util/openers/universal_opener_controller.dart';
import 'package:loggy/loggy.dart';
import 'package:receive_sharing_intent/receive_sharing_intent.dart';
import 'package:uni_links/uni_links.dart';
@@ -110,7 +110,7 @@ class IntentPluginState extends ConsumerState {
if (redirectUrl != null) {
UniversalOpenerController opener = UniversalOpenerController(url: redirectUrl);
await opener.initHumHub();
- navigatorKey.currentState!.pushNamed(WebViewApp.path, arguments: opener);
+ navigatorKey.currentState!.pushNamed(WebView.path, arguments: opener);
return;
}
}
@@ -129,7 +129,7 @@ class IntentPluginState extends ConsumerState {
LoadingProvider.of(ref).showLoading();
bool isNewRouteSameAsCurrent = false;
navigatorKey.currentState!.popUntil((route) {
- if (route.settings.name == WebViewApp.path) {
+ if (route.settings.name == WebView.path) {
isNewRouteSameAsCurrent = true;
}
return true;
@@ -138,7 +138,7 @@ class IntentPluginState extends ConsumerState {
await opener.initHumHub();
// Always pop the current instance and init the new one.
LoadingProvider.of(ref).dismissAll();
- navigatorKey.currentState!.pushNamed(WebViewApp.path, arguments: opener);
+ navigatorKey.currentState!.pushNamed(WebView.path, arguments: opener);
return isNewRouteSameAsCurrent;
}
}
diff --git a/lib/util/log.dart b/lib/util/log.dart
index 0bd8661..b8d6956 100644
--- a/lib/util/log.dart
+++ b/lib/util/log.dart
@@ -11,8 +11,7 @@ class GlobalLog extends LoggyPrinter {
bool get _colorize => showColors ?? false;
static final _levelColors = {
- LogLevel.debug:
- AnsiColor(foregroundColor: AnsiColor.grey(0.5), italic: true),
+ LogLevel.debug: AnsiColor(foregroundColor: AnsiColor.grey(0.5), italic: true),
LogLevel.info: AnsiColor(foregroundColor: 35),
LogLevel.warning: AnsiColor(foregroundColor: 214),
LogLevel.error: AnsiColor(foregroundColor: 196),
@@ -30,21 +29,14 @@ class GlobalLog extends LoggyPrinter {
@override
void onLog(LogRecord record) {
final time = record.time.toIso8601String().split('T')[1];
- final callerFrame =
- record.callerFrame == null ? '-' : '(${record.callerFrame?.location})';
- final logLevel = record.level
- .toString()
- .replaceAll('Level.', '')
- .toUpperCase()
- .padRight(8);
+ final callerFrame = record.callerFrame == null ? '-' : '(${record.callerFrame?.location})';
+ final logLevel = record.level.toString().replaceAll('Level.', '').toUpperCase().padRight(8);
- final color =
- _colorize ? levelColor(record.level) ?? AnsiColor() : AnsiColor();
+ final color = _colorize ? levelColor(record.level) ?? AnsiColor() : AnsiColor();
final prefix = levelPrefix(record.level) ?? _defaultPrefix;
if (kDebugMode) {
- print(color(
- '$prefix$time $logLevel GLOBAL $callerFrame ${record.message}'));
+ print(color('$prefix$time $logLevel GLOBAL $callerFrame ${record.message}'));
}
if (record.stackTrace != null) {
@@ -61,4 +53,4 @@ class GlobalLog extends LoggyPrinter {
AnsiColor? levelColor(LogLevel level) {
return _levelColors[level];
}
-}
\ No newline at end of file
+}
diff --git a/lib/util/notifications/channel.dart b/lib/util/notifications/channel.dart
index b38843f..a9d3626 100644
--- a/lib/util/notifications/channel.dart
+++ b/lib/util/notifications/channel.dart
@@ -1,51 +1,29 @@
-import 'package:flutter/cupertino.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:humhub/flavored/util/notifications/channel.dart';
import 'package:humhub/pages/web_view.dart';
-import 'package:humhub/util/universal_opener_controller.dart';
+import 'package:humhub/util/notifications/init_from_push.dart';
+import 'package:humhub/util/openers/universal_opener_controller.dart';
import 'package:humhub/util/router.dart';
-import 'package:loggy/loggy.dart';
+import 'package:package_info_plus/package_info_plus.dart';
-abstract class NotificationChannel {
+class NotificationChannel {
final String id;
final String name;
final String description;
- NotificationChannel(this.id, this.name, this.description);
-
- Future onTap(String? payload);
-
- @protected
- Future navigate(String route, {Object? arguments}) async {
- logInfo('NotificationChannel navigate: $route');
- if (navigatorKey.currentState?.mounted ?? false) {
- await navigatorKey.currentState?.pushNamed(
- route,
- arguments: arguments,
- );
- } else {
- queueRoute(
- route,
- arguments: arguments,
- );
- }
- }
-}
-
-class RedirectNotificationChannel extends NotificationChannel {
- RedirectNotificationChannel()
- : super(
- 'redirect',
- 'Redirect app notifications',
- 'These notifications are redirect the user to specific url in a payload.',
- );
+ const NotificationChannel(
+ {this.id = 'redirect',
+ this.name = 'Redirect app notifications',
+ this.description = 'These notifications are redirect the user to specific url in a payload.'});
/// If the WebView is not opened yet or the app is not running the onTap will wake up the app or redirect to the WebView.
- /// If app is already running in WebView mode then the state of [WebViewApp] will be updated with new url.
- @override
+ /// If app is already running in WebView mode then the state of [WebView] will be updated with new url.
+ ///
Future onTap(String? payload) async {
if (payload != null && navigatorKey.currentState != null) {
bool isNewRouteSameAsCurrent = false;
navigatorKey.currentState!.popUntil((route) {
- if (route.settings.name == WebViewApp.path) {
+ if (route.settings.name == WebView.path) {
isNewRouteSameAsCurrent = true;
}
return true;
@@ -53,28 +31,29 @@ class RedirectNotificationChannel extends NotificationChannel {
UniversalOpenerController opener = UniversalOpenerController(url: payload);
await opener.initHumHub();
if (isNewRouteSameAsCurrent) {
- navigatorKey.currentState!.pushNamed(WebViewApp.path, arguments: opener);
+ navigatorKey.currentState!.pushNamed(WebView.path, arguments: opener);
return;
}
- navigatorKey.currentState!.pushNamed(WebViewApp.path, arguments: opener);
+ navigatorKey.currentState!.pushNamed(WebView.path, arguments: opener);
} else {
if (payload != null) {
InitFromPush.setPayload(payload);
}
}
}
-}
-
-class InitFromPush {
- static String? _redirectUrlFromInit;
-
- static setPayload(String payload) {
- _redirectUrlFromInit = payload;
- }
- static String? usePayload() {
- String? payload = _redirectUrlFromInit;
- _redirectUrlFromInit = null;
- return payload;
+ static Future getChannel() async {
+ PackageInfo packageInfo = await PackageInfo.fromPlatform(); // Replace this with the actual condition logic
+ switch (packageInfo.packageName) {
+ case 'com.humhub.app':
+ return const NotificationChannel();
+ default:
+ return const NotificationChannelF();
+ }
}
}
+
+// Providers for NotificationChannel and NotificationChannelF
+final notificationChannelProvider = FutureProvider((ref) {
+ return NotificationChannel.getChannel();
+});
diff --git a/lib/util/notifications/init_from_push.dart b/lib/util/notifications/init_from_push.dart
new file mode 100644
index 0000000..7f42313
--- /dev/null
+++ b/lib/util/notifications/init_from_push.dart
@@ -0,0 +1,13 @@
+class InitFromPush {
+ static String? _redirectUrlFromInit;
+
+ static setPayload(String payload) {
+ _redirectUrlFromInit = payload;
+ }
+
+ static String? usePayload() {
+ String? payload = _redirectUrlFromInit;
+ _redirectUrlFromInit = null;
+ return payload;
+ }
+}
\ No newline at end of file
diff --git a/lib/util/notifications/plugin.dart b/lib/util/notifications/plugin.dart
index f79ed02..257a1f9 100644
--- a/lib/util/notifications/plugin.dart
+++ b/lib/util/notifications/plugin.dart
@@ -1,6 +1,7 @@
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;
@@ -17,6 +18,14 @@ 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/notifications/service.dart b/lib/util/notifications/service.dart
index 11cdba9..aaf8908 100644
--- a/lib/util/notifications/service.dart
+++ b/lib/util/notifications/service.dart
@@ -48,7 +48,8 @@ class NotificationService {
static void handleNotification(NotificationResponse response) async {
final parsed = response.payload != null ? json.decode(response.payload!) : {};
if (parsed["redirectUrl"] != null) {
- await RedirectNotificationChannel().onTap(parsed['redirectUrl']);
+ var channel = await NotificationChannel.getChannel();
+ channel.onTap(parsed['redirectUrl']);
return;
}
}
@@ -85,6 +86,13 @@ class NotificationService {
largeIcon: const DrawableResourceAndroidBitmap('@mipmap/ic_launcher'),
color: color,
),
+ iOS: DarwinNotificationDetails(
+ presentAlert: true,
+ presentBadge: true,
+ presentSound: true,
+ threadIdentifier: channel.id,
+ interruptionLevel: InterruptionLevel.timeSensitive,
+ ),
);
}
diff --git a/lib/util/opener_controller.dart b/lib/util/openers/opener_controller.dart
similarity index 95%
rename from lib/util/opener_controller.dart
rename to lib/util/openers/opener_controller.dart
index a5f9647..0ac8eca 100644
--- a/lib/util/opener_controller.dart
+++ b/lib/util/openers/opener_controller.dart
@@ -6,9 +6,9 @@ import 'package:humhub/models/manifest.dart';
import 'package:humhub/util/providers.dart';
import 'package:http/http.dart' as http;
import 'package:loggy/loggy.dart';
-import 'api_provider.dart';
-import 'connectivity_plugin.dart';
-import 'form_helper.dart';
+import '../api_provider.dart';
+import '../connectivity_plugin.dart';
+import '../form_helper.dart';
class OpenerController {
late AsyncValue? asyncData;
@@ -93,7 +93,9 @@ class OpenerController {
String currentUrl = urlTextController.text;
String hash = HumHub.generateHash(32);
if (lastUrl == currentUrl) hash = ref.read(humHubProvider).randomHash ?? hash;
- await ref.read(humHubProvider).setInstance(HumHub(manifest: manifest, randomHash: hash, manifestUrl: manifestUrl));
+ await ref
+ .read(humHubProvider)
+ .setInstance(HumHub(manifest: manifest, randomHash: hash, manifestUrl: manifestUrl));
}
}
diff --git a/lib/util/universal_opener_controller.dart b/lib/util/openers/universal_opener_controller.dart
similarity index 97%
rename from lib/util/universal_opener_controller.dart
rename to lib/util/openers/universal_opener_controller.dart
index 8a9f96d..1177193 100644
--- a/lib/util/universal_opener_controller.dart
+++ b/lib/util/openers/universal_opener_controller.dart
@@ -3,8 +3,8 @@ import 'package:http/http.dart';
import 'package:humhub/models/hum_hub.dart';
import 'package:humhub/models/manifest.dart';
import 'package:http/http.dart' as http;
-import 'api_provider.dart';
-import 'connectivity_plugin.dart';
+import '../api_provider.dart';
+import '../connectivity_plugin.dart';
class UniversalOpenerController {
late AsyncValue? asyncData;
diff --git a/lib/util/override_locale.dart b/lib/util/override_locale.dart
index 075035b..b73b0d0 100644
--- a/lib/util/override_locale.dart
+++ b/lib/util/override_locale.dart
@@ -15,9 +15,9 @@ class OverrideLocale extends StatefulWidget {
static OverrideLocaleModel of(BuildContext context) {
final result = context.dependOnInheritedWidgetOfExactType();
assert(
- result != null,
- 'No OverrideLocale found in context'
- 'Place OverrideLocale widget as high in widget tree as possible.',
+ result != null,
+ 'No OverrideLocale found in context'
+ 'Place OverrideLocale widget as high in widget tree as possible.',
);
return result!;
}
diff --git a/lib/util/providers.dart b/lib/util/providers.dart
index 8704437..3bffdea 100644
--- a/lib/util/providers.dart
+++ b/lib/util/providers.dart
@@ -1,11 +1,9 @@
import 'dart:convert';
-import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:humhub/models/hum_hub.dart';
import 'package:humhub/models/manifest.dart';
-import 'package:humhub/util/extensions.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'const.dart';
@@ -42,7 +40,7 @@ class HumHubNotifier extends ChangeNotifier {
_humHubInstance.isHideOpener = instance.isHideOpener;
_humHubInstance.randomHash = instance.randomHash;
_humHubInstance.appVersion = packageInfo.version;
- _humHubInstance.manifestUrl = instance.manifestUrl;
+ _humHubInstance.manifestUrl = instance.manifestUrl;
_updateSafeStorage();
notifyListeners();
}
@@ -86,33 +84,3 @@ class HumHubNotifier extends ChangeNotifier {
final humHubProvider = ChangeNotifierProvider((ref) {
return HumHubNotifier(HumHub());
});
-
-/// Remembers whether current FirebaseApp is initialized.
-final firebaseInitialized = StateProvider>(
- (ref) => const AsyncValue.loading(),
-);
-
-final _pushTokenProvider = FutureProvider>(
- (ref) async {
- var initialized = ref.watch(firebaseInitialized.notifier).state;
- if (initialized.isLoaded) {
- return AsyncValue.guard(FirebaseMessaging.instance.getToken);
- }
- return const AsyncValue.loading();
- },
-);
-
-/// Provides current push token. Will wait until Firebase is initialized.
-///
-/// See also:
-/// * [_PushPluginState._init]
-final pushTokenProvider = Provider>(
- (ref) {
- var provider = ref.watch(_pushTokenProvider);
- return provider.when(
- data: (value) => value,
- error: (e, s) => AsyncValue.error(e, s),
- loading: () => const AsyncValue.loading(),
- );
- },
-);
diff --git a/lib/util/push/provider.dart b/lib/util/push/provider.dart
new file mode 100644
index 0000000..76ce8b4
--- /dev/null
+++ b/lib/util/push/provider.dart
@@ -0,0 +1,33 @@
+import 'package:firebase_messaging/firebase_messaging.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:humhub/util/extensions.dart';
+
+/// Remembers whether current FirebaseApp is initialized.
+final firebaseInitialized = StateProvider>(
+ (ref) => const AsyncValue.loading(),
+);
+
+final _pushTokenProvider = FutureProvider>(
+ (ref) async {
+ var initialized = ref.watch(firebaseInitialized.notifier).state;
+ if (initialized.isLoaded) {
+ return AsyncValue.guard(FirebaseMessaging.instance.getToken);
+ }
+ return const AsyncValue.loading();
+ },
+);
+
+/// Provides current push token. Will wait until Firebase is initialized.
+///
+/// See also:
+/// * [_PushPluginState._init]
+final pushTokenProvider = Provider>(
+ (ref) {
+ var provider = ref.watch(_pushTokenProvider);
+ return provider.when(
+ data: (value) => value,
+ error: (e, s) => AsyncValue.error(e, s),
+ loading: () => const AsyncValue.loading(),
+ );
+ },
+);
diff --git a/lib/util/push/push_plugin.dart b/lib/util/push/push_plugin.dart
index a01195a..8d773fe 100644
--- a/lib/util/push/push_plugin.dart
+++ b/lib/util/push/push_plugin.dart
@@ -7,17 +7,17 @@ import 'package:humhub/models/event.dart';
import 'package:humhub/util/notifications/channel.dart';
import 'package:humhub/util/notifications/plugin.dart';
import 'package:humhub/util/notifications/service.dart';
+import 'package:humhub/util/push/provider.dart';
import 'package:humhub/util/push/register_token_plugin.dart';
-import 'package:humhub/util/providers.dart';
import 'package:loggy/loggy.dart';
class PushPlugin extends ConsumerStatefulWidget {
final Widget child;
- const PushPlugin({
+ const PushPlugin({
Key? key,
required this.child,
- }) : super(key: key);
+ }) : super(key: key, );
@override
PushPluginState createState() => PushPluginState();
@@ -42,7 +42,7 @@ class PushPluginState extends ConsumerState {
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
logInfo("Firebase messaging onMessageOpenedApp");
final data = PushEvent(message).parsedData;
- RedirectNotificationChannel().onTap(data.redirectUrl);
+ ref.read(notificationChannelProvider).value!.onTap(data.redirectUrl);
});
//When the app is terminated, i.e., app is neither in foreground or background.
@@ -60,10 +60,34 @@ class PushPluginState extends ConsumerState {
@override
void initState() {
+ ref.read(notificationChannelProvider);
_init();
super.initState();
}
+ _handleInitialMsg(RemoteMessage message) {
+ final data = PushEvent(message).parsedData;
+ if (data.redirectUrl != null) {
+ ref.read(notificationChannelProvider).value!.onTap(data.redirectUrl);
+ }
+ }
+
+ Future _handleNotification(RemoteMessage message, NotificationService notificationService) async {
+ // Here we handle the notification that we get form an push notification.
+ final data = PushEvent(message).parsedData;
+ if (message.notification == null) return;
+ final title = message.notification?.title;
+ final body = message.notification?.body;
+ if (title == null || body == null) return;
+ await notificationService.showNotification(
+ ref.read(notificationChannelProvider).value!,
+ title,
+ body,
+ payload: data.channelPayload,
+ redirectUrl: data.redirectUrl,
+ );
+ }
+
@override
Widget build(BuildContext context) {
return RegisterToken(
@@ -72,29 +96,6 @@ class PushPluginState extends ConsumerState {
}
}
-_handleInitialMsg(RemoteMessage message) {
- final data = PushEvent(message).parsedData;
- if (data.redirectUrl != null) {
- RedirectNotificationChannel().onTap(data.redirectUrl);
- }
-}
-
-Future _handleNotification(RemoteMessage message, NotificationService notificationService) async {
- // Here we handle the notification that we get form an push notification.
- final data = PushEvent(message).parsedData;
- if (message.notification == null) return;
- final title = message.notification?.title;
- final body = message.notification?.body;
- if (title == null || body == null) return;
- await notificationService.showNotification(
- RedirectNotificationChannel(),
- title,
- body,
- payload: data.channelPayload,
- redirectUrl: data.redirectUrl,
- );
-}
-
Future _handleData(RemoteMessage message, BuildContext context, WidgetRef ref) async {
// Here we handle the data that we get form an push notification.
PushEventData data = PushEvent(message).parsedData;
diff --git a/lib/util/push/register_token_plugin.dart b/lib/util/push/register_token_plugin.dart
index 329c27f..f01d169 100644
--- a/lib/util/push/register_token_plugin.dart
+++ b/lib/util/push/register_token_plugin.dart
@@ -7,6 +7,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:humhub/util/api_provider.dart';
import 'package:humhub/util/extensions.dart';
import 'package:humhub/util/providers.dart';
+import 'package:humhub/util/push/provider.dart';
import 'package:loggy/loggy.dart';
class RegisterToken extends ConsumerWidget {
@@ -42,7 +43,7 @@ class _RegisterToken extends ConsumerStatefulWidget {
_RegisterTokenState createState() => _RegisterTokenState();
}
-class _RegisterTokenState extends ConsumerState<_RegisterToken>{
+class _RegisterTokenState extends ConsumerState<_RegisterToken> {
Future _maybeRegisterToken() async {
final token = await FirebaseMessaging.instance.getToken();
if (token == null) {
@@ -66,13 +67,13 @@ class _RegisterTokenState extends ConsumerState<_RegisterToken>{
}
Future Function(Dio dio) _registerToken(String? token) => (dio) async {
- await dio.post(
- '/fcm-push/token/update',
- data: {
- 'token': token,
- },
- );
- };
+ await dio.post(
+ '/fcm-push/token/update',
+ data: {
+ 'token': token,
+ },
+ );
+ };
@override
void didUpdateWidget(oldWidget) {
diff --git a/lib/util/router.dart b/lib/util/router.dart
index 8fc58ce..0d69566 100644
--- a/lib/util/router.dart
+++ b/lib/util/router.dart
@@ -34,7 +34,7 @@ class MyRouter {
static var routes = {
Opener.path: (context) => const Opener(),
- WebViewApp.path: (context) => const WebViewApp(),
+ WebView.path: (context) => const WebView(),
'/help': (context) => Platform.isAndroid ? const HelpAndroid() : const HelpIos(),
};
@@ -46,9 +46,9 @@ class MyRouter {
initRoute = Opener.path;
return Opener.path;
case RedirectAction.webView:
- initRoute = WebViewApp.path;
+ initRoute = WebView.path;
initParams = humhub.manifest;
- return WebViewApp.path;
+ return WebView.path;
}
}
}
diff --git a/lib/util/show_dialog.dart b/lib/util/show_dialog.dart
new file mode 100644
index 0000000..780f758
--- /dev/null
+++ b/lib/util/show_dialog.dart
@@ -0,0 +1,58 @@
+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;
+
+ ShowDialog(this.context);
+
+ static ShowDialog of(BuildContext context) {
+ 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,
+ builder: (BuildContext context) {
+ return AlertDialog(
+ title: Text(AppLocalizations.of(context)!.connectivity_popup_title),
+ content: Text(AppLocalizations.of(context)!.connectivity_popup_content),
+ actions: [
+ TextButton(
+ child: Text(AppLocalizations.of(context)!.ok.toUpperCase()),
+ onPressed: () {
+ Navigator.of(context).pop(); // Close the dialog
+ },
+ ),
+ ],
+ );
+ },
+ );
+ }
+}
diff --git a/lib/util/storage_service.dart b/lib/util/storage_service.dart
new file mode 100644
index 0000000..3963f2b
--- /dev/null
+++ b/lib/util/storage_service.dart
@@ -0,0 +1,29 @@
+import 'package:flutter_secure_storage/flutter_secure_storage.dart';
+import 'package:shared_preferences/shared_preferences.dart';
+
+class SecureStorageService {
+ // Private constructor
+ static const FlutterSecureStorage _instance = FlutterSecureStorage();
+
+ // Factory constructor that returns the single instance
+ factory SecureStorageService() {
+ return SecureStorageService._internal();
+ }
+
+ // Private named constructor
+ SecureStorageService._internal();
+
+ // Method to access the single instance of FlutterSecureStorage
+ static FlutterSecureStorage get instance => _instance;
+
+ static clearSecureStorageOnReinstall() async {
+ String key = 'hasRunBefore';
+ SharedPreferences prefs = await SharedPreferences.getInstance();
+ bool hasRunBefore = prefs.getBool(key) ?? false;
+ if (!hasRunBefore) {
+ FlutterSecureStorage storage = const FlutterSecureStorage();
+ await storage.deleteAll();
+ prefs.setBool(key, true);
+ }
+ }
+}
diff --git a/lib/util/web_view_global_controller.dart b/lib/util/web_view_global_controller.dart
new file mode 100644
index 0000000..cab3d6e
--- /dev/null
+++ b/lib/util/web_view_global_controller.dart
@@ -0,0 +1,11 @@
+import 'package:flutter_inappwebview/flutter_inappwebview.dart';
+
+class WebViewGlobalController {
+ static InAppWebViewController? _value;
+
+ static InAppWebViewController? get value => _value;
+
+ static void setValue(InAppWebViewController newValue) {
+ _value = newValue;
+ }
+}
\ No newline at end of file
diff --git a/pubspec.lock b/pubspec.lock
index 2242002..5c38660 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -278,6 +278,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.5.0"
+ flutter_dotenv:
+ dependency: "direct main"
+ description:
+ name: flutter_dotenv
+ sha256: "9357883bdd153ab78cbf9ffa07656e336b8bbb2b5a3ca596b0b27e119f7c7d77"
+ url: "https://pub.dev"
+ source: hosted
+ version: "5.1.0"
flutter_inappwebview:
dependency: "direct main"
description:
diff --git a/pubspec.yaml b/pubspec.yaml
index cff522b..d57756f 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -59,6 +59,7 @@ dependencies:
swipe_to: ^1.0.2
shared_preferences: ^2.2.2
intl: any
+ flutter_dotenv: ^5.1.0
dev_dependencies:
flutter_test:
@@ -90,10 +91,10 @@ flutter:
- asset: assets/fonts/OpenSans-SemiBold.ttf
assets:
+ - assets/
- assets/images/
- assets/images/locale/
- - assets/opener_animation.riv
- - assets/opener_animation_reverse.riv
+ - assets/animations/
# To add assets to your application, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
diff --git a/test/opener_test.dart b/test/opener_test.dart
index 6acfdf1..a8a7758 100644
--- a/test/opener_test.dart
+++ b/test/opener_test.dart
@@ -1,5 +1,5 @@
import 'package:flutter_test/flutter_test.dart';
-import 'package:humhub/util/opener_controller.dart';
+import 'package:humhub/util/openers/opener_controller.dart';
void main() {
void testGroupOfURIs(Map uriMap) {