diff --git a/ios/Podfile b/ios/Podfile new file mode 100644 index 00000000..98a90b8a --- /dev/null +++ b/ios/Podfile @@ -0,0 +1,87 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '9.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def parse_KV_file(file, separator='=') + file_abs_path = File.expand_path(file) + if !File.exists? file_abs_path + return []; + end + generated_key_values = {} + skip_line_start_symbols = ["#", "/"] + File.foreach(file_abs_path) do |line| + next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } + plugin = line.split(pattern=separator) + if plugin.length == 2 + podname = plugin[0].strip() + path = plugin[1].strip() + podpath = File.expand_path("#{path}", file_abs_path) + generated_key_values[podname] = podpath + else + puts "Invalid plugin specification: #{line}" + end + end + generated_key_values +end + +target 'Runner' do + # Flutter Pod + + copied_flutter_dir = File.join(__dir__, 'Flutter') + copied_framework_path = File.join(copied_flutter_dir, 'Flutter.framework') + copied_podspec_path = File.join(copied_flutter_dir, 'Flutter.podspec') + unless File.exist?(copied_framework_path) && File.exist?(copied_podspec_path) + # Copy Flutter.framework and Flutter.podspec to Flutter/ to have something to link against if the xcode backend script has not run yet. + # That script will copy the correct debug/profile/release version of the framework based on the currently selected Xcode configuration. + # CocoaPods will not embed the framework on pod install (before any build phases can generate) if the dylib does not exist. + + generated_xcode_build_settings_path = File.join(copied_flutter_dir, 'Generated.xcconfig') + unless File.exist?(generated_xcode_build_settings_path) + raise "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + generated_xcode_build_settings = parse_KV_file(generated_xcode_build_settings_path) + cached_framework_dir = generated_xcode_build_settings['FLUTTER_FRAMEWORK_DIR']; + + unless File.exist?(copied_framework_path) + FileUtils.cp_r(File.join(cached_framework_dir, 'Flutter.framework'), copied_flutter_dir) + end + unless File.exist?(copied_podspec_path) + FileUtils.cp(File.join(cached_framework_dir, 'Flutter.podspec'), copied_flutter_dir) + end + end + + # Keep pod path relative so it can be checked into Podfile.lock. + pod 'Flutter', :path => 'Flutter' + + # Plugin Pods + + # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock + # referring to absolute paths on developers' machines. + system('rm -rf .symlinks') + system('mkdir -p .symlinks/plugins') + plugin_pods = parse_KV_file('../.flutter-plugins') + plugin_pods.each do |name, path| + symlink = File.join('.symlinks', 'plugins', name) + File.symlink(path, symlink) + pod name, :path => File.join(symlink, 'ios') + end +end + +# Prevent Cocoapods from embedding a second Flutter framework and causing an error with the new Xcode build system. +install! 'cocoapods', :disable_input_output_paths => true + +post_install do |installer| + installer.pods_project.targets.each do |target| + target.build_configurations.each do |config| + config.build_settings['ENABLE_BITCODE'] = 'NO' + end + end +end diff --git a/ios/Podfile.lock b/ios/Podfile.lock new file mode 100644 index 00000000..d5d23f84 --- /dev/null +++ b/ios/Podfile.lock @@ -0,0 +1,88 @@ +PODS: + - Flutter (1.0.0) + - flutter_downloader (0.0.1): + - Flutter + - flutter_native_timezone (0.0.1): + - Flutter + - package_info (0.0.1): + - Flutter + - path_provider (0.0.1): + - Flutter + - permission_handler (3.3.0): + - Flutter + - shared_preferences (0.0.1): + - Flutter + - shared_preferences_macos (0.0.1): + - Flutter + - shared_preferences_web (0.0.1): + - Flutter + - url_launcher (0.0.1): + - Flutter + - url_launcher_macos (0.0.1): + - Flutter + - url_launcher_web (0.0.1): + - Flutter + - webview_flutter (0.0.1): + - Flutter + +DEPENDENCIES: + - Flutter (from `Flutter`) + - flutter_downloader (from `.symlinks/plugins/flutter_downloader/ios`) + - flutter_native_timezone (from `.symlinks/plugins/flutter_native_timezone/ios`) + - package_info (from `.symlinks/plugins/package_info/ios`) + - path_provider (from `.symlinks/plugins/path_provider/ios`) + - permission_handler (from `.symlinks/plugins/permission_handler/ios`) + - shared_preferences (from `.symlinks/plugins/shared_preferences/ios`) + - shared_preferences_macos (from `.symlinks/plugins/shared_preferences_macos/ios`) + - shared_preferences_web (from `.symlinks/plugins/shared_preferences_web/ios`) + - url_launcher (from `.symlinks/plugins/url_launcher/ios`) + - url_launcher_macos (from `.symlinks/plugins/url_launcher_macos/ios`) + - url_launcher_web (from `.symlinks/plugins/url_launcher_web/ios`) + - webview_flutter (from `.symlinks/plugins/webview_flutter/ios`) + +EXTERNAL SOURCES: + Flutter: + :path: Flutter + flutter_downloader: + :path: ".symlinks/plugins/flutter_downloader/ios" + flutter_native_timezone: + :path: ".symlinks/plugins/flutter_native_timezone/ios" + package_info: + :path: ".symlinks/plugins/package_info/ios" + path_provider: + :path: ".symlinks/plugins/path_provider/ios" + permission_handler: + :path: ".symlinks/plugins/permission_handler/ios" + shared_preferences: + :path: ".symlinks/plugins/shared_preferences/ios" + shared_preferences_macos: + :path: ".symlinks/plugins/shared_preferences_macos/ios" + shared_preferences_web: + :path: ".symlinks/plugins/shared_preferences_web/ios" + url_launcher: + :path: ".symlinks/plugins/url_launcher/ios" + url_launcher_macos: + :path: ".symlinks/plugins/url_launcher_macos/ios" + url_launcher_web: + :path: ".symlinks/plugins/url_launcher_web/ios" + webview_flutter: + :path: ".symlinks/plugins/webview_flutter/ios" + +SPEC CHECKSUMS: + Flutter: 0e3d915762c693b495b44d77113d4970485de6ec + flutter_downloader: 058b9c41564a90500f67f3e432e3524613a7fd83 + flutter_native_timezone: e72ff1900460ff57cfd331d1758050c9269f3e99 + package_info: 48b108e75b8802c2d5e126f208ef540561c98aef + path_provider: fb74bd0465e96b594bb3b5088ee4a4e7bb1f2a9d + permission_handler: 67637977b227d62d46bfbf524f335f8568de5a73 + shared_preferences: 430726339841afefe5142b9c1f50cb6bd7793e01 + shared_preferences_macos: f3f29b71ccbb56bf40c9dd6396c9acf15e214087 + shared_preferences_web: 141cce0c3ed1a1c5bf2a0e44f52d31eeb66e5ea9 + url_launcher: a1c0cc845906122c4784c542523d8cacbded5626 + url_launcher_macos: fd7894421cd39320dce5f292fc99ea9270b2a313 + url_launcher_web: e5527357f037c87560776e36436bf2b0288b965c + webview_flutter: 1aa7604e6cdb451a9b7ed2c37d5454c0b440246b + +PODFILE CHECKSUM: 3dbe063e9c90a5d7c9e4e76e70a821b9e2c1d271 + +COCOAPODS: 1.8.3 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index e2baaeec..e8b5c702 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -47,6 +47,7 @@ 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; }; 3D4BC0D723C787F7002D08EA /* Debug-brb.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Debug-brb.xcconfig"; sourceTree = ""; }; + 3D6BD53C240D9194002B2EA4 /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = usr/lib/libsqlite3.tbd; sourceTree = SDKROOT; }; 40D42FD375749A70301C939A /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; @@ -81,6 +82,7 @@ 29E3041B0FE1DA60C9EFE097 /* Frameworks */ = { isa = PBXGroup; children = ( + 3D6BD53C240D9194002B2EA4 /* libsqlite3.tbd */, BE79F876E04DB7AC9481B591 /* libPods-Runner.a */, ); name = Frameworks; @@ -192,6 +194,7 @@ TargetAttributes = { 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; + DevelopmentTeam = WP53SSE8S6; }; }; }; @@ -399,8 +402,9 @@ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = S8QB4VV633; + DEVELOPMENT_TEAM = ""; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -412,8 +416,9 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.schulcloud; + PRODUCT_BUNDLE_IDENTIFIER = org.schulcloud; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; VERSIONING_SYSTEM = "apple-generic"; }; name = Profile; @@ -479,6 +484,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = WP53SSE8S6; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -490,7 +496,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.schulcloud; + PRODUCT_BUNDLE_IDENTIFIER = org.schulcloud; PRODUCT_NAME = "$(TARGET_NAME)"; VERSIONING_SYSTEM = "apple-generic"; }; @@ -550,6 +556,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = WP53SSE8S6; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -561,7 +568,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.schulcloud; + PRODUCT_BUNDLE_IDENTIFIER = org.schulcloud; PRODUCT_NAME = "$(TARGET_NAME)"; VERSIONING_SYSTEM = "apple-generic"; }; @@ -676,6 +683,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = WP53SSE8S6; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -687,7 +695,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.schulcloud; + PRODUCT_BUNDLE_IDENTIFIER = org.schulcloud; PRODUCT_NAME = "$(TARGET_NAME)"; VERSIONING_SYSTEM = "apple-generic"; }; @@ -699,6 +707,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = WP53SSE8S6; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -710,7 +719,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.schulcloud; + PRODUCT_BUNDLE_IDENTIFIER = org.schulcloud; PRODUCT_NAME = "$(TARGET_NAME)"; VERSIONING_SYSTEM = "apple-generic"; }; diff --git a/ios/Runner/AppDelegate.m b/ios/Runner/AppDelegate.m index 59a72e90..c8f0db85 100644 --- a/ios/Runner/AppDelegate.m +++ b/ios/Runner/AppDelegate.m @@ -1,11 +1,19 @@ #include "AppDelegate.h" #include "GeneratedPluginRegistrant.h" +#include "FlutterDownloaderPlugin.h" @implementation AppDelegate +void registerPlugins(NSObject* registry) { + if (![registry hasPlugin:@"FlutterDownloaderPlugin"]) { + [FlutterDownloaderPlugin registerWithRegistrar:[registry registrarForPlugin:@"FlutterDownloaderPlugin"]]; + } +} + - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [GeneratedPluginRegistrant registerWithRegistry:self]; + [FlutterDownloaderPlugin setPluginRegistrantCallback:registerPlugins]; // Override point for customization after application launch. return [super application:application didFinishLaunchingWithOptions:launchOptions]; } diff --git a/ios/Runner/Base.lproj/Main.storyboard b/ios/Runner/Base.lproj/Main.storyboard index f3c28516..aeb09a26 100644 --- a/ios/Runner/Base.lproj/Main.storyboard +++ b/ios/Runner/Base.lproj/Main.storyboard @@ -1,8 +1,10 @@ - - + + + - + + @@ -14,13 +16,14 @@ - + - + + diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 9029a5c6..f5914cb1 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -22,10 +22,13 @@ $(FLUTTER_BUILD_NUMBER) LSRequiresIPhoneOS + UIBackgroundModes + + fetch + remote-notification + UILaunchStoryboardName LaunchScreen - io.flutter.embedded_views_preview - true UIMainStoryboardFile Main UISupportedInterfaceOrientations @@ -43,5 +46,7 @@ UIViewControllerBasedStatusBarAppearance + io.flutter.embedded_views_preview + true diff --git a/lib/file/bloc.dart b/lib/file/bloc.dart index 216a554e..182ea652 100644 --- a/lib/file/bloc.dart +++ b/lib/file/bloc.dart @@ -1,4 +1,7 @@ import 'dart:convert'; +import 'dart:io'; +import 'dart:isolate'; +import 'dart:ui'; import 'package:flutter/widgets.dart'; import 'package:flutter_cached/flutter_cached.dart'; @@ -7,6 +10,7 @@ import 'package:meta/meta.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:schulcloud/app/app.dart'; import 'package:schulcloud/course/course.dart'; +import 'package:path_provider/path_provider.dart'; import 'data.dart'; @@ -75,13 +79,37 @@ class FileBloc { ); final signedUrl = json.decode(response.body)['url']; + final appDirectory = await getApplicationDocumentsDirectory(); + var appDirectoryAsString = appDirectory.path; + if (Platform.isIOS && appDirectoryAsString.endsWith(Platform.pathSeparator) == false){ + appDirectoryAsString = appDirectoryAsString + Platform.pathSeparator; + } + await FlutterDownloader.enqueue( url: signedUrl, - savedDir: '/sdcard/Download', + savedDir: appDirectoryAsString, fileName: file.name, showNotification: true, - openFileFromNotification: true, + openFileFromNotification: true, // Android only ); + + FlutterDownloader.registerCallback(_onDownloadStatusUpdate); + + final port = ReceivePort(); + IsolateNameServer.registerPortWithName(port.sendPort, 'port123'); + port.listen((data) { + final taskId = data[0] as String; + final status = data[1] as DownloadTaskStatus; + // final progress = data[2] as int; + if (status == DownloadTaskStatus.complete) { + FlutterDownloader.open(taskId: taskId); + } + }); + } + + static void _onDownloadStatusUpdate(String taskId, DownloadTaskStatus status, int progress) { + // print('Download task with id $taskId has status $status and progress $progress'); + IsolateNameServer.lookupPortByName('port123').send([taskId, status, progress]); } Future ensureStoragePermissionGranted() async { diff --git a/lib/main.dart b/lib/main.dart index 6ed0cdb8..a7e38497 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -9,6 +9,7 @@ import 'package:schulcloud/file/file.dart'; import 'package:schulcloud/login/login.dart'; import 'package:schulcloud/news/news.dart'; import 'package:time_machine/time_machine.dart'; +import 'package:flutter_downloader/flutter_downloader.dart'; const _schulCloudRed = MaterialColor(0xffb10438, { 50: Color(0xfffce2e6), @@ -58,6 +59,7 @@ const schulCloudAppConfig = AppConfig( Future main({AppConfig appConfig = schulCloudAppConfig}) async { await initializeHive(); + await FlutterDownloader.initialize(); services ..registerSingletonAsync((_) async { diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..48e341a0 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3 @@ +{ + "lockfileVersion": 1 +} diff --git a/pubspec.lock b/pubspec.lock index 15a10476..d9937b13 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -208,7 +208,7 @@ packages: name: flutter_downloader url: "https://pub.dartlang.org" source: hosted - version: "1.1.9" + version: "1.4.1" flutter_html: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 68d6377a..3076b292 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,8 +17,7 @@ dependencies: datetime_picker_formfield: ^1.0.0 flare_flutter: ^1.5.8 flutter_cached: ^3.0.1 - # The current version of flutter_downloader, 1.2.1, causes the build to fail. Until this is fixed, we rely on an older version. - flutter_downloader: 1.1.9 + flutter_downloader: ^1.4.1 flutter_html: ^0.10.4 flutter_native_timezone: ^1.0.4 flutter_svg: ^0.14.2