-
Notifications
You must be signed in to change notification settings - Fork 47
Add file support on opening #203
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -3,7 +3,7 @@ | |||||
| <plist version="1.0"> | ||||||
| <dict> | ||||||
| <key>CADisableMinimumFrameDurationOnPhone</key> | ||||||
| <true/> | ||||||
| <true /> | ||||||
| <key>CFBundleDevelopmentRegion</key> | ||||||
| <string>$(DEVELOPMENT_LANGUAGE)</string> | ||||||
| <key>CFBundleDisplayName</key> | ||||||
|
|
@@ -27,17 +27,19 @@ | |||||
| <key>GADApplicationIdentifier</key> | ||||||
| <string>ca-app-pub-6029472941300558~5225768298</string> | ||||||
| <key>LSRequiresIPhoneOS</key> | ||||||
| <true/> | ||||||
| <true /> | ||||||
| <key>NSDocumentsFolderUsageDescription</key> | ||||||
| <string>WARP needs access to store its configuration files</string> | ||||||
| <key>NSVPNConfigurationUsageDescription</key> | ||||||
| <string>Defyx needs access to VPN configurations to secure your connection.</string> | ||||||
| <key>NSUserTrackingUsageDescription</key> | ||||||
| <string>We need permission to show you personalized ads. This helps support our free VPN service. Declining means you'll see generic ads instead.</string> | ||||||
| <string>We need permission to show you personalized ads. This helps support our free VPN | ||||||
| service. Declining means you'll see generic ads instead.</string> | ||||||
| <key>NSPhotoLibraryUsageDescription</key> | ||||||
| <string>Defyx needs access to your photo library to let you select and share images, such as profile pictures or VPN connection QR codes.</string> | ||||||
| <string>Defyx needs access to your photo library to let you select and share images, such as | ||||||
| profile pictures or VPN connection QR codes.</string> | ||||||
| <key>UIApplicationSupportsIndirectInputEvents</key> | ||||||
| <true/> | ||||||
| <true /> | ||||||
| <key>UILaunchStoryboardName</key> | ||||||
| <string>LaunchScreen</string> | ||||||
| <key>UIMainStoryboardFile</key> | ||||||
|
|
@@ -64,36 +66,53 @@ | |||||
| <key>NSAppTransportSecurity</key> | ||||||
| <dict> | ||||||
| <key>NSAllowsArbitraryLoads</key> | ||||||
| <true/> | ||||||
| <true /> | ||||||
| </dict> | ||||||
| <key>UIStatusBarHidden</key> | ||||||
| <false/> | ||||||
| <key>LSSupportsOpeningDocumentsInPlace</key> | ||||||
| <true/> | ||||||
| <key>UIFileSharingEnabled</key> | ||||||
| <true/> | ||||||
| <key>UISupportsDocumentBrowser</key> | ||||||
| <true/> | ||||||
| <key>UTImportedTypeDeclarations</key> | ||||||
| <array> | ||||||
| <dict> | ||||||
| <key>UTTypeConformsTo</key> | ||||||
| <array> | ||||||
| <string>public.data</string> | ||||||
| <string>public.content</string> | ||||||
| </array> | ||||||
| <key>UTTypeDescription</key> | ||||||
| <string>DefyX Configuration File</string> | ||||||
| <key>UTTypeIdentifier</key> | ||||||
| <string>de.unboundtech.defyxvpn.dfx</string> | ||||||
| <key>UTTypeTagSpecification</key> | ||||||
| <false /> | ||||||
| <key>LSSupportsOpeningDocumentsInPlace</key> | ||||||
| <true /> | ||||||
| <key>UIFileSharingEnabled</key> | ||||||
| <true /> | ||||||
| <key>UISupportsDocumentBrowser</key> | ||||||
| <true /> | ||||||
| <key>UTImportedTypeDeclarations</key> | ||||||
| <array> | ||||||
| <dict> | ||||||
| <key>UTTypeConformsTo</key> | ||||||
| <array> | ||||||
| <string>public.data</string> | ||||||
| <string>public.content</string> | ||||||
| </array> | ||||||
| <key>UTTypeDescription</key> | ||||||
| <string>DefyX Configuration File</string> | ||||||
| <key>UTTypeIdentifier</key> | ||||||
| <string>de.unboundtech.defyxvpn.dfx</string> | ||||||
| <key>UTTypeTagSpecification</key> | ||||||
| <dict> | ||||||
| <key>public.filename-extension</key> | ||||||
| <array> | ||||||
| <string>dfx</string> | ||||||
| </array> | ||||||
| </dict> | ||||||
| </dict> | ||||||
| </array> | ||||||
| <key>CFBundleDocumentTypes</key> | ||||||
| <array> | ||||||
| <dict> | ||||||
| <key>public.filename-extension</key> | ||||||
| <key>CFBundleTypeName</key> | ||||||
| <string>DFX File</string> | ||||||
| <key>LSHandlerRank</key> | ||||||
| <string>Alternate</string> | ||||||
| <key>CFBundleTypeExtensions</key> | ||||||
| <array> | ||||||
| <string>dfx</string> | ||||||
| </array> | ||||||
| <key>LSItemContentTypes</key> | ||||||
| <array> | ||||||
| <string>public.data</string> | ||||||
|
||||||
| <string>public.data</string> | |
| <string>de.unboundtech.defyxvpn.dfx</string> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,6 +4,7 @@ import 'package:app_tracking_transparency/app_tracking_transparency.dart'; | |
| import 'package:defyx_vpn/app/advertise_director.dart'; | ||
| import 'package:defyx_vpn/app/router/app_router.dart'; | ||
| import 'package:defyx_vpn/core/theme/app_theme.dart'; | ||
| import 'package:defyx_vpn/modules/core/file_listener.dart'; | ||
| import 'package:defyx_vpn/modules/core/vpn.dart'; | ||
| import 'package:defyx_vpn/modules/core/desktop_platform_handler.dart'; | ||
| import 'package:defyx_vpn/modules/main/presentation/widgets/ump_service.dart'; | ||
|
|
@@ -35,6 +36,7 @@ class App extends ConsumerWidget { | |
| } | ||
|
|
||
| Future<bool> _initializeApp(WidgetRef ref) async { | ||
| FileListener().init(ProviderScope.containerOf(ref.context)); | ||
| await VPN(ProviderScope.containerOf(ref.context)).getVPNStatus(); | ||
| await AlertService().init(); | ||
|
Comment on lines
38
to
41
|
||
| await AnimationService().init(); | ||
|
|
@@ -57,20 +59,22 @@ class App extends ConsumerWidget { | |
| if (Platform.isAndroid || Platform.isIOS) { | ||
| // Request App Tracking Transparency (iOS only) | ||
| if (Platform.isIOS) { | ||
| final status = await AppTrackingTransparency.trackingAuthorizationStatus; | ||
| final status = | ||
| await AppTrackingTransparency.trackingAuthorizationStatus; | ||
| if (status == TrackingStatus.notDetermined) { | ||
| // Small delay to ensure UI is ready | ||
| await Future.delayed(const Duration(milliseconds: 500)); | ||
| final result = await AppTrackingTransparency.requestTrackingAuthorization(); | ||
| final result = | ||
| await AppTrackingTransparency.requestTrackingAuthorization(); | ||
| debugPrint('📱 ATT Authorization: $result'); | ||
| } else { | ||
| debugPrint('📱 ATT Status: $status'); | ||
| } | ||
| } | ||
|
|
||
| // Get UMP service with cache integration | ||
| final umpService = ref.read(umpServiceProvider); | ||
|
|
||
| // Request UMP consent (checks cache first) | ||
| await umpService.requestConsent( | ||
| onDone: () async { | ||
|
|
@@ -91,41 +95,42 @@ class App extends ConsumerWidget { | |
| final designSize = _getDesignSize(context); | ||
|
|
||
| return ToastificationWrapper( | ||
| config: ToastificationConfig( | ||
| maxToastLimit: 1, | ||
| blockBackgroundInteraction: false, | ||
| applyMediaQueryViewInsets: true, | ||
| ), | ||
| child: ScreenUtilInit( | ||
| designSize: designSize, | ||
| minTextAdapt: true, | ||
| splitScreenMode: true, | ||
| builder: (_, __) { | ||
| return MaterialApp.router( | ||
| title: 'Defyx', | ||
| theme: AppTheme.lightTheme, | ||
| darkTheme: AppTheme.darkTheme, | ||
| themeMode: ThemeMode.light, | ||
| routerConfig: router, | ||
| builder: _appBuilder, | ||
| debugShowCheckedModeBanner: false, | ||
| // Force English locale (comment out to enable device language detection) | ||
| locale: const Locale('en'), | ||
| localizationsDelegates: const [ | ||
| AppLocalizations.delegate, | ||
| GlobalMaterialLocalizations.delegate, | ||
| GlobalWidgetsLocalizations.delegate, | ||
| GlobalCupertinoLocalizations.delegate, | ||
| ], | ||
| supportedLocales: const [ | ||
| Locale('en'), | ||
| Locale('fa'), | ||
| Locale('zh'), | ||
| Locale('ru'), | ||
| ], | ||
| ); | ||
| }, | ||
| )); | ||
| config: ToastificationConfig( | ||
| maxToastLimit: 1, | ||
| blockBackgroundInteraction: false, | ||
| applyMediaQueryViewInsets: true, | ||
| ), | ||
| child: ScreenUtilInit( | ||
| designSize: designSize, | ||
| minTextAdapt: true, | ||
| splitScreenMode: true, | ||
| builder: (_, __) { | ||
| return MaterialApp.router( | ||
| title: 'Defyx', | ||
| theme: AppTheme.lightTheme, | ||
| darkTheme: AppTheme.darkTheme, | ||
| themeMode: ThemeMode.light, | ||
| routerConfig: router, | ||
| builder: _appBuilder, | ||
| debugShowCheckedModeBanner: false, | ||
| // Force English locale (comment out to enable device language detection) | ||
| locale: const Locale('en'), | ||
| localizationsDelegates: const [ | ||
| AppLocalizations.delegate, | ||
| GlobalMaterialLocalizations.delegate, | ||
| GlobalWidgetsLocalizations.delegate, | ||
| GlobalCupertinoLocalizations.delegate, | ||
| ], | ||
| supportedLocales: const [ | ||
| Locale('en'), | ||
| Locale('fa'), | ||
| Locale('zh'), | ||
| Locale('ru'), | ||
| ], | ||
| ); | ||
| }, | ||
| ), | ||
| ); | ||
| } | ||
|
|
||
| Size _getDesignSize(BuildContext context) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,56 @@ | ||||||||||||||||||||||||||||||||||
| import 'dart:io'; | ||||||||||||||||||||||||||||||||||
| import 'package:defyx_vpn/core/data/local/remote/api/flowline_service.dart'; | ||||||||||||||||||||||||||||||||||
| import 'package:flutter/material.dart'; | ||||||||||||||||||||||||||||||||||
| import 'package:flutter_riverpod/flutter_riverpod.dart'; | ||||||||||||||||||||||||||||||||||
| import 'package:receive_sharing_intent/receive_sharing_intent.dart'; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| class FileListener { | ||||||||||||||||||||||||||||||||||
| ProviderContainer? _container; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| void _startListening(void Function(String dfxPath) onDfxFile) { | ||||||||||||||||||||||||||||||||||
| ReceiveSharingIntent.instance.getMediaStream().listen( | ||||||||||||||||||||||||||||||||||
| (List<SharedMediaFile> files) async { | ||||||||||||||||||||||||||||||||||
| if (files.isEmpty) return; | ||||||||||||||||||||||||||||||||||
| final file = files.first; | ||||||||||||||||||||||||||||||||||
| if (file.path.endsWith('.dfx')) { | ||||||||||||||||||||||||||||||||||
| final content = await _readFileAsString(file.path); | ||||||||||||||||||||||||||||||||||
| onDfxFile(content); | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+15
to
+17
|
||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+11
to
+18
|
||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||
| onError: (err) { | ||||||||||||||||||||||||||||||||||
| debugPrint('Error receiving shared media: $err'); | ||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| ReceiveSharingIntent.instance | ||||||||||||||||||||||||||||||||||
| .getInitialMedia() | ||||||||||||||||||||||||||||||||||
| .then((List<SharedMediaFile> files) async { | ||||||||||||||||||||||||||||||||||
| if (files.isEmpty) return; | ||||||||||||||||||||||||||||||||||
| final file = files.first; | ||||||||||||||||||||||||||||||||||
| if (file.path.endsWith('.dfx')) { | ||||||||||||||||||||||||||||||||||
| final content = await _readFileAsString(file.path); | ||||||||||||||||||||||||||||||||||
| onDfxFile(content); | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||
| .catchError((err) { | ||||||||||||||||||||||||||||||||||
| debugPrint('Error getting initial shared media: $err'); | ||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| Future<String> _readFileAsString(String path) async { | ||||||||||||||||||||||||||||||||||
| final file = File(path); | ||||||||||||||||||||||||||||||||||
| return await file.readAsString(); | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| Future<void> _handleFile(String content) async { | ||||||||||||||||||||||||||||||||||
| _container ??= ProviderContainer(); | ||||||||||||||||||||||||||||||||||
| await _container | ||||||||||||||||||||||||||||||||||
| ?.read(flowlineServiceProvider) | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+46
to
+48
|
||||||||||||||||||||||||||||||||||
| _container ??= ProviderContainer(); | |
| await _container | |
| ?.read(flowlineServiceProvider) | |
| final container = _container; | |
| assert( | |
| container != null, | |
| 'FileListener.init must be called with a ProviderContainer before handling files.', | |
| ); | |
| if (container == null) { | |
| debugPrint( | |
| 'FileListener used before init; ignoring received file content.', | |
| ); | |
| return; | |
| } | |
| await container | |
| .read(flowlineServiceProvider) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The intent filter only matches
android:scheme="file", but on modern Android most file managers/DocumentProvider flows deliverACTION_VIEWURIs ascontent://.... With the current filter, tapping a.dfxfile is unlikely to resolve to the app.Consider adding a
contentscheme variant (and handling persisted URI permissions as needed), or registering by MIME/type/extension in a way that matches how files are actually opened (and ensure the Flutter side can read the content URI).