From b7b3a37b52c27a4ab04ffc20b9938c9c79b5c8f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Horv=C3=A1th=20Gergely?= Date: Thu, 2 May 2024 01:41:04 +0200 Subject: [PATCH 01/13] - data backup - I can't test it because I'm stuck on the login screen. --- refilc/ios/PrivacyInfo.xcprivacy | 20 ++ refilc/ios/Runner.xcodeproj/project.pbxproj | 60 +++-- refilc/ios/Runner/AppDelegate.swift | 108 +++++++- refilc/ios/Runner/Info.plist | 255 +++++++++--------- refilc/ios/Runner/public_vars.swift | 12 + refilc/ios/livecard/LiveActivityManager.swift | 92 +++++++ refilc/ios/livecard/lesson_model.swift | 65 +++-- refilc/ios/livecard/livecard.swift | 220 +++++++-------- .../lib/api/providers/live_card_provider.dart | 239 +++++++++------- .../liveactivity/platform_channel.dart | 43 +++ refilc/lib/api/providers/sync.dart | 10 + refilc/lib/helpers/live_activity_helper.dart | 15 ++ refilc/lib/main.dart | 58 +++- refilc/lib/models/settings.dart | 6 + .../lib/screens/settings/settings_helper.dart | 9 + 15 files changed, 817 insertions(+), 395 deletions(-) create mode 100644 refilc/ios/PrivacyInfo.xcprivacy create mode 100644 refilc/ios/Runner/public_vars.swift create mode 100644 refilc/ios/livecard/LiveActivityManager.swift create mode 100644 refilc/lib/api/providers/liveactivity/platform_channel.dart create mode 100644 refilc/lib/helpers/live_activity_helper.dart diff --git a/refilc/ios/PrivacyInfo.xcprivacy b/refilc/ios/PrivacyInfo.xcprivacy new file mode 100644 index 0000000..a1c5b3b --- /dev/null +++ b/refilc/ios/PrivacyInfo.xcprivacy @@ -0,0 +1,20 @@ + + + + + + NSPrivacyAccessedAPITypes + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryUserDefaults + + NSPrivacyAccessedAPITypeReasons + + CA92.1 + + + + + \ No newline at end of file diff --git a/refilc/ios/Runner.xcodeproj/project.pbxproj b/refilc/ios/Runner.xcodeproj/project.pbxproj index 806d9e6..e4a26c0 100644 --- a/refilc/ios/Runner.xcodeproj/project.pbxproj +++ b/refilc/ios/Runner.xcodeproj/project.pbxproj @@ -17,7 +17,13 @@ 3127F7A828EAEE8500C2EFB3 /* lesson_model.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3127F7A728EAEE8500C2EFB3 /* lesson_model.swift */; }; 373A6ECB5FC71FE9D8AF2EDB /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1F0ADD56276103500A3016C8 /* Pods_Runner.framework */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; - 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 4F35BF332BE2FFA30098EF72 /* public_vars.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F35BF322BE2FFA30098EF72 /* public_vars.swift */; }; + 4F35BF352BE2FFD80098EF72 /* LiveActivityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F35BF342BE2FFD80098EF72 /* LiveActivityManager.swift */; }; + 4F35BF362BE2FFD80098EF72 /* LiveActivityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F35BF342BE2FFD80098EF72 /* LiveActivityManager.swift */; }; + 4F35BF382BE300A70098EF72 /* lesson_model.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3127F7A728EAEE8500C2EFB3 /* lesson_model.swift */; }; + 4F35BF392BE300B10098EF72 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 4F35BF3A2BE301180098EF72 /* public_vars.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F35BF322BE2FFA30098EF72 /* public_vars.swift */; }; + 4F35BF3E2BE304550098EF72 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 4F35BF3D2BE304550098EF72 /* PrivacyInfo.xcprivacy */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; @@ -73,6 +79,10 @@ 3127F7A728EAEE8500C2EFB3 /* lesson_model.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = lesson_model.swift; sourceTree = ""; }; 317DE77A294F6FFB002E323E /* livecard.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = livecard.entitlements; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 4F35BF322BE2FFA30098EF72 /* public_vars.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = public_vars.swift; sourceTree = ""; }; + 4F35BF342BE2FFD80098EF72 /* LiveActivityManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveActivityManager.swift; sourceTree = ""; }; + 4F35BF3B2BE303A40098EF72 /* app_group_directory.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = app_group_directory.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 4F35BF3D2BE304550098EF72 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; 707F8089D970F81C480F73C4 /* 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 = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; @@ -118,6 +128,7 @@ 3127F79728EAEDE300C2EFB3 /* Assets.xcassets */, 3127F79928EAEDE300C2EFB3 /* Info.plist */, 3127F7A528EAEE5900C2EFB3 /* livecard.swift */, + 4F35BF342BE2FFD80098EF72 /* LiveActivityManager.swift */, ); path = livecard; sourceTree = ""; @@ -125,6 +136,7 @@ 6640A963014A9D4F31026053 /* Frameworks */ = { isa = PBXGroup; children = ( + 4F35BF3B2BE303A40098EF72 /* app_group_directory.framework */, 1F0ADD56276103500A3016C8 /* Pods_Runner.framework */, 3127F73F28EAEC8A00C2EFB3 /* IntentsUI.framework */, 3127F75528EAECC800C2EFB3 /* WidgetKit.framework */, @@ -157,6 +169,7 @@ 97C146E51CF9000F007C117D = { isa = PBXGroup; children = ( + 4F35BF3D2BE304550098EF72 /* PrivacyInfo.xcprivacy */, 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 3127F78F28EAEDE200C2EFB3 /* livecard */, @@ -187,6 +200,7 @@ 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + 4F35BF322BE2FFA30098EF72 /* public_vars.swift */, ); path = Runner; sourceTree = ""; @@ -242,7 +256,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastSwiftUpdateCheck = 1410; + LastSwiftUpdateCheck = 1530; LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { @@ -290,6 +304,7 @@ 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 4F35BF3E2BE304550098EF72 /* PrivacyInfo.xcprivacy in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -320,6 +335,8 @@ buildActionMask = 12; files = ( ); + inputFileListPaths = ( + ); inputPaths = ( "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); @@ -372,7 +389,7 @@ 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; - buildActionMask = 2147483647; + buildActionMask = 12; files = ( ); inputPaths = ( @@ -391,9 +408,11 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 4F35BF362BE2FFD80098EF72 /* LiveActivityManager.swift in Sources */, 3127F7A828EAEE8500C2EFB3 /* lesson_model.swift in Sources */, 3127F7A428EAEE3D00C2EFB3 /* livecard.intentdefinition in Sources */, 3127F7A628EAEE5900C2EFB3 /* livecard.swift in Sources */, + 4F35BF3A2BE301180098EF72 /* public_vars.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -401,8 +420,11 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 4F35BF392BE300B10098EF72 /* AppDelegate.swift in Sources */, + 4F35BF382BE300A70098EF72 /* lesson_model.swift in Sources */, + 4F35BF352BE2FFD80098EF72 /* LiveActivityManager.swift in Sources */, 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + 4F35BF332BE2FFA30098EF72 /* public_vars.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -496,7 +518,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CURRENT_PROJECT_VERSION = 250; - DEVELOPMENT_TEAM = 4DKAF249F3; + DEVELOPMENT_TEAM = UT7MSP4GWZ; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = reFilc; @@ -506,7 +528,7 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 5.0.0; - PRODUCT_BUNDLE_IDENTIFIER = com.refilc2.naplo; + PRODUCT_BUNDLE_IDENTIFIER = com.refilcrel.naplo; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -528,14 +550,14 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 250; - DEVELOPMENT_TEAM = 4DKAF249F3; + DEVELOPMENT_TEAM = UT7MSP4GWZ; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = livecard/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = livecard; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INFOPLIST_KEY_NSSupportsLiveActivities = YES; - IPHONEOS_DEPLOYMENT_TARGET = 16.1; + IPHONEOS_DEPLOYMENT_TARGET = 16.2; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -544,7 +566,7 @@ MARKETING_VERSION = 5.0.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = com.refilc2.naplo.livecardpro; + PRODUCT_BUNDLE_IDENTIFIER = com.refilcrel.naplo.livecardpro; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; @@ -570,14 +592,14 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 250; - DEVELOPMENT_TEAM = 4DKAF249F3; + DEVELOPMENT_TEAM = UT7MSP4GWZ; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = livecard/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = livecard; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INFOPLIST_KEY_NSSupportsLiveActivities = YES; - IPHONEOS_DEPLOYMENT_TARGET = 16.1; + IPHONEOS_DEPLOYMENT_TARGET = 16.2; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -585,7 +607,7 @@ ); MARKETING_VERSION = 5.0.0; MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = com.refilc2.naplo.livecardpro; + PRODUCT_BUNDLE_IDENTIFIER = com.refilcrel.naplo.livecardpro; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; @@ -610,14 +632,14 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 250; - DEVELOPMENT_TEAM = 4DKAF249F3; + DEVELOPMENT_TEAM = UT7MSP4GWZ; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = livecard/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = livecard; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INFOPLIST_KEY_NSSupportsLiveActivities = YES; - IPHONEOS_DEPLOYMENT_TARGET = 16.1; + IPHONEOS_DEPLOYMENT_TARGET = 16.2; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -625,7 +647,7 @@ ); MARKETING_VERSION = 5.0.0; MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = com.refilc2.naplo.livecardpro; + PRODUCT_BUNDLE_IDENTIFIER = com.refilcrel.naplo.livecardpro; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; @@ -754,7 +776,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CURRENT_PROJECT_VERSION = 250; - DEVELOPMENT_TEAM = 4DKAF249F3; + DEVELOPMENT_TEAM = UT7MSP4GWZ; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = reFilc; @@ -764,7 +786,7 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 5.0.0; - PRODUCT_BUNDLE_IDENTIFIER = com.refilc2.naplo; + PRODUCT_BUNDLE_IDENTIFIER = com.refilcrel.naplo; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -782,7 +804,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CURRENT_PROJECT_VERSION = 250; - DEVELOPMENT_TEAM = 4DKAF249F3; + DEVELOPMENT_TEAM = UT7MSP4GWZ; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = reFilc; @@ -792,7 +814,7 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 5.0.0; - PRODUCT_BUNDLE_IDENTIFIER = com.refilc2.naplo; + PRODUCT_BUNDLE_IDENTIFIER = com.refilcrel.naplo; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; diff --git a/refilc/ios/Runner/AppDelegate.swift b/refilc/ios/Runner/AppDelegate.swift index 2c4c46e..a3d1b29 100644 --- a/refilc/ios/Runner/AppDelegate.swift +++ b/refilc/ios/Runner/AppDelegate.swift @@ -1,25 +1,113 @@ import UIKit +import background_fetch +import ActivityKit import Flutter @UIApplicationMain @objc class AppDelegate: FlutterAppDelegate { + private var methodChannel: FlutterMethodChannel? + override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { GeneratedPluginRegistrant.register(with: self) - - // here, Without this code the task will not work. - //SwiftFlutterForegroundTaskPlugin.setPluginRegistrantCallback(registerPlugins) - if #available(iOS 10.0, *) { - UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate + guard let controller = window?.rootViewController as? FlutterViewController else { + fatalError("rootViewController is not type FlutterViewController") } - + methodChannel = FlutterMethodChannel(name: "hu.refilc/liveactivity", + binaryMessenger: controller as! FlutterBinaryMessenger) + methodChannel?.setMethodCallHandler({ + [weak self] (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in + guard call.method == "createLiveActivity" || call.method == "endLiveActivity" || call.method == "updateLiveActivity" else { + result(FlutterMethodNotImplemented) + return + } + self?.handleMethodCall(call, result: result) + }) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } -} -// here -func registerPlugins(registry: FlutterPluginRegistry) { - GeneratedPluginRegistrant.register(with: registry) + override func applicationWillTerminate(_ application: UIApplication) { + if #available(iOS 16.2, *) { + LiveActivityManager.stop() + } + } + + private func handleMethodCall(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + if call.method == "createLiveActivity" { + if let args = call.arguments as? [String: Any] { + lessonDataDictionary = args + globalLessonData = LessonData(from: lessonDataDictionary) + print("swift: megkapott flutter adatok:",lessonDataDictionary) + print("Live Activity bekapcsolva az eszközön: ",checkLiveActivityFeatureAvailable()) + if(checkLiveActivityFeatureAvailable()) { + createLiveActivity(with: lessonDataDictionary) + result(checkLiveActivityFeatureAvailable()) + } else { + result(nil) + } + + } else { + result(FlutterError(code: "INVALID_ARGUMENTS", message: "Invalid iOS arguments received", details: nil)) + } + } else if call.method == "updateLiveActivity" { + if let args = call.arguments as? [String: Any] { + lessonDataDictionary = args + globalLessonData = LessonData(from: lessonDataDictionary) + updateLiveActivity(with: lessonDataDictionary) + result(nil) + } else { + result(FlutterError(code: "INVALID_ARGUMENTS", message: "Invalid iOS arguments received", details: nil)) + } + } else if call.method == "endLiveActivity" { + endLiveActivity() + result(nil) + } + } + + private func createLiveActivity(with activityData: [String: Any]) -> String? { + var lessonData = LessonData(from: activityData) + print("Live Activity létrehozása...") + if #available(iOS 16.2, *) { + LiveActivityManager.create() + } + return nil + } + + private func updateLiveActivity(with activityData: [String: Any]) { + let lessonData = LessonData(from: activityData) + print("swift: megkapott flutter adatok:",lessonDataDictionary) + print("Live Activity frissítés...") + if #available(iOS 16.2, *) { + LiveActivityManager.update() + } + } + + + private func endLiveActivity() { + print("Live Activity befejezése...") + if #available(iOS 16.2, *) { + LiveActivityManager.stop() + } + } + + private func checkIfLiveActivityExists() -> Bool { + if let activityID = activityID { + if #available(iOS 16.2, *) { + return LiveActivityManager.isRunning(activityID) + } + } + return false + } + + private func checkLiveActivityFeatureAvailable() -> Bool { + if #available(iOS 16.2, *) { + guard ActivityAuthorizationInfo().areActivitiesEnabled else { + return false + } + return true + } + return false + } } diff --git a/refilc/ios/Runner/Info.plist b/refilc/ios/Runner/Info.plist index e9ca19a..e32ae93 100644 --- a/refilc/ios/Runner/Info.plist +++ b/refilc/ios/Runner/Info.plist @@ -1,137 +1,138 @@ + + BGTaskSchedulerPermittedIdentifiers + + com.transistorsoft.refilcnotification + com.transistorsoft.refilcliveactivity + + CADisableMinimumFrameDurationOnPhone + + CFBundleAlternateIcons - BGTaskSchedulerPermittedIdentifiers - - com.transistorsoft.fetch - - CADisableMinimumFrameDurationOnPhone - - CFBundleAlternateIcons + refilc_concept - refilc_concept - - CFBundleIconFiles - - refilc_concept - - UIPrerenderedIcon - - - refilc_default - - CFBundleIconFiles - - refilc_default - - UIPrerenderedIcon - - - refilc_overcomplicated - - CFBundleIconFiles - - refilc_overcomplicated - - UIPrerenderedIcon - - - refilc_pride - - CFBundleIconFiles - - refilc_pride - - UIPrerenderedIcon - - + CFBundleIconFiles + + refilc_concept + + UIPrerenderedIcon + - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIcons + refilc_default - CFBundlePrimaryIcon - - CFBundleIconFiles - - - - CFBundleIconName - - UIPrerenderedIcon - - + CFBundleIconFiles + + refilc_default + + UIPrerenderedIcon + + + refilc_overcomplicated + + CFBundleIconFiles + + refilc_overcomplicated + + UIPrerenderedIcon + + + refilc_pride + + CFBundleIconFiles + + refilc_pride + + UIPrerenderedIcon + - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - reFilc - CFBundlePackageType - APPL - CFBundleShortVersionString - $(FLUTTER_BUILD_NAME) - CFBundleSignature - ???? - CFBundleURLTypes - - - CFBundleTypeRole - Editor - CFBundleURLSchemes - - refilcapp - - - - CFBundleVersion - $(FLUTTER_BUILD_NUMBER) - ITSAppUsesNonExemptEncryption - - LSApplicationQueriesSchemes - - https - http - - LSRequiresIPhoneOS - - NSCameraUsageDescription - The app requires the camera access to set a custom profile picture. - NSPhotoLibraryUsageDescription - The app requires the photo library to set a custom profile picture. - NSSupportsLiveActivities - - UIApplicationSupportsIndirectInputEvents - - UIBackgroundModes - - fetch - processing - - UILaunchStoryboardName - LaunchScreen - UIMainStoryboardFile - Main - UIStatusBarHidden - - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UIViewControllerBasedStatusBarAppearance - + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIcons + + CFBundlePrimaryIcon + + CFBundleIconFiles + + + + CFBundleIconName + + UIPrerenderedIcon + + + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + reFilc + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLSchemes + + refilcapp + + + + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + ITSAppUsesNonExemptEncryption + + LSApplicationQueriesSchemes + + https + http + + LSRequiresIPhoneOS + + NSCameraUsageDescription + The app requires the camera access to set a custom profile picture. + NSPhotoLibraryUsageDescription + The app requires the photo library to set a custom profile picture. + NSSupportsLiveActivities + + UIApplicationSupportsIndirectInputEvents + + UIBackgroundModes + + fetch + processing + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIStatusBarHidden + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + diff --git a/refilc/ios/Runner/public_vars.swift b/refilc/ios/Runner/public_vars.swift new file mode 100644 index 0000000..f084d22 --- /dev/null +++ b/refilc/ios/Runner/public_vars.swift @@ -0,0 +1,12 @@ +// +// public_vars.swift +// Runner +// +// Created by Geryy on 02/05/2024. +// + +import Foundation + +var lessonDataDictionary: [String: Any] = [:] +var globalLessonData = LessonData(from: lessonDataDictionary) +var activityID: String? = "" diff --git a/refilc/ios/livecard/LiveActivityManager.swift b/refilc/ios/livecard/LiveActivityManager.swift new file mode 100644 index 0000000..25f9eab --- /dev/null +++ b/refilc/ios/livecard/LiveActivityManager.swift @@ -0,0 +1,92 @@ +import ActivityKit +import WidgetKit +import Foundation + +public struct LiveActivitiesAppAttributes: ActivityAttributes, Identifiable { + public typealias LiveDeliveryData = ContentState + public struct ContentState: Codable, Hashable { + var color: String + var icon: String + var index: String + var title: String + var subtitle: String + var description: String + var startDate: Date + var endDate: Date + var date: ClosedRange + var nextSubject: String + var nextRoom: String + } + + public var id = UUID() +} + +@available(iOS 16.2, *) +final class LiveActivityManager { + static let shared = LiveActivityManager() + var currentActivity: Activity? + + class func create() { + + Task { + do { + let contentState = LiveActivitiesAppAttributes.ContentState(color: globalLessonData.color, icon: globalLessonData.icon, index: globalLessonData.index, title: globalLessonData.title, subtitle: globalLessonData.subtitle, description: globalLessonData.description, startDate: globalLessonData.startDate, endDate: globalLessonData.endDate, date: globalLessonData.date, nextSubject: globalLessonData.nextSubject, nextRoom: globalLessonData.nextRoom) + + let activityContent = ActivityContent(state: contentState, staleDate: globalLessonData.endDate, relevanceScore: 0) + + let activity = try Activity.request( + attributes: LiveActivitiesAppAttributes(), + content: activityContent, + pushType: nil + ) + + activityID = activity.id + print("Live Activity létrehozva. Azonosító: \(activity.id)") + } catch { + print("Hiba történt a Live Activity létrehozásakor: \(error)") + } + } + } + + class func update() { + Task { + for activity in Activity.activities { + do { + let contentState = LiveActivitiesAppAttributes.ContentState(color: globalLessonData.color, icon: globalLessonData.icon, index: globalLessonData.index, title: globalLessonData.title, subtitle: globalLessonData.subtitle, description: globalLessonData.description, startDate: globalLessonData.startDate, endDate: globalLessonData.endDate, date: globalLessonData.date, nextSubject: globalLessonData.nextSubject, nextRoom: globalLessonData.nextRoom) + + let activityContent = ActivityContent(state: contentState, staleDate: globalLessonData.endDate, relevanceScore: 0) + + await activity.update(activityContent) + activityID = activity.id + print("Live Activity frissítve. Azonosító: \(activity.id)") + } catch { + print("Hiba történt a Live Activity frissítésekor: \(error)") + } + } + } + } + + + class func stop() { + if (activityID != "") { + Task { + for activity in Activity.activities{ + let contentState = LiveActivitiesAppAttributes.ContentState(color: globalLessonData.color, icon: globalLessonData.icon, index: globalLessonData.index, title: globalLessonData.title, subtitle: globalLessonData.subtitle, description: globalLessonData.description, startDate: globalLessonData.startDate, endDate: globalLessonData.endDate, date: globalLessonData.date, nextSubject: globalLessonData.nextSubject, nextRoom: globalLessonData.nextRoom) + + await activity.end(ActivityContent(state: contentState, staleDate: Date.distantFuture),dismissalPolicy: .immediate) + } + activityID = nil + print("Live Activity sikeresen leállítva") + } + } + } + + class func isRunning(_ activityID: String) -> Bool { + for activity in Activity.activities { + if activity.id == activityID { + return true + } + } + return false + } +} diff --git a/refilc/ios/livecard/lesson_model.swift b/refilc/ios/livecard/lesson_model.swift index f098b63..f0d9904 100644 --- a/refilc/ios/livecard/lesson_model.swift +++ b/refilc/ios/livecard/lesson_model.swift @@ -1,31 +1,40 @@ import Foundation +import ActivityKit -class LessonData { - var color: String - var icon: String - var index: String - var title: String - var subtitle: String - var description: String - var startDate: Date - var endDate: Date - var date: ClosedRange - var nextSubject: String - var nextRoom: String - - init?() { - let sharedDefault = UserDefaults(suiteName: "group.refilc2.livecard")! - - self.color = sharedDefault.string(forKey: "color")! - self.icon = sharedDefault.string(forKey: "icon")! - self.index = sharedDefault.string(forKey: "index")! - self.title = sharedDefault.string(forKey: "title")! - self.subtitle = sharedDefault.string(forKey: "subtitle")! - self.description = sharedDefault.string(forKey: "description")! - self.startDate = Date(timeIntervalSince1970: Double(sharedDefault.string(forKey: "startDate")!)! / 1000) - self.endDate = Date(timeIntervalSince1970: Double(sharedDefault.string(forKey: "endDate")!)! / 1000) - date = self.startDate...self.endDate - self.nextSubject = sharedDefault.string(forKey: "nextSubject")! - self.nextRoom = sharedDefault.string(forKey: "nextRoom")! - } +public struct LessonData { + var color: String + var icon: String + var index: String + var title: String + var subtitle: String + var description: String + var startDate: Date + var endDate: Date + var date: ClosedRange + var nextSubject: String + var nextRoom: String + + init(from dictionary: [String: Any]) { + self.color = dictionary["color"] as? String ?? "" + self.icon = dictionary["icon"] as? String ?? "" + self.index = dictionary["index"] as? String ?? "" + self.title = dictionary["title"] as? String ?? "" + self.subtitle = dictionary["subtitle"] as? String ?? "" + self.description = dictionary["description"] as? String ?? "" + self.nextSubject = dictionary["nextSubject"] as? String ?? "" + self.nextRoom = dictionary["nextRoom"] as? String ?? "" + + if let startDateStr = dictionary["startDate"] as? String, let startDateInt = Int(startDateStr) { + self.startDate = Date(timeIntervalSince1970: TimeInterval(startDateInt) / 1000) + } else { + self.startDate = Date() + } + + if let endDateStr = dictionary["endDate"] as? String, let endDateInt = Int(endDateStr) { + self.endDate = Date(timeIntervalSince1970: TimeInterval(endDateInt) / 1000) + } else { + self.endDate = Date() + } + date = self.startDate...self.endDate + } } diff --git a/refilc/ios/livecard/livecard.swift b/refilc/ios/livecard/livecard.swift index 3781b3e..7b47058 100644 --- a/refilc/ios/livecard/livecard.swift +++ b/refilc/ios/livecard/livecard.swift @@ -5,8 +5,8 @@ import SwiftUI @main struct Widgets: WidgetBundle { var body: some Widget { - if #available(iOS 16.1, *) { - LiveCardWidget() + if #available(iOS 16.2, *) { + LiveCardWidget() } } } @@ -37,22 +37,11 @@ extension Color { } } - -// We need to redefined live activities pipe -struct LiveActivitiesAppAttributes: ActivityAttributes, Identifiable { - public struct ContentState: Codable, Hashable { } - - var id = UUID() -} - struct LockScreenLiveActivityView: View { let context: ActivityViewContext - - let lesson = LessonData() - var body: some View { HStack(alignment: .center) { - Image(systemName: lesson!.icon) + Image(systemName: context.state.icon) .resizable() .aspectRatio(contentMode: .fit) .frame(width: CGFloat(30), height: CGFloat(30)) @@ -60,17 +49,17 @@ struct LockScreenLiveActivityView: View { VStack(alignment: .leading) { HStack(alignment: .center) { - Text(lesson!.index + lesson!.title) + Text(context.state.index + context.state.title) .font(.title3) .bold() - Text(lesson!.subtitle) + Text(context.state.subtitle) .font(.subheadline) .padding(.trailing, 12) } - if (lesson!.description != "") { - Text(lesson!.description) + if (context.state.description != "") { + Text(context.state.description) .font(.subheadline) } @@ -79,16 +68,16 @@ struct LockScreenLiveActivityView: View { .resizable() .aspectRatio(contentMode: .fit) .frame(width: CGFloat(8), height: CGFloat(8)) - Text(lesson!.nextSubject) + Text(context.state.nextSubject) .font(.caption) - Text(lesson!.nextRoom) + Text(context.state.nextRoom) .font(.caption2) } }.padding(15) Spacer() - Text(timerInterval: lesson!.date, countsDown: true) + Text(timerInterval: context.state.date, countsDown: true) .multilineTextAlignment(.center) .frame(width: 85) .font(.title2) @@ -96,110 +85,105 @@ struct LockScreenLiveActivityView: View { .padding(.trailing, CGFloat(24)) } .activityBackgroundTint( - lesson!.color != "#676767" - ? Color(hex: lesson!.color) + context.state.color != "#676767" + ? Color(hex: context.state.color) // Ha nem megy hat nem megy : Color.clear ) } } -@available(iOSApplicationExtension 16.1, *) +@available(iOSApplicationExtension 16.2, *) struct LiveCardWidget: Widget { - var body: some WidgetConfiguration { - /// Live Activity Notification - ActivityConfiguration(for: LiveActivitiesAppAttributes.self) { context in - LockScreenLiveActivityView(context: context) - /// Dynamic Island - } dynamicIsland: { context in - let lesson = LessonData() - - /// Expanded - return DynamicIsland { - DynamicIslandExpandedRegion(.leading) { - VStack { - Spacer() - ProgressView( - timerInterval: lesson!.date, - countsDown: true, - label: { - Image(systemName: lesson!.icon) - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: CGFloat(32), height: CGFloat(32)) - }, - currentValueLabel: { - Image(systemName: lesson!.icon) - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: CGFloat(32), height: CGFloat(32)) - } - ).progressViewStyle(.circular) - } - } - DynamicIslandExpandedRegion(.center) { - VStack(alignment: .leading) { - Text(lesson!.index + lesson!.title) - .lineLimit(1) - .font(.title3) - .bold() - - Text(lesson!.description) - .lineLimit(2) - .font(.caption) - }.padding(EdgeInsets(top: 0.0, leading: 5.0, bottom: 0.0, trailing: 0.0)) - } - DynamicIslandExpandedRegion(.trailing) { - VStack { - Spacer() - Text(lesson!.subtitle) - .lineLimit(1) - .font(.subheadline) - Spacer() - } - } - - /// Compact - } compactLeading: { - Label { - Text(lesson!.title) - } icon: { - Image(systemName: lesson!.icon) - } - .font(.caption2) - } - compactTrailing: { - Text(timerInterval: lesson!.date, countsDown: true) - .multilineTextAlignment(.center) - .frame(width: 40) - .font(.caption2) - - /// Collapsed - } minimal: { - VStack(alignment: .center, content: { - ProgressView( - timerInterval: lesson!.date, - countsDown: true, - label: { - Image(systemName: lesson!.icon) - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: CGFloat(12), height: CGFloat(12)) - }, - currentValueLabel: { - Image(systemName: lesson!.icon) - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: CGFloat(12), height: CGFloat(12)) + var body: some WidgetConfiguration { + /// Live Activity Notification + ActivityConfiguration(for: LiveActivitiesAppAttributes.self) { context in + LockScreenLiveActivityView(context: context) + /// Dynamic Island + } dynamicIsland: { context in + + /// Expanded + return DynamicIsland { + DynamicIslandExpandedRegion(.leading) { + VStack { + Spacer() + ProgressView( + timerInterval: context.state.date, + countsDown: true, + label: { + Image(systemName: context.state.icon) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: CGFloat(32), height: CGFloat(32)) + }, + currentValueLabel: { + Image(systemName: context.state.icon) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: CGFloat(32), height: CGFloat(32)) + } + ).progressViewStyle(.circular) + } + } + DynamicIslandExpandedRegion(.center) { + VStack(alignment: .center) { + Text(context.state.index + context.state.title) + .lineLimit(1) + .font(.body) + .bold() + + Text(context.state.subtitle) + .lineLimit(1) + .font(.subheadline) + Spacer() + + Text(context.state.description) + .lineLimit(2) + .font(.caption) + }.padding(EdgeInsets(top: 0.0, leading: 5.0, bottom: 0.0, trailing: 0.0)) + } + + /// Compact + } compactLeading: { + Label { + Text(context.state.title) + } icon: { + Image(systemName: context.state.icon) + } + .font(.caption2) } - ).progressViewStyle(.circular) - }) - } - .keylineTint( - lesson!.color != "#676767" - ? Color(hex: lesson!.color) - : Color.clear - ) + compactTrailing: { + Text(timerInterval: context.state.date, countsDown: true) + .multilineTextAlignment(.center) + .frame(width: 40) + .font(.caption2) + + /// Collapsed + } minimal: { + VStack(alignment: .center, content: { + ProgressView( + timerInterval: context.state.date, + countsDown: true, + label: { + Image(systemName: context.state.icon) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: CGFloat(12), height: CGFloat(12)) + }, + currentValueLabel: { + Image(systemName: context.state.icon) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: CGFloat(12), height: CGFloat(12)) + } + ).progressViewStyle(.circular) + }) + } + .keylineTint( + context.state.color != "#676767" + ? Color(hex: context.state.color) + : Color.clear + ) + } } - } } diff --git a/refilc/lib/api/providers/live_card_provider.dart b/refilc/lib/api/providers/live_card_provider.dart index 05197fb..7ace094 100644 --- a/refilc/lib/api/providers/live_card_provider.dart +++ b/refilc/lib/api/providers/live_card_provider.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'dart:io'; +import 'package:refilc/api/providers/liveactivity/platform_channel.dart'; import 'package:refilc/helpers/subject.dart'; import 'package:refilc/models/settings.dart'; import 'package:refilc_kreta_api/models/lesson.dart'; @@ -10,7 +11,6 @@ import 'package:refilc_kreta_api/models/week.dart'; import 'package:refilc/utils/format.dart'; import 'package:refilc_kreta_api/providers/timetable_provider.dart'; import 'package:flutter/foundation.dart'; -import 'package:live_activities/live_activities.dart'; import 'package:refilc_mobile_ui/pages/home/live_card/live_card.i18n.dart'; enum LiveCardState { @@ -29,6 +29,15 @@ class LiveCardProvider extends ChangeNotifier { Lesson? prevLesson; List? nextLessons; + // new variables + static bool hasActivityStarted = false; + static bool hasDayEnd = false; + static DateTime? storeFirstRunDate; + static bool hasActivitySettingsChanged = false; + static Map LAData = {}; + static DateTime? now; + // + LiveCardState currentState = LiveCardState.empty; late Timer _timer; late final TimetableProvider _timetable; @@ -36,9 +45,6 @@ class LiveCardProvider extends ChangeNotifier { late Duration _delay; - final _liveActivitiesPlugin = LiveActivities(); - String? _latestActivityId; - Map _lastActivity = {}; bool _hasCheckedTimetable = false; @@ -47,23 +53,6 @@ class LiveCardProvider extends ChangeNotifier { required SettingsProvider settings, }) : _timetable = timetable, _settings = settings { - if (Platform.isIOS) { - _liveActivitiesPlugin.areActivitiesEnabled().then((value) { - // Console log - if (kDebugMode) { - print("iOS LiveActivity enabled: $value"); - } - - if (value) { - _liveActivitiesPlugin.init(appGroupId: "group.refilc2.livecard"); - - _liveActivitiesPlugin.getAllActivitiesIds().then((value) { - _latestActivityId = value.isNotEmpty ? value.first : null; - }); - } - }); - } - _timer = Timer.periodic(const Duration(seconds: 1), (timer) => update()); _delay = settings.bellDelayEnabled ? Duration(seconds: settings.bellDelay) @@ -71,21 +60,6 @@ class LiveCardProvider extends ChangeNotifier { update(); } - @override - void dispose() { - _timer.cancel(); - if (Platform.isIOS) { - _liveActivitiesPlugin.areActivitiesEnabled().then((value) { - if (value) { - if (_latestActivityId != null) { - _liveActivitiesPlugin.endActivity(_latestActivityId!); - } - } - }); - } - super.dispose(); - } - // Debugging static DateTime _now() { // return DateTime(2023, 9, 27, 9, 30); @@ -110,31 +84,88 @@ class LiveCardProvider extends ChangeNotifier { Map toMap() { switch (currentState) { + case LiveCardState.morning: + return { + "color": + '#${_settings.liveActivityColor.toString().substring(10, 16)}', + "icon": nextLesson != null + ? SubjectIcon.resolveName(subject: nextLesson?.subject) + : "book", + "title": "Első órádig:", + "subtitle": "", + "description": "", + "startDate": storeFirstRunDate != null ? ((storeFirstRunDate?.millisecondsSinceEpoch ?? 0) - (_delay.inMilliseconds)).toString(): "", + "endDate": ((nextLesson?.start.millisecondsSinceEpoch ?? 0) - + _delay.inMilliseconds) + .toString(), + "nextSubject": nextLesson != null + ? nextLesson?.subject.renamedTo ?? ShortSubject.resolve(subject: nextLesson?.subject).capital() + : "", + "nextRoom": nextLesson?.room.replaceAll("_", " ") ?? "", + }; + + case LiveCardState.afternoon: + return { + "color": + '#${_settings.liveActivityColor.toString().substring(10, 16)}', + "icon": nextLesson != null + ? SubjectIcon.resolveName(subject: nextLesson?.subject) + : "book", + "title": "Első órádig:", + "subtitle": "", + "description": "", + "startDate": storeFirstRunDate != null ? ((storeFirstRunDate?.millisecondsSinceEpoch ?? 0) - (_delay.inMilliseconds)).toString(): "", + "endDate": ((nextLesson?.start.millisecondsSinceEpoch ?? 0) - + _delay.inMilliseconds) + .toString(), + "nextSubject": nextLesson != null + ? nextLesson?.subject.renamedTo ?? ShortSubject.resolve(subject: nextLesson?.subject).capital() + : "", + "nextRoom": nextLesson?.room.replaceAll("_", " ") ?? "", + }; + + case LiveCardState.night: + return { + "color": + '#${_settings.liveActivityColor.toString().substring(10, 16)}', + "icon": nextLesson != null + ? SubjectIcon.resolveName(subject: nextLesson?.subject) + : "book", + "title": "Első órádig:", + "subtitle": "", + "description": "", + "startDate": storeFirstRunDate != null ? ((storeFirstRunDate?.millisecondsSinceEpoch ?? 0) - (_delay.inMilliseconds)).toString(): "", + "endDate": ((nextLesson?.start.millisecondsSinceEpoch ?? 0) - + _delay.inMilliseconds) + .toString(), + "nextSubject": nextLesson != null + ? nextLesson?.subject.renamedTo ?? ShortSubject.resolve(subject: nextLesson?.subject).capital() + : "", + "nextRoom": nextLesson?.room.replaceAll("_", " ") ?? "", + }; + case LiveCardState.duringLesson: return { "color": - '#${_settings.liveActivityColor.toString().substring(10, 16)}', + '#${_settings.liveActivityColor.toString().substring(10, 16)}', "icon": currentLesson != null ? SubjectIcon.resolveName(subject: currentLesson?.subject) : "book", "index": - currentLesson != null ? '${currentLesson!.lessonIndex}. ' : "", + currentLesson != null ? '${currentLesson!.lessonIndex}. ' : "", "title": currentLesson != null - ? currentLesson?.subject.renamedTo ?? - ShortSubject.resolve(subject: currentLesson?.subject) - .capital() + ? currentLesson?.subject.renamedTo ?? ShortSubject.resolve(subject: currentLesson?.subject).capital() : "", "subtitle": currentLesson?.room.replaceAll("_", " ") ?? "", "description": currentLesson?.description ?? "", "startDate": ((currentLesson?.start.millisecondsSinceEpoch ?? 0) - - _delay.inMilliseconds) + _delay.inMilliseconds) .toString(), "endDate": ((currentLesson?.end.millisecondsSinceEpoch ?? 0) - - _delay.inMilliseconds) + _delay.inMilliseconds) .toString(), "nextSubject": nextLesson != null - ? nextLesson?.subject.renamedTo ?? - ShortSubject.resolve(subject: nextLesson?.subject).capital() + ? nextLesson?.subject.renamedTo ?? ShortSubject.resolve(subject: nextLesson?.subject).capital() : "", "nextRoom": nextLesson?.room.replaceAll("_", " ") ?? "", }; @@ -150,23 +181,21 @@ class LiveCardProvider extends ChangeNotifier { return { "color": - '#${_settings.liveActivityColor.toString().substring(10, 16)}', + '#${_settings.liveActivityColor.toString().substring(10, 16)}', "icon": iconFloorMap[diff] ?? "cup.and.saucer", "title": "Szünet", "description": "go $diff".i18n.fill([ diff != "to room" ? (nextLesson!.getFloor() ?? 0) : nextLesson!.room ]), "startDate": ((prevLesson?.end.millisecondsSinceEpoch ?? 0) - - _delay.inMilliseconds) + _delay.inMilliseconds) .toString(), "endDate": ((nextLesson?.start.millisecondsSinceEpoch ?? 0) - - _delay.inMilliseconds) + _delay.inMilliseconds) .toString(), "nextSubject": (nextLesson != null - ? nextLesson?.subject.renamedTo ?? - ShortSubject.resolve(subject: nextLesson?.subject) - .capital() - : "") + ? nextLesson?.subject.renamedTo ?? ShortSubject.resolve(subject: nextLesson?.subject).capital() + : "") .capital(), "nextRoom": nextLesson?.room.replaceAll("_", " ") ?? "", "index": "", @@ -178,37 +207,6 @@ class LiveCardProvider extends ChangeNotifier { } void update() async { - if (Platform.isIOS) { - _liveActivitiesPlugin.areActivitiesEnabled().then((value) { - if (value) { - final cmap = toMap(); - if (!mapEquals(cmap, _lastActivity)) { - _lastActivity = cmap; - try { - if (_lastActivity.isNotEmpty) { - if (_latestActivityId == null) { - _liveActivitiesPlugin - .createActivity(_lastActivity) - .then((value) => _latestActivityId = value); - } else { - _liveActivitiesPlugin.updateActivity( - _latestActivityId!, _lastActivity); - } - } else { - if (_latestActivityId != null) { - _liveActivitiesPlugin.endActivity(_latestActivityId!); - } - } - } catch (e) { - if (kDebugMode) { - print('ERROR: Unable to create or update iOS LiveActivity!'); - } - } - } - } - }); - } - List today = _today(_timetable); if (today.isEmpty && !_hasCheckedTimetable) { @@ -221,15 +219,22 @@ class LiveCardProvider extends ChangeNotifier { ? Duration(seconds: _settings.bellDelay) : Duration.zero; - final now = _now().add(_delay); + + DateTime now = _now().add(_delay); + + if ((currentState == LiveCardState.morning || + currentState == LiveCardState.afternoon || + currentState == LiveCardState.night) && storeFirstRunDate == null) { + storeFirstRunDate = now; + } // Filter cancelled lessons #20 // Filter label lessons #128 today = today .where((lesson) => - lesson.status?.name != "Elmaradt" && - lesson.subject.id != '' && - !lesson.isEmpty) + lesson.status?.name != "Elmaradt" && + lesson.subject.id != '' && + !lesson.isEmpty) .toList(); if (today.isNotEmpty) { @@ -237,7 +242,7 @@ class LiveCardProvider extends ChangeNotifier { today.sort((a, b) => a.start.compareTo(b.start)); final _lesson = today.firstWhere( - (l) => l.start.isBefore(now) && l.end.isAfter(now), + (l) => l.start.isBefore(now) && l.end.isAfter(now), orElse: () => Lesson.fromJson({})); if (_lesson.start.year != 0) { @@ -283,11 +288,65 @@ class LiveCardProvider extends ChangeNotifier { currentState = LiveCardState.empty; } + //LIVE ACTIVITIES + + //CREATE + if (!hasActivityStarted && nextLesson != null && nextLesson! + .start + .difference(now) + .inMinutes <= 60 && (currentState == LiveCardState.morning || + currentState == LiveCardState.afternoon || + currentState == LiveCardState.night)) { + debugPrint( + "Az első óra előtt állunk, kevesebb mint egy órával. Létrehozás..."); + PlatformChannel.createLiveActivity(toMap()); + hasActivityStarted = true; + } + else if (!hasActivityStarted && ((currentState == LiveCardState.duringLesson && + currentLesson != null) || + currentState == LiveCardState.duringBreak)) { + debugPrint( + "Óra van, vagy szünet, de nincs LiveActivity. létrehozás..."); + PlatformChannel.createLiveActivity(toMap()); + hasActivityStarted = true; + } + + //UPDATE + else if (hasActivityStarted) { + if (hasActivitySettingsChanged) { + debugPrint("Valamelyik beállítás megváltozott. Frissítés..."); + PlatformChannel.updateLiveActivity(toMap()); + hasActivitySettingsChanged = false; + } + else if (nextLesson != null || currentLesson != null) { + bool afterPrevLessonEnd = prevLesson != null && + now.subtract(const Duration(seconds: 1)).isBefore( + prevLesson!.end) && now.isAfter(prevLesson!.end); + + bool afterCurrentLessonStart = currentLesson != null && + now.subtract(const Duration(seconds: 1)).isBefore( + currentLesson!.start) && now.isAfter(currentLesson!.start); + if (afterPrevLessonEnd || afterCurrentLessonStart) { + debugPrint( + "Óra kezdete/vége után 1 másodperccel vagyunk. Frissítés..."); + PlatformChannel.updateLiveActivity(toMap()); + } + } + } + + //END + if (hasActivityStarted && !hasDayEnd && nextLesson == null && + now.isAfter(prevLesson!.end)) { + debugPrint("Az utolsó óra véget ért. Befejezés..."); + PlatformChannel.endLiveActivity(); + hasDayEnd = true; + hasActivityStarted = false; + } + LAData = toMap(); notifyListeners(); } bool get show => currentState != LiveCardState.empty; - Duration get delay => _delay; bool _sameDate(DateTime a, DateTime b) => @@ -296,4 +355,4 @@ class LiveCardProvider extends ChangeNotifier { List _today(TimetableProvider p) => (p.getWeek(Week.current()) ?? []) .where((l) => _sameDate(l.date, _now())) .toList(); -} +} \ No newline at end of file diff --git a/refilc/lib/api/providers/liveactivity/platform_channel.dart b/refilc/lib/api/providers/liveactivity/platform_channel.dart new file mode 100644 index 0000000..8b782da --- /dev/null +++ b/refilc/lib/api/providers/liveactivity/platform_channel.dart @@ -0,0 +1,43 @@ +import 'dart:async'; +import 'dart:io'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/services.dart'; + +class PlatformChannel { + static const MethodChannel _channel = MethodChannel('hu.refilc/liveactivity'); + + static Future createLiveActivity( + Map activityData) async { + if (Platform.isIOS) { + try { + debugPrint("creating..."); + await _channel.invokeMethod('createLiveActivity', activityData); + } on PlatformException catch (e) { + debugPrint("Hiba történt a Live Activity létrehozásakor: ${e.message}"); + } + } + } + + static Future updateLiveActivity( + Map activityData) async { + if (Platform.isIOS) { + try { + debugPrint("updating..."); + await _channel.invokeMethod('updateLiveActivity', activityData); + } on PlatformException catch (e) { + debugPrint("Hiba történt a Live Activity frissítésekor: ${e.message}"); + } + } + } + + static Future endLiveActivity() async { + if (Platform.isIOS) { + try { + debugPrint("finishing..."); + await _channel.invokeMethod('endLiveActivity'); + } on PlatformException catch (e) { + debugPrint("Hiba történt a Live Activity befejezésekor: ${e.message}"); + } + } + } +} \ No newline at end of file diff --git a/refilc/lib/api/providers/sync.dart b/refilc/lib/api/providers/sync.dart index 52a69ae..d6be0b1 100644 --- a/refilc/lib/api/providers/sync.dart +++ b/refilc/lib/api/providers/sync.dart @@ -22,6 +22,9 @@ import 'package:flutter/widgets.dart'; import 'package:provider/provider.dart'; import 'package:home_widget/home_widget.dart'; +import 'live_card_provider.dart'; +import 'liveactivity/platform_channel.dart'; + // Mutex bool lock = false; @@ -86,10 +89,17 @@ Future syncAll(BuildContext context) { return false; } + + return Future.wait(tasks).then((value) { // Unlock lock = false; + if(Platform.isIOS && LiveCardProvider.hasActivityStarted == true){ + PlatformChannel.endLiveActivity(); + LiveCardProvider.hasActivityStarted = false; + } + // Update Widget if (Platform.isAndroid) updateWidget(); }); diff --git a/refilc/lib/helpers/live_activity_helper.dart b/refilc/lib/helpers/live_activity_helper.dart new file mode 100644 index 0000000..69a26bc --- /dev/null +++ b/refilc/lib/helpers/live_activity_helper.dart @@ -0,0 +1,15 @@ +import 'package:refilc/api/providers/live_card_provider.dart'; +import '../api/providers/liveactivity/platform_channel.dart'; + + +class LiveActivityHelper { + @pragma('vm:entry-point') + void backgroundJob() async { + // initialize provider + if (!LiveCardProvider.hasDayEnd) { + await PlatformChannel.updateLiveActivity(LiveCardProvider.LAData); + } else { + await PlatformChannel.endLiveActivity(); + } + } +} \ No newline at end of file diff --git a/refilc/lib/main.dart b/refilc/lib/main.dart index 818a38b..7b58d0f 100644 --- a/refilc/lib/main.dart +++ b/refilc/lib/main.dart @@ -16,6 +16,8 @@ import 'package:refilc_mobile_ui/screens/error_screen.dart'; import 'package:refilc_mobile_ui/screens/error_report_screen.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; +import 'helpers/live_activity_helper.dart'; + // days without touching grass: 5,843 (16 yrs) void main() async { @@ -84,6 +86,7 @@ class Startup { // Notifications setup if (!kIsWeb) { initPlatformState(); + initAdditionalBackgroundFetch(); flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); } @@ -196,7 +199,12 @@ Future initPlatformState() async { if (kDebugMode) { print("[BackgroundFetch] Event received $taskId"); } - NotificationsHelper().backgroundJob(); + if (taskId == "com.transistorsoft.refilcliveactivity") { + if (!Platform.isIOS) return; + LiveActivityHelper().backgroundJob(); + } else { + NotificationsHelper().backgroundJob(); + } BackgroundFetch.finish(taskId); }, (String taskId) async { // <-- Task timeout handler. @@ -231,6 +239,50 @@ void backgroundHeadlessTask(HeadlessTask task) { if (kDebugMode) { print('[BackgroundFetch] Headless event received.'); } - NotificationsHelper().backgroundJob(); - BackgroundFetch.finish(task.taskId); + if (taskId == "com.transistorsoft.refilcliveactivity") { + if (!Platform.isIOS) return; + LiveActivityHelper().backgroundJob(); + } else { + NotificationsHelper().backgroundJob(); + } BackgroundFetch.finish(task.taskId); +} + +Future initAdditionalBackgroundFetch() async { + int status = await BackgroundFetch.configure( + BackgroundFetchConfig( + minimumFetchInterval: 1, // 1 minute + stopOnTerminate: false, + enableHeadless: true, + requiresBatteryNotLow: false, + requiresCharging: false, + requiresStorageNotLow: false, + requiresDeviceIdle: false, + requiredNetworkType: NetworkType.ANY, + startOnBoot: true), (String taskId) async { + // <-- Event handler + + if (kDebugMode) { + print("[BackgroundFetch] Event received $taskId"); + } + LiveActivityHelper liveActivityHelper = LiveActivityHelper(); + liveActivityHelper.backgroundJob(); + + BackgroundFetch.finish(taskId); + }, (String taskId) async { + // <-- Task timeout handler. + if (kDebugMode) { + print("[BackgroundFetch] TASK TIMEOUT taskId: $taskId"); + } + BackgroundFetch.finish(taskId); + }); + if (kDebugMode) { + print('[BackgroundFetch] configure success: $status'); + } + BackgroundFetch.scheduleTask(TaskConfig( + taskId: "com.transistorsoft.refilcliveactivity", + delay: 300000, // 5 minute + periodic: true, + forceAlarmManager: true, + stopOnTerminate: false, + enableHeadless: true)); } diff --git a/refilc/lib/models/settings.dart b/refilc/lib/models/settings.dart index 04a8ade..bb1433a 100644 --- a/refilc/lib/models/settings.dart +++ b/refilc/lib/models/settings.dart @@ -1,5 +1,6 @@ import 'dart:convert'; import 'dart:developer'; +import 'dart:io'; import 'package:flutter/services.dart'; import 'package:refilc/api/providers/database_provider.dart'; @@ -10,6 +11,8 @@ import 'package:refilc/theme/colors/dark_mobile.dart'; import 'package:flutter/material.dart'; import 'package:uuid/uuid.dart'; +import '../api/providers/live_card_provider.dart'; + enum Pages { home, grades, timetable, notes, absences } enum UpdateChannel { stable, beta, dev } @@ -666,6 +669,9 @@ class SettingsProvider extends ChangeNotifier { if (bellDelay != null && bellDelay != _bellDelay) _bellDelay = bellDelay; if (bellDelayEnabled != null && bellDelayEnabled != _bellDelayEnabled) { _bellDelayEnabled = bellDelayEnabled; + if(Platform.isIOS){ + LiveCardProvider.hasActivitySettingsChanged = true; + } } if (gradeOpeningFun != null && gradeOpeningFun != _gradeOpeningFun) { _gradeOpeningFun = gradeOpeningFun; diff --git a/refilc_mobile_ui/lib/screens/settings/settings_helper.dart b/refilc_mobile_ui/lib/screens/settings/settings_helper.dart index 83c0e6a..e566eea 100644 --- a/refilc_mobile_ui/lib/screens/settings/settings_helper.dart +++ b/refilc_mobile_ui/lib/screens/settings/settings_helper.dart @@ -4,6 +4,7 @@ import 'dart:io'; import 'package:flutter_svg/svg.dart'; import 'package:refilc/api/providers/database_provider.dart'; +import 'package:refilc/api/providers/live_card_provider.dart'; import 'package:refilc/api/providers/user_provider.dart'; import 'package:refilc/helpers/quick_actions.dart'; import 'package:refilc/models/settings.dart'; @@ -750,6 +751,9 @@ class _BellDelaySettingState extends State Provider.of(context, listen: false) .update(bellDelay: currentDelay.inSeconds); _tabController.index = currentDelay.inSeconds > 0 ? 1 : 0; + if(Platform.isIOS){ + LiveCardProvider.hasActivitySettingsChanged = true; + } setState(() {}); } }, @@ -760,6 +764,9 @@ class _BellDelaySettingState extends State //Provider.of(context, listen: false).update(context, rounding: (r * 10).toInt()); Provider.of(context, listen: false) .update(bellDelay: currentDelay.inSeconds); + if(Platform.isIOS){ + LiveCardProvider.hasActivitySettingsChanged = true; + } Navigator.of(context).maybePop(); }, ), @@ -897,6 +904,7 @@ class _LiveActivityColorSettingState extends State { currentColor = k as Color; settings.update( liveActivityColor: currentColor.withAlpha(255)); + LiveCardProvider.hasActivitySettingsChanged = true; Navigator.of(context).maybePop(); }); }, @@ -913,6 +921,7 @@ class _LiveActivityColorSettingState extends State { var defaultColors = SettingsProvider.defaultSettings().liveActivityColor; settings.update(liveActivityColor: defaultColors); + LiveCardProvider.hasActivitySettingsChanged = true; Navigator.of(context).maybePop(); }, child: Text(SettingsLocalization("reset").i18n), From d5f583a0d2c0dd05e6016d2ec3ad8c8cb2a24e17 Mon Sep 17 00:00:00 2001 From: Kima Date: Fri, 3 May 2024 16:51:43 +0200 Subject: [PATCH 02/13] visual bug fixes --- refilc/lib/ui/widgets/lesson/lesson_tile.dart | 5 +- .../lib/pages/grades/grade_subject_view.dart | 3 +- .../lib/pages/home/home_page.dart | 15 ++++-- .../lib/pages/home/live_card/live_card.dart | 53 ++++++++++--------- 4 files changed, 44 insertions(+), 32 deletions(-) diff --git a/refilc/lib/ui/widgets/lesson/lesson_tile.dart b/refilc/lib/ui/widgets/lesson/lesson_tile.dart index 43a494b..5dbbdaf 100644 --- a/refilc/lib/ui/widgets/lesson/lesson_tile.dart +++ b/refilc/lib/ui/widgets/lesson/lesson_tile.dart @@ -27,7 +27,7 @@ class LessonTile extends StatelessWidget { this.currentLessonIndicator = true, this.padding, this.contentPadding, - this.showSubTiles = false, + this.showSubTiles = true, }); final Lesson lesson; @@ -151,7 +151,8 @@ class LessonTile extends StatelessWidget { child: PanelTitle(title: Text(lesson.name)), ), child: Padding( - padding: EdgeInsets.only(bottom: subtiles.isEmpty ? 0.0 : 12.0), + padding: EdgeInsets.only( + bottom: (subtiles.isNotEmpty && showSubTiles) ? 12.0 : 0.0), child: Column( mainAxisSize: MainAxisSize.min, children: [ diff --git a/refilc_mobile_ui/lib/pages/grades/grade_subject_view.dart b/refilc_mobile_ui/lib/pages/grades/grade_subject_view.dart index 0bb1df2..bc321e0 100644 --- a/refilc_mobile_ui/lib/pages/grades/grade_subject_view.dart +++ b/refilc_mobile_ui/lib/pages/grades/grade_subject_view.dart @@ -19,6 +19,7 @@ import 'package:refilc_kreta_api/models/grade.dart'; import 'package:refilc_kreta_api/models/subject.dart'; import 'package:refilc_mobile_ui/common/average_display.dart'; import 'package:refilc_mobile_ui/common/bottom_sheet_menu/rounded_bottom_sheet.dart'; +import 'package:refilc_mobile_ui/common/empty.dart'; import 'package:refilc_mobile_ui/common/filter_bar.dart'; import 'package:refilc_mobile_ui/common/panel/panel.dart'; import 'package:refilc_mobile_ui/common/splitted_panel/splitted_panel.dart'; @@ -281,7 +282,7 @@ class _GradeSubjectViewState extends State title: Text("exams".i18n), children: _tiles, )) - : const SizedBox(), + : const Empty(), ), ); diff --git a/refilc_mobile_ui/lib/pages/home/home_page.dart b/refilc_mobile_ui/lib/pages/home/home_page.dart index 449e521..8939275 100644 --- a/refilc_mobile_ui/lib/pages/home/home_page.dart +++ b/refilc_mobile_ui/lib/pages/home/home_page.dart @@ -214,7 +214,7 @@ class HomePageState extends State with TickerProviderStateMixin { // TODO: REMOVE IN PRODUCTION BUILD!!! // print(_liveCard.currentState); - // _liveCard.currentState = LiveCardState.duringBreak; + _liveCard.currentState = LiveCardState.duringLesson; return Scaffold( body: Stack( @@ -330,7 +330,7 @@ class HomePageState extends State with TickerProviderStateMixin { LiveCardState.duringLesson || _liveCard.currentState == LiveCardState.duringBreak) - ? 288.0 + ? 292.0 : 238.0)), // Live Card @@ -348,10 +348,15 @@ class HomePageState extends State with TickerProviderStateMixin { ? 0.0 : 62.0) + MediaQuery.of(context).padding.top, - bottom: _liveCard.currentState == - LiveCardState.morning + bottom: (_liveCard.currentState == + LiveCardState.morning) ? 44.0 - : 52.0, + : ((_liveCard.currentState == + LiveCardState.duringLesson || + _liveCard.currentState == + LiveCardState.duringBreak) + ? 55.0 + : 52.0), ), child: Transform.scale( scale: _liveCardAnimation.value, diff --git a/refilc_mobile_ui/lib/pages/home/live_card/live_card.dart b/refilc_mobile_ui/lib/pages/home/live_card/live_card.dart index d6dbcf5..e693e73 100644 --- a/refilc_mobile_ui/lib/pages/home/live_card/live_card.dart +++ b/refilc_mobile_ui/lib/pages/home/live_card/live_card.dart @@ -6,6 +6,10 @@ import 'package:refilc/helpers/subject.dart'; import 'package:refilc/models/settings.dart'; import 'package:refilc/theme/colors/colors.dart'; import 'package:refilc/ui/widgets/lesson/lesson_tile.dart'; +import 'package:refilc_kreta_api/models/category.dart'; +import 'package:refilc_kreta_api/models/lesson.dart'; +import 'package:refilc_kreta_api/models/subject.dart'; +import 'package:refilc_kreta_api/models/teacher.dart'; import 'package:refilc_mobile_ui/common/panel/panel.dart'; import 'package:refilc_mobile_ui/common/progress_bar.dart'; import 'package:refilc_mobile_ui/common/round_border_icon.dart'; @@ -67,30 +71,30 @@ class LiveCardStateA extends State { // test // TODO: REMOVE IN PRODUCTION BUILD!!! - // liveCard.currentState = LiveCardState.duringBreak; - // liveCard.nextLesson = Lesson( - // date: DateTime.now().add(Duration( - // minutes: 30, - // )), - // subject: GradeSubject( - // category: Category(id: 'asd'), id: 'asd', name: 'Matematika'), - // lessonIndex: '1', - // teacher: Teacher(id: 'id', name: 'name'), - // start: DateTime.now().subtract(Duration( - // minutes: 30, - // )), - // end: DateTime.now().add(Duration( - // minutes: 15, - // )), - // homeworkId: 'homeworkId', - // id: 'id', - // description: 'description', - // room: 'ABC69', - // groupName: 'groupName', - // name: 'name', - // ); + liveCard.currentState = LiveCardState.duringLesson; + liveCard.currentLesson = Lesson( + date: DateTime.now().add(Duration( + minutes: 30, + )), + subject: GradeSubject( + category: Category(id: 'asd'), id: 'asd', name: 'Matematika'), + lessonIndex: '1', + teacher: Teacher(id: 'id', name: 'name'), + start: DateTime.now().subtract(Duration( + minutes: 30, + )), + end: DateTime.now().add(Duration( + minutes: 15, + )), + homeworkId: 'homeworkId', + id: 'id', + description: 'description', + room: 'ABC69', + groupName: 'groupName', + name: 'name', + ); - // liveCard.prevLesson = liveCard.nextLesson; + liveCard.nextLesson = liveCard.currentLesson; // final dt = DateTime(2024, 3, 22, 17, 12, 1, 1, 1); @@ -408,8 +412,9 @@ class LiveCardStateA extends State { swapRoom: true, currentLessonIndicator: false, padding: - const EdgeInsets.only(top: 8.0, bottom: 4.0), + const EdgeInsets.only(top: 6.0, bottom: 4.0), contentPadding: EdgeInsets.zero, + showSubTiles: false, ), if (!(nextSubject == null && progressCurrent == null && From f346d9b8efc63b9c4d81073af8d461a179b655c7 Mon Sep 17 00:00:00 2001 From: Kima Date: Fri, 3 May 2024 16:56:29 +0200 Subject: [PATCH 03/13] made dark mode colors better and bugfix --- refilc/lib/theme/theme.dart | 2 +- .../lib/pages/notes/notes_page.dart | 20 ++++++++++++------- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/refilc/lib/theme/theme.dart b/refilc/lib/theme/theme.dart index 755589e..1acae12 100644 --- a/refilc/lib/theme/theme.dart +++ b/refilc/lib/theme/theme.dart @@ -187,7 +187,7 @@ class AppTheme { accentColor == AccentColor.ogfilc) || !settings.newColors ? accent - : ColorsUtils().lighten(accent, amount: 0.3); + : ColorsUtils().lighten(accent, amount: 0.22); // Color newScaffoldBg = ColorsUtils().lighten(accent, amount: 0.4); Color newTertiary = (accentColor == AccentColor.adaptive || accentColor == AccentColor.custom || diff --git a/refilc_mobile_ui/lib/pages/notes/notes_page.dart b/refilc_mobile_ui/lib/pages/notes/notes_page.dart index c5ee6c5..61ff85c 100644 --- a/refilc_mobile_ui/lib/pages/notes/notes_page.dart +++ b/refilc_mobile_ui/lib/pages/notes/notes_page.dart @@ -135,13 +135,19 @@ class NotesPageState extends State with TickerProviderStateMixin { title: Text('your_notes'.i18n), padding: EdgeInsets.zero, isTransparent: true, - child: Center( - child: Wrap( - spacing: 18.0, - runSpacing: 18.0, - children: selfNoteTiles, - ), - ), + child: selfNoteTiles.length > 1 + ? Center( + child: Wrap( + spacing: 18.0, + runSpacing: 18.0, + children: selfNoteTiles, + ), + ) + : Wrap( + spacing: 18.0, + runSpacing: 18.0, + children: selfNoteTiles, + ), )); } From 0b96cd9080ffba3bc2793615f8ae45dbe2efd68c Mon Sep 17 00:00:00 2001 From: Kima Date: Fri, 3 May 2024 17:08:03 +0200 Subject: [PATCH 04/13] things things --- refilc/lib/ui/widgets/lesson/lesson_tile.dart | 4 +-- .../lib/pages/absences/absences_page.dart | 34 +++++++++++-------- .../lib/pages/home/live_card/live_card.dart | 2 +- 3 files changed, 23 insertions(+), 17 deletions(-) diff --git a/refilc/lib/ui/widgets/lesson/lesson_tile.dart b/refilc/lib/ui/widgets/lesson/lesson_tile.dart index 5dbbdaf..f2041da 100644 --- a/refilc/lib/ui/widgets/lesson/lesson_tile.dart +++ b/refilc/lib/ui/widgets/lesson/lesson_tile.dart @@ -246,7 +246,7 @@ class LessonTile extends StatelessWidget { ? accent.withOpacity(.15) : Theme.of(context) .colorScheme - .secondary + .tertiary .withOpacity(.15), borderRadius: BorderRadius.circular(10.0), ), @@ -398,7 +398,7 @@ class LessonTile extends StatelessWidget { ? accent.withOpacity(.15) : Theme.of(context) .colorScheme - .secondary + .tertiary .withOpacity(.15), borderRadius: BorderRadius.circular(10.0), ), diff --git a/refilc_mobile_ui/lib/pages/absences/absences_page.dart b/refilc_mobile_ui/lib/pages/absences/absences_page.dart index 6b02ce1..ff339db 100644 --- a/refilc_mobile_ui/lib/pages/absences/absences_page.dart +++ b/refilc_mobile_ui/lib/pages/absences/absences_page.dart @@ -362,6 +362,7 @@ class AbsencesPageState extends State List unexcused = []; List excused = []; + List pending = []; List absencePositions = []; List finalChartColors = []; @@ -375,13 +376,14 @@ class AbsencesPageState extends State .where((e) => e.delay == 0 && e.state == Justification.excused) .toList(); + pending = absenceProvider.absences + .where((e) => + e.delay == 0 && e.state == Justification.pending) + .toList(); value1 = excused.length; value2 = unexcused.length; - value3 = absenceProvider.absences - .where((e) => - e.delay == 0 && e.state == Justification.pending) - .length; + value3 = pending.length; title1 = "stat_1".i18n; title2 = "stat_2".i18n; suffix = " ${"hr".i18n}"; @@ -394,15 +396,15 @@ class AbsencesPageState extends State .where((e) => e.delay != 0 && e.state == Justification.excused) .toList(); + pending = absenceProvider.absences + .where((e) => + e.delay != 0 && e.state == Justification.pending) + .toList(); value1 = excused.map((e) => e.delay).fold(0, (a, b) => a + b); value2 = unexcused.map((e) => e.delay).fold(0, (a, b) => a + b); - value3 = absenceProvider.absences - .where((e) => - e.delay != 0 && e.state == Justification.pending) - .map((e) => e.delay) - .fold(0, (a, b) => a + b); + value3 = pending.map((e) => e.delay).fold(0, (a, b) => a + b); title1 = "stat_3".i18n; title2 = "stat_4".i18n; suffix = " ${"min".i18n}"; @@ -417,7 +419,7 @@ class AbsencesPageState extends State int barTotal = DateTime.now().difference(DateTime(yr, 09, 01)).inDays; - [...unexcused, ...excused].forEachIndexed((i, a) { + [...unexcused, ...excused, ...pending].forEachIndexed((i, a) { int abs = DateTime.now().difference(a.date).inDays; double startPos = (barTotal - abs) / barTotal; @@ -435,11 +437,14 @@ class AbsencesPageState extends State end: endPos, color: a.state == Justification.excused ? Colors.green - : Colors.red, + : a.state == Justification.unexcused + ? Colors.red + : Colors.orange, )); - if ([...unexcused, ...excused].length > i + 1) { + if ([...unexcused, ...excused, ...pending].length > i + 1) { int nextAbs = DateTime.now() - .difference([...unexcused, ...excused][i + 1].date) + .difference( + [...unexcused, ...excused, ...pending][i + 1].date) .inDays; double nextStartPos = (barTotal - nextAbs) / barTotal; @@ -454,7 +459,8 @@ class AbsencesPageState extends State // print(value2.toString() + '-total'); // print(absenceChartData.length.toString() + '-chartdata'); - if ((i + 1 == [...unexcused, ...excused].length) && + if ((i + 1 == + [...unexcused, ...excused, ...pending].length) && endPos < 0.999) { absenceChartData.add(AbsenceChartData( start: endPos, diff --git a/refilc_mobile_ui/lib/pages/home/live_card/live_card.dart b/refilc_mobile_ui/lib/pages/home/live_card/live_card.dart index e693e73..3cf6f4b 100644 --- a/refilc_mobile_ui/lib/pages/home/live_card/live_card.dart +++ b/refilc_mobile_ui/lib/pages/home/live_card/live_card.dart @@ -525,7 +525,7 @@ class LiveCardStateA extends State { decoration: BoxDecoration( color: Theme.of(context) .colorScheme - .secondary + .tertiary .withOpacity(.15), borderRadius: BorderRadius.circular(10.0), From 5f6ad03335b3a755b0381bbde104a49f7ff2dc07 Mon Sep 17 00:00:00 2001 From: Kima Date: Fri, 3 May 2024 17:33:14 +0200 Subject: [PATCH 05/13] some progress in lesson tile popup --- .../widgets/lesson/lesson_viewable.dart | 508 ++++++++++++------ 1 file changed, 357 insertions(+), 151 deletions(-) diff --git a/refilc_mobile_ui/lib/common/widgets/lesson/lesson_viewable.dart b/refilc_mobile_ui/lib/common/widgets/lesson/lesson_viewable.dart index a30dd2b..1cca668 100644 --- a/refilc_mobile_ui/lib/common/widgets/lesson/lesson_viewable.dart +++ b/refilc_mobile_ui/lib/common/widgets/lesson/lesson_viewable.dart @@ -1,9 +1,15 @@ import 'package:flutter_feather_icons/flutter_feather_icons.dart'; +import 'package:flutter_svg/svg.dart'; import 'package:provider/provider.dart'; import 'package:refilc/api/providers/database_provider.dart'; import 'package:refilc/api/providers/user_provider.dart'; +import 'package:refilc/helpers/subject.dart'; +import 'package:refilc/theme/colors/colors.dart'; +import 'package:refilc/theme/colors/utils.dart'; import 'package:refilc_kreta_api/models/lesson.dart'; +import 'package:refilc_mobile_ui/common/bottom_sheet_menu/rounded_bottom_sheet.dart'; import 'package:refilc_mobile_ui/common/panel/panel_button.dart'; +import 'package:refilc_mobile_ui/common/round_border_icon.dart'; import 'package:refilc_mobile_ui/common/viewable.dart'; import 'package:refilc_mobile_ui/common/widgets/card_handle.dart'; import 'package:refilc/ui/widgets/lesson/lesson_tile.dart'; @@ -49,158 +55,164 @@ class LessonViewableState extends State { if (lsn.subject.id == '' || tile.lesson.isEmpty) return tile; - return Viewable( - tile: tile, - view: CardHandle(child: LessonView(lsn)), - actions: [ - PanelButton( - background: true, - title: Text( - "edit_lesson".i18n, - textAlign: TextAlign.center, - maxLines: 2, - overflow: TextOverflow.ellipsis, - ), - onPressed: () { - Navigator.of(context, rootNavigator: true).pop(); - - if (!Provider.of(context, listen: false) - .hasScope(PremiumScopes.timetableNotes)) { - PlusLockedFeaturePopup.show( - context: context, feature: PremiumFeature.timetableNotes); - - return; - } - - showDialog( - context: context, - builder: (context) => StatefulBuilder(builder: (context, setS) { - return AlertDialog( - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(14.0))), - title: Text("edit_lesson".i18n), - content: Column( - mainAxisSize: MainAxisSize.min, - children: [ - // description - TextField( - controller: _descTxt, - decoration: InputDecoration( - border: OutlineInputBorder( - borderSide: const BorderSide( - color: Colors.grey, width: 1.5), - borderRadius: BorderRadius.circular(12.0), - ), - focusedBorder: OutlineInputBorder( - borderSide: const BorderSide( - color: Colors.grey, width: 1.5), - borderRadius: BorderRadius.circular(12.0), - ), - contentPadding: - const EdgeInsets.symmetric(horizontal: 12.0), - hintText: 'l_desc'.i18n, - suffixIcon: IconButton( - icon: const Icon( - FeatherIcons.x, - color: Colors.grey, - size: 18.0, - ), - onPressed: () { - setState(() { - _descTxt.text = ''; - }); - }, - ), - ), - ), - // const SizedBox( - // height: 14.0, - // ), - // // class - // TextField( - // controller: _descTxt, - // onEditingComplete: () async { - // // SharedTheme? theme = await shareProvider.getThemeById( - // // context, - // // id: _paintId.text.replaceAll(' ', ''), - // // ); - - // // if (theme != null) { - // // // set theme variable - // // newThemeByID = theme; - - // // _paintId.clear(); - // // } else { - // // ScaffoldMessenger.of(context).showSnackBar( - // // CustomSnackBar( - // // content: Text("theme_not_found".i18n, - // // style: const TextStyle(color: Colors.white)), - // // backgroundColor: AppColors.of(context).red, - // // context: context, - // // ), - // // ); - // // } - // }, - // decoration: InputDecoration( - // border: OutlineInputBorder( - // borderSide: const BorderSide( - // color: Colors.grey, width: 1.5), - // borderRadius: BorderRadius.circular(12.0), - // ), - // focusedBorder: OutlineInputBorder( - // borderSide: const BorderSide( - // color: Colors.grey, width: 1.5), - // borderRadius: BorderRadius.circular(12.0), - // ), - // contentPadding: - // const EdgeInsets.symmetric(horizontal: 12.0), - // hintText: 'l_desc'.i18n, - // suffixIcon: IconButton( - // icon: const Icon( - // FeatherIcons.x, - // color: Colors.grey, - // size: 18.0, - // ), - // onPressed: () { - // setState(() { - // _descTxt.text = ''; - // }); - // }, - // ), - // ), - // ), - ], - ), - actions: [ - TextButton( - child: Text( - "cancel".i18n, - style: const TextStyle(fontWeight: FontWeight.w500), - ), - onPressed: () { - Navigator.of(context).maybePop(); - }, - ), - TextButton( - child: Text( - "done".i18n, - style: const TextStyle(fontWeight: FontWeight.w500), - ), - onPressed: () async { - saveLesson(); - - Navigator.of(context).pop(); - setState(() {}); - }, - ), - ], - ); - }), - ); - }, - ), - ], + return LessonTile( + lsn, + swapDesc: widget.swapDesc, + onTap: () => TimetableLessonPopup.show(context: context, lesson: lsn), ); + + // return Viewable( + // tile: tile, + // view: CardHandle(child: LessonView(lsn)), + // actions: [ + // PanelButton( + // background: true, + // title: Text( + // "edit_lesson".i18n, + // textAlign: TextAlign.center, + // maxLines: 2, + // overflow: TextOverflow.ellipsis, + // ), + // onPressed: () { + // Navigator.of(context, rootNavigator: true).pop(); + + // if (!Provider.of(context, listen: false) + // .hasScope(PremiumScopes.timetableNotes)) { + // PlusLockedFeaturePopup.show( + // context: context, feature: PremiumFeature.timetableNotes); + + // return; + // } + + // showDialog( + // context: context, + // builder: (context) => StatefulBuilder(builder: (context, setS) { + // return AlertDialog( + // shape: const RoundedRectangleBorder( + // borderRadius: BorderRadius.all(Radius.circular(14.0))), + // title: Text("edit_lesson".i18n), + // content: Column( + // mainAxisSize: MainAxisSize.min, + // children: [ + // // description + // TextField( + // controller: _descTxt, + // decoration: InputDecoration( + // border: OutlineInputBorder( + // borderSide: const BorderSide( + // color: Colors.grey, width: 1.5), + // borderRadius: BorderRadius.circular(12.0), + // ), + // focusedBorder: OutlineInputBorder( + // borderSide: const BorderSide( + // color: Colors.grey, width: 1.5), + // borderRadius: BorderRadius.circular(12.0), + // ), + // contentPadding: + // const EdgeInsets.symmetric(horizontal: 12.0), + // hintText: 'l_desc'.i18n, + // suffixIcon: IconButton( + // icon: const Icon( + // FeatherIcons.x, + // color: Colors.grey, + // size: 18.0, + // ), + // onPressed: () { + // setState(() { + // _descTxt.text = ''; + // }); + // }, + // ), + // ), + // ), + // // const SizedBox( + // // height: 14.0, + // // ), + // // // class + // // TextField( + // // controller: _descTxt, + // // onEditingComplete: () async { + // // // SharedTheme? theme = await shareProvider.getThemeById( + // // // context, + // // // id: _paintId.text.replaceAll(' ', ''), + // // // ); + + // // // if (theme != null) { + // // // // set theme variable + // // // newThemeByID = theme; + + // // // _paintId.clear(); + // // // } else { + // // // ScaffoldMessenger.of(context).showSnackBar( + // // // CustomSnackBar( + // // // content: Text("theme_not_found".i18n, + // // // style: const TextStyle(color: Colors.white)), + // // // backgroundColor: AppColors.of(context).red, + // // // context: context, + // // // ), + // // // ); + // // // } + // // }, + // // decoration: InputDecoration( + // // border: OutlineInputBorder( + // // borderSide: const BorderSide( + // // color: Colors.grey, width: 1.5), + // // borderRadius: BorderRadius.circular(12.0), + // // ), + // // focusedBorder: OutlineInputBorder( + // // borderSide: const BorderSide( + // // color: Colors.grey, width: 1.5), + // // borderRadius: BorderRadius.circular(12.0), + // // ), + // // contentPadding: + // // const EdgeInsets.symmetric(horizontal: 12.0), + // // hintText: 'l_desc'.i18n, + // // suffixIcon: IconButton( + // // icon: const Icon( + // // FeatherIcons.x, + // // color: Colors.grey, + // // size: 18.0, + // // ), + // // onPressed: () { + // // setState(() { + // // _descTxt.text = ''; + // // }); + // // }, + // // ), + // // ), + // // ), + // ], + // ), + // actions: [ + // TextButton( + // child: Text( + // "cancel".i18n, + // style: const TextStyle(fontWeight: FontWeight.w500), + // ), + // onPressed: () { + // Navigator.of(context).maybePop(); + // }, + // ), + // TextButton( + // child: Text( + // "done".i18n, + // style: const TextStyle(fontWeight: FontWeight.w500), + // ), + // onPressed: () async { + // saveLesson(); + + // Navigator.of(context).pop(); + // setState(() {}); + // }, + // ), + // ], + // ); + // }), + // ); + // }, + // ), + // ], + // ); } void saveLesson() async { @@ -218,3 +230,197 @@ class LessonViewableState extends State { .storeCustomLessonDescriptions(lessonDesc, userId: user.id!); } } + +class TimetableLessonPopup extends StatelessWidget { + const TimetableLessonPopup({super.key, required this.lesson}); + + final Lesson lesson; + + static void show({ + required BuildContext context, + required Lesson lesson, + }) => + showRoundedModalBottomSheet( + context, + child: TimetableLessonPopup( + lesson: lesson, + ), + showHandle: false, + ); + + // IconData _getIcon() => _featureLevels[feature] == PremiumFeatureLevel.cap + // ? FilcIcons.kupak + // : _featureLevels[feature] == PremiumFeatureLevel.ink + // ? FilcIcons.tinta + // : FilcIcons.tinta; + // Color _getColor(BuildContext context) => + // _featureLevels[feature] == PremiumFeatureLevel.gold + // ? const Color(0xFFC89B08) + // : Theme.of(context).brightness == Brightness.light + // ? const Color(0xff691A9B) + // : const Color(0xffA66FC8); + // String? _getAsset() => _featureAssets[feature]; + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + color: Theme.of(context).scaffoldBackgroundColor, + borderRadius: const BorderRadius.vertical( + top: Radius.circular(12.0), + ), + ), + child: Stack( + children: [ + SvgPicture.asset( + "assets/svg/mesh_bg.svg", + // ignore: deprecated_member_use + color: ColorsUtils().fade( + context, Theme.of(context).scaffoldBackgroundColor, + darkenAmount: 0.1, lightenAmount: 0.1), + width: MediaQuery.of(context).size.width, + ), + SizedBox( + width: MediaQuery.of(context).size.width, + child: Padding( + padding: const EdgeInsets.all(18.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + width: 40, + height: 4, + decoration: BoxDecoration( + color: ColorsUtils().fade( + context, Theme.of(context).scaffoldBackgroundColor, + darkenAmount: 0.2, lightenAmount: 0.2), + borderRadius: BorderRadius.circular( + 2.0, + ), + ), + ), + const SizedBox( + height: 38.0, + ), + RoundBorderIcon( + icon: Icon( + SubjectIcon.resolveVariant( + context: context, subject: lesson.subject), + ), + ), + const SizedBox( + height: 55.0, + ), + Container( + width: double.infinity, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.background, + borderRadius: const BorderRadius.vertical( + top: Radius.circular(12.0), + bottom: Radius.circular(6.0), + ), + ), + padding: const EdgeInsets.all(14.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '6:09 - 4:20', + style: TextStyle( + color: AppColors.of(context).text.withOpacity(0.85), + fontSize: 14.0, + fontWeight: FontWeight.w500, + ), + ), + const SizedBox( + height: 12.0, + ), + Text( + lesson.name, + style: TextStyle( + color: AppColors.of(context).text, + fontSize: 20.0, + fontWeight: FontWeight.w700, + ), + ), + const SizedBox( + height: 8.0, + ), + Text( + lesson.teacher.name, + style: TextStyle( + color: AppColors.of(context).text.withOpacity(0.9), + fontSize: 14.0, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + const SizedBox( + height: 6.0, + ), + Container( + width: double.infinity, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.background, + borderRadius: const BorderRadius.vertical( + top: Radius.circular(6.0), + bottom: Radius.circular(12.0), + ), + ), + padding: const EdgeInsets.all(14.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + lesson.description, + style: TextStyle( + color: AppColors.of(context).text.withOpacity(0.9), + fontSize: 14.0, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ), + const SizedBox( + height: 24.0, + ), + GestureDetector( + onTap: () { + Navigator.of(context, rootNavigator: true) + .pushReplacementNamed('/'); + }, + child: Container( + width: double.infinity, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.background, + borderRadius: BorderRadius.circular(12.0), + ), + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + 'view_subject'.i18n, + style: TextStyle( + color: + AppColors.of(context).text.withOpacity(0.9), + fontSize: 18.0, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + ), + ], + ), + ), + ), + ], + ), + ); + } +} From d217265ae7fc436009c5bc71a063a0f6210526f1 Mon Sep 17 00:00:00 2001 From: zypherift Date: Fri, 3 May 2024 18:41:19 +0200 Subject: [PATCH 06/13] revert login screen --- .../lib/screens/login/login_screen.dart | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/refilc_mobile_ui/lib/screens/login/login_screen.dart b/refilc_mobile_ui/lib/screens/login/login_screen.dart index 433d283..fd21903 100644 --- a/refilc_mobile_ui/lib/screens/login/login_screen.dart +++ b/refilc_mobile_ui/lib/screens/login/login_screen.dart @@ -190,12 +190,12 @@ class LoginScreenState extends State { ), ], )), - const SizedBox(height: 15.625), - Padding( - padding: const EdgeInsets.only( - left: 16, right: 16), - child: Image.asset( - 'assets/images/showcase$i.png')) + const SizedBox(height: 15.625), //meth + // Padding( + // padding: const EdgeInsets.only( + // left: 16, right: 16), + // child: Image.asset( + // 'assets/images/showcase$i.png')) ], ); }, @@ -210,7 +210,7 @@ class LoginScreenState extends State { decoration: const BoxDecoration( gradient: LinearGradient( colors: [Color(0x00DAE4F7), Color(0xFFDAE4F7)], - stops: [0, 0.1], + stops: [0, 0.05], begin: Alignment.topCenter, end: Alignment.bottomCenter, ), @@ -238,7 +238,7 @@ class LoginScreenState extends State { "login".i18n, style: const TextStyle( fontFamily: 'Montserrat', - fontSize: 20, + fontSize: 19, fontWeight: FontWeight.w700), )), ), From 56c87b146ba679e6ed9bd0f14ae6d71e01e290d5 Mon Sep 17 00:00:00 2001 From: zypherift Date: Fri, 3 May 2024 19:05:19 +0200 Subject: [PATCH 07/13] remove comment --- .../lib/screens/login/login_screen.dart | 798 ++++++++---------- 1 file changed, 335 insertions(+), 463 deletions(-) diff --git a/refilc_mobile_ui/lib/screens/login/login_screen.dart b/refilc_mobile_ui/lib/screens/login/login_screen.dart index 3f99c7d..fa79fc2 100644 --- a/refilc_mobile_ui/lib/screens/login/login_screen.dart +++ b/refilc_mobile_ui/lib/screens/login/login_screen.dart @@ -86,483 +86,355 @@ class LoginScreenState extends State { precacheImage(const AssetImage('assets/images/showcase3.png'), context); precacheImage(const AssetImage('assets/images/showcase4.png'), context); - return Portal( - child: Scaffold( - body: Container( - decoration: const BoxDecoration(color: Color(0xFFDAE4F7)), - child: SingleChildScrollView( - physics: const ClampingScrollPhysics(), - controller: _scrollController, - child: Container( - decoration: const BoxDecoration(color: Color(0xFFDAE4F7)), - width: MediaQuery.of(context).size.width, - height: MediaQuery.of(context).size.height, - child: SafeArea( - child: Column( - children: [ - // app icon - Padding( - padding: const EdgeInsets.only(left: 24, top: 20), - child: Row( - children: [ - Image.asset( - 'assets/icons/ic_rounded.png', - width: 30.0, - ), - const SizedBox(width: 8), - const Text( - 'reFilc', - style: TextStyle( - color: Color(0xFF050B15), - fontSize: 18.0, - fontWeight: FontWeight.bold, - fontFamily: 'Montserrat'), - ), - Material( - type: MaterialType.transparency, - child: showBack - ? BackButton( - color: AppColors.of(context).text) - : const SizedBox(height: 48.0), - ), - ], - )), - Stack( - alignment: Alignment.bottomCenter, - children: [ - // Column( - // mainAxisAlignment: MainAxisAlignment.center, - // crossAxisAlignment: CrossAxisAlignment.center, - // children: [ - // const SizedBox(height: 21), - // CarouselSlider( - // options: CarouselOptions( - // height: MediaQuery.of(context).size.height, - // viewportFraction: 1, - // autoPlay: true, - // autoPlayInterval: const Duration(seconds: 6), - // pauseAutoPlayOnTouch: true), - // items: [1, 2, 3, 4].map((i) { - // return Builder( - // builder: (BuildContext context) { - // return Column( - // crossAxisAlignment: - // CrossAxisAlignment.start, - // mainAxisAlignment: - // MainAxisAlignment.start, - // children: [ - // Padding( - // padding: - // const EdgeInsets.only(left: 24), - // child: Column( - // crossAxisAlignment: - // CrossAxisAlignment.start, - // mainAxisAlignment: - // MainAxisAlignment.start, - // children: [ - // Text( - // "welcome_title_$i".i18n, - // style: const TextStyle( - // color: Color(0xFF050B15), - // fontSize: 19, - // fontFamily: 'Montserrat', - // fontWeight: - // FontWeight.w700, - // height: 1.3), - // ), - // const SizedBox( - // height: 14.375), //meth - // Padding( - // padding: - // const EdgeInsets.only( - // right: 20), - // child: Text( - // "welcome_text_$i".i18n, - // style: const TextStyle( - // color: - // Color(0xFF050B15), - // fontFamily: 'FigTree', - // fontWeight: - // FontWeight.w500, - // fontSize: 17, - // height: 1.3), - // ), - // ), - // ], - // )), - // const SizedBox(height: 15.625), - // Padding( - // padding: const EdgeInsets.only( - // left: 16, right: 16), - // child: Image.asset( - // 'assets/images/showcase$i.png')) - // ], - // ); - // }, - // ); - // }).toList(), - // ), - // ], - // ), - // Container( - // height: 250, - // width: double.infinity, - // decoration: const BoxDecoration( - // gradient: LinearGradient( - // colors: [Color(0x00DAE4F7), Color(0xFFDAE4F7)], - // stops: [0, 0.1], - // begin: Alignment.topCenter, - // end: Alignment.bottomCenter, - // ), - // ), - // child: Padding( - // padding: const EdgeInsets.only(top: 3), - // child: Column( - // children: [ - // SizedBox( - // height: 48, - // width: double.infinity, - // child: Padding( - // padding: const EdgeInsets.symmetric( - // horizontal: 16), - // child: FilledButton( - // style: ButtonStyle( - // shape: MaterialStateProperty.all< - // RoundedRectangleBorder>( - // const RoundedRectangleBorder( - // borderRadius: BorderRadius.all( - // Radius.circular(12)), - // ))), - // onPressed: () {}, - // child: Text( - // "login".i18n, - // style: const TextStyle( - // fontFamily: 'Montserrat', - // fontSize: 20, - // fontWeight: FontWeight.w700), - // )), - // ), - // ), - // const SizedBox(height: 8), - // ], - // ), - // ), - // ) - // - // - // TODO: OLD LOGIN FROM HERE - Column( - //login buttons and ui starts here - mainAxisAlignment: MainAxisAlignment.end, - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - const SizedBox(height: 21), - CarouselSlider( - options: CarouselOptions( - height: MediaQuery.of(context).size.height, - viewportFraction: 1, - autoPlay: true, - autoPlayInterval: const Duration(seconds: 6), - pauseAutoPlayOnTouch: true), - items: [1, 2, 3, 4].map((i) { - return Builder( - builder: (BuildContext context) { - return Column( - crossAxisAlignment: - CrossAxisAlignment.start, - mainAxisAlignment: - MainAxisAlignment.start, - children: [ - Padding( - padding: - const EdgeInsets.only(left: 24), - child: Column( - crossAxisAlignment: - CrossAxisAlignment.start, - mainAxisAlignment: - MainAxisAlignment.start, - children: [ - Text( - "welcome_title_$i".i18n, + return Scaffold( + body: Container( + decoration: const BoxDecoration(color: Color(0xFFDAE4F7)), + child: SingleChildScrollView( + physics: const ClampingScrollPhysics(), + controller: _scrollController, + child: Container( + decoration: const BoxDecoration(color: Color(0xFFDAE4F7)), + width: MediaQuery.of(context).size.width, + height: MediaQuery.of(context).size.height, + child: SafeArea( + child: Column( + children: [ + // app icon + Padding( + padding: const EdgeInsets.only(left: 24, top: 20), + child: Row( + children: [ + Image.asset( + 'assets/icons/ic_rounded.png', + width: 30.0, + ), + const SizedBox(width: 8), + const Text( + 'reFilc', + style: TextStyle( + color: Color(0xFF050B15), + fontSize: 18.0, + fontWeight: FontWeight.bold, + fontFamily: 'Montserrat'), + ), + Material( + type: MaterialType.transparency, + child: showBack + ? BackButton(color: AppColors.of(context).text) + : const SizedBox(height: 48.0), + ), + ], + )), + Stack( + alignment: Alignment.bottomCenter, + children: [ + Column( + //login buttons and ui starts here + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + const SizedBox(height: 21), + CarouselSlider( + options: CarouselOptions( + height: MediaQuery.of(context).size.height, + viewportFraction: 1, + autoPlay: true, + autoPlayInterval: const Duration(seconds: 6), + pauseAutoPlayOnTouch: true), + items: [1, 2, 3, 4].map((i) { + return Builder( + builder: (BuildContext context) { + return Column( + crossAxisAlignment: + CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Padding( + padding: + const EdgeInsets.only(left: 24), + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + mainAxisAlignment: + MainAxisAlignment.start, + children: [ + Text( + "welcome_title_$i".i18n, + style: const TextStyle( + color: Color(0xFF050B15), + fontSize: 19, + fontFamily: 'Montserrat', + fontWeight: FontWeight.w700, + height: 1.3), + ), + const SizedBox( + height: 14.375), //meth + Padding( + padding: const EdgeInsets.only( + right: 20), + child: Text( + "welcome_text_$i".i18n, style: const TextStyle( color: Color(0xFF050B15), - fontSize: 19, - fontFamily: 'Montserrat', + fontFamily: 'FigTree', fontWeight: - FontWeight.w700, + FontWeight.w500, + fontSize: 17, height: 1.3), ), - const SizedBox( - height: 14.375), //meth - Padding( - padding: - const EdgeInsets.only( - right: 20), - child: Text( - "welcome_text_$i".i18n, - style: const TextStyle( - color: - Color(0xFF050B15), - fontFamily: 'FigTree', - fontWeight: - FontWeight.w500, - fontSize: 17, - height: 1.3), - ), - ), - ], - )), - const SizedBox(height: 15.625), - Padding( - padding: const EdgeInsets.only( - left: 16, right: 16), - child: Image.asset( - 'assets/images/showcase$i.png')) - ], - ); - }, - ); - }).toList(), - ), - ], - ), - Container( - height: 250, - width: double.infinity, - decoration: const BoxDecoration( - gradient: LinearGradient( - colors: [Color(0x00DAE4F7), Color(0xFFDAE4F7)], - stops: [0, 0.1], - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - ), + ), + ], + )), + const SizedBox(height: 15.625), + Padding( + padding: const EdgeInsets.only( + left: 16, right: 16), + child: Image.asset( + 'assets/images/showcase$i.png')) + ], + ); + }, + ); + }).toList(), ), - child: Padding( - padding: const EdgeInsets.only(top: 3), - child: Column( - children: [ - SizedBox( - height: 48, - width: double.infinity, - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 16), - child: FilledButton( - style: ButtonStyle( - shape: MaterialStateProperty.all< - RoundedRectangleBorder>( - const RoundedRectangleBorder( - borderRadius: BorderRadius.all( - Radius.circular(12)), - ))), - onPressed: () {}, - child: Text( - "login".i18n, - style: const TextStyle( - fontFamily: 'Montserrat', - fontSize: 20, - fontWeight: FontWeight.w700), - )), - ), + ], + ), + Container( + height: 250, + width: double.infinity, + decoration: const BoxDecoration( + gradient: LinearGradient( + colors: [Color(0x00DAE4F7), Color(0xFFDAE4F7)], + stops: [0, 0.1], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ), + ), + child: Padding( + padding: const EdgeInsets.only(top: 3), + child: Column( + children: [ + SizedBox( + height: 48, + width: double.infinity, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16), + child: FilledButton( + style: ButtonStyle( + shape: MaterialStateProperty.all< + RoundedRectangleBorder>( + const RoundedRectangleBorder( + borderRadius: BorderRadius.all( + Radius.circular(12)), + ))), + onPressed: () {}, + child: Text( + "login".i18n, + style: const TextStyle( + fontFamily: 'Montserrat', + fontSize: 20, + fontWeight: FontWeight.w700), + )), ), - const SizedBox(height: 8), - ], - ), + ), + const SizedBox(height: 8), + ], ), - ) - // Column( - // //login buttons and ui starts here - // mainAxisAlignment: MainAxisAlignment.end, - // crossAxisAlignment: CrossAxisAlignment.end, - // children: [ - // Padding( - // padding: const EdgeInsets.only( - // left: 22.0, - // right: 22.0, - // top: 0.0, - // ), - // child: AutofillGroup( - // child: Column( - // crossAxisAlignment: CrossAxisAlignment.end, - // children: [ - // // username - // Padding( - // padding: - // const EdgeInsets.only(bottom: 6.0), - // child: Row( - // mainAxisAlignment: - // MainAxisAlignment.spaceBetween, - // children: [ - // Expanded( - // child: Text( - // "username".i18n, - // maxLines: 1, - // style: TextStyle( - // color: AppColors.of(context) - // .loginPrimary, - // fontWeight: FontWeight.w500, - // fontSize: 12.0, - // ), - // ), - // ), - // Expanded( - // child: Text( - // "usernameHint".i18n, - // maxLines: 1, - // textAlign: TextAlign.right, - // style: TextStyle( - // color: AppColors.of(context) - // .loginSecondary, - // fontWeight: FontWeight.w500, - // fontSize: 12.0, - // ), - // ), - // ), - // ], - // ), - // ), - // Padding( - // padding: - // const EdgeInsets.only(bottom: 12.0), - // child: LoginInput( - // style: LoginInputStyle.username, - // controller: usernameController, - // ), - // ), - - // // password - // Padding( - // padding: - // const EdgeInsets.only(bottom: 6.0), - // child: Row( - // mainAxisAlignment: - // MainAxisAlignment.spaceBetween, - // children: [ - // Expanded( - // child: Text( - // "password".i18n, - // maxLines: 1, - // style: TextStyle( - // color: AppColors.of(context) - // .loginPrimary, - // fontWeight: FontWeight.w500, - // fontSize: 12.0, - // ), - // ), - // ), - // Expanded( - // child: Text( - // "passwordHint".i18n, - // maxLines: 1, - // textAlign: TextAlign.right, - // style: TextStyle( - // color: AppColors.of(context) - // .loginSecondary, - // fontWeight: FontWeight.w500, - // fontSize: 12.0, - // ), - // ), - // ), - // ], - // ), - // ), - // Padding( - // padding: - // const EdgeInsets.only(bottom: 12.0), - // child: LoginInput( - // style: LoginInputStyle.password, - // controller: passwordController, - // ), - // ), - - // // school - // Padding( - // padding: - // const EdgeInsets.only(bottom: 6.0), - // child: Text( - // "school".i18n, - // maxLines: 1, - // style: TextStyle( - // color: AppColors.of(context) - // .loginPrimary, - // fontWeight: FontWeight.w500, - // fontSize: 12.0, - // ), - // ), - // ), - // SchoolInput( - // scroll: _scrollController, - // controller: schoolController, - // ), - // ], - // ), - // ), - // ), - // Padding( - // padding: const EdgeInsets.only( - // top: 35.0, - // left: 22.0, - // right: 22.0, - // ), - // child: Visibility( - // visible: _loginState != LoginState.inProgress, - // replacement: const Padding( - // padding: EdgeInsets.symmetric(vertical: 6.0), - // child: CircularProgressIndicator( - // valueColor: AlwaysStoppedAnimation( - // Colors.white), - // ), - // ), - // child: LoginButton( - // child: Text("login".i18n, - // maxLines: 1, - // style: const TextStyle( - // fontWeight: FontWeight.bold, - // fontSize: 20.0, - // )), - // onPressed: () => _loginAPI(context: context), - // ), - // ), - // ), - // ], - // ), - ], - ), - - if (_loginState == LoginState.missingFields || - _loginState == LoginState.invalidGrant || - _loginState == LoginState.failed) - Padding( - padding: const EdgeInsets.only( - top: 8.0, left: 12.0, right: 12.0), - child: Text( - [ - "missing_fields", - "invalid_grant", - "error" - ][_loginState.index] - .i18n, - style: const TextStyle( - color: Colors.red, - fontWeight: FontWeight.w500, - ), - textAlign: TextAlign.center, ), - ), - // privacy policy - GestureDetector( - onTap: () => PrivacyView.show(context), + ) + // Column( + // //login buttons and ui starts here + // mainAxisAlignment: MainAxisAlignment.end, + // crossAxisAlignment: CrossAxisAlignment.end, + // children: [ + // Padding( + // padding: const EdgeInsets.only( + // left: 22.0, + // right: 22.0, + // top: 0.0, + // ), + // child: AutofillGroup( + // child: Column( + // crossAxisAlignment: CrossAxisAlignment.end, + // children: [ + // // username + // Padding( + // padding: + // const EdgeInsets.only(bottom: 6.0), + // child: Row( + // mainAxisAlignment: + // MainAxisAlignment.spaceBetween, + // children: [ + // Expanded( + // child: Text( + // "username".i18n, + // maxLines: 1, + // style: TextStyle( + // color: AppColors.of(context) + // .loginPrimary, + // fontWeight: FontWeight.w500, + // fontSize: 12.0, + // ), + // ), + // ), + // Expanded( + // child: Text( + // "usernameHint".i18n, + // maxLines: 1, + // textAlign: TextAlign.right, + // style: TextStyle( + // color: AppColors.of(context) + // .loginSecondary, + // fontWeight: FontWeight.w500, + // fontSize: 12.0, + // ), + // ), + // ), + // ], + // ), + // ), + // Padding( + // padding: + // const EdgeInsets.only(bottom: 12.0), + // child: LoginInput( + // style: LoginInputStyle.username, + // controller: usernameController, + // ), + // ), + + // // password + // Padding( + // padding: + // const EdgeInsets.only(bottom: 6.0), + // child: Row( + // mainAxisAlignment: + // MainAxisAlignment.spaceBetween, + // children: [ + // Expanded( + // child: Text( + // "password".i18n, + // maxLines: 1, + // style: TextStyle( + // color: AppColors.of(context) + // .loginPrimary, + // fontWeight: FontWeight.w500, + // fontSize: 12.0, + // ), + // ), + // ), + // Expanded( + // child: Text( + // "passwordHint".i18n, + // maxLines: 1, + // textAlign: TextAlign.right, + // style: TextStyle( + // color: AppColors.of(context) + // .loginSecondary, + // fontWeight: FontWeight.w500, + // fontSize: 12.0, + // ), + // ), + // ), + // ], + // ), + // ), + // Padding( + // padding: + // const EdgeInsets.only(bottom: 12.0), + // child: LoginInput( + // style: LoginInputStyle.password, + // controller: passwordController, + // ), + // ), + + // // school + // Padding( + // padding: + // const EdgeInsets.only(bottom: 6.0), + // child: Text( + // "school".i18n, + // maxLines: 1, + // style: TextStyle( + // color: AppColors.of(context) + // .loginPrimary, + // fontWeight: FontWeight.w500, + // fontSize: 12.0, + // ), + // ), + // ), + // SchoolInput( + // scroll: _scrollController, + // controller: schoolController, + // ), + // ], + // ), + // ), + // ), + // Padding( + // padding: const EdgeInsets.only( + // top: 35.0, + // left: 22.0, + // right: 22.0, + // ), + // child: Visibility( + // visible: _loginState != LoginState.inProgress, + // replacement: const Padding( + // padding: EdgeInsets.symmetric(vertical: 6.0), + // child: CircularProgressIndicator( + // valueColor: AlwaysStoppedAnimation( + // Colors.white), + // ), + // ), + // child: LoginButton( + // child: Text("login".i18n, + // maxLines: 1, + // style: const TextStyle( + // fontWeight: FontWeight.bold, + // fontSize: 20.0, + // )), + // onPressed: () => _loginAPI(context: context), + // ), + // ), + // ), + // ], + // ), + ], + ), + + if (_loginState == LoginState.missingFields || + _loginState == LoginState.invalidGrant || + _loginState == LoginState.failed) + Padding( + padding: const EdgeInsets.only( + top: 8.0, left: 12.0, right: 12.0), child: Text( - 'privacy'.i18n, - style: TextStyle( - color: AppColors.of(context).loginSecondary, + [ + "missing_fields", + "invalid_grant", + "error" + ][_loginState.index] + .i18n, + style: const TextStyle( + color: Colors.red, fontWeight: FontWeight.w500, - fontSize: 14.0, ), + textAlign: TextAlign.center, ), ), - ], - ), + // privacy policy + GestureDetector( + onTap: () => PrivacyView.show(context), + child: Text( + 'privacy'.i18n, + style: TextStyle( + color: AppColors.of(context).loginSecondary, + fontWeight: FontWeight.w500, + fontSize: 14.0, + ), + ), + ), + ], ), ), ), From 816b14e735e505f14d385288b075e1fa14282216 Mon Sep 17 00:00:00 2001 From: zypherift Date: Fri, 3 May 2024 21:06:44 +0200 Subject: [PATCH 08/13] started bottommodalsheet, school selector is buggy need fix --- .../lib/screens/login/login_screen.dart | 449 +++++++++++------- 1 file changed, 285 insertions(+), 164 deletions(-) diff --git a/refilc_mobile_ui/lib/screens/login/login_screen.dart b/refilc_mobile_ui/lib/screens/login/login_screen.dart index fa79fc2..a9f2dbd 100644 --- a/refilc_mobile_ui/lib/screens/login/login_screen.dart +++ b/refilc_mobile_ui/lib/screens/login/login_screen.dart @@ -1,10 +1,9 @@ // import 'dart:async'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/widgets.dart'; import 'package:refilc/api/client.dart'; import 'package:refilc/api/login.dart'; import 'package:refilc/theme/colors/colors.dart'; +import 'package:refilc_mobile_ui/common/bottom_sheet_menu/rounded_bottom_sheet.dart'; import 'package:refilc_mobile_ui/common/custom_snack_bar.dart'; import 'package:refilc_mobile_ui/common/system_chrome.dart'; import 'package:refilc_mobile_ui/common/widgets/absence/absence_display.dart'; @@ -17,7 +16,6 @@ import 'package:flutter/services.dart'; import 'login_screen.i18n.dart'; import 'package:carousel_slider/carousel_slider.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import 'package:flutter_portal/flutter_portal.dart'; class LoginScreen extends StatefulWidget { const LoginScreen({super.key, this.back = false}); @@ -85,6 +83,7 @@ class LoginScreenState extends State { precacheImage(const AssetImage('assets/images/showcase2.png'), context); precacheImage(const AssetImage('assets/images/showcase3.png'), context); precacheImage(const AssetImage('assets/images/showcase4.png'), context); + bool selected = false; return Scaffold( body: Container( @@ -200,18 +199,18 @@ class LoginScreenState extends State { ], ), Container( - height: 250, + height: 300, width: double.infinity, decoration: const BoxDecoration( gradient: LinearGradient( colors: [Color(0x00DAE4F7), Color(0xFFDAE4F7)], - stops: [0, 0.1], + stops: [0, 0.12], begin: Alignment.topCenter, end: Alignment.bottomCenter, ), ), child: Padding( - padding: const EdgeInsets.only(top: 3), + padding: EdgeInsets.only(top: 50, bottom: MediaQuery.of(context).viewInsets.bottom), child: Column( children: [ SizedBox( @@ -228,7 +227,285 @@ class LoginScreenState extends State { borderRadius: BorderRadius.all( Radius.circular(12)), ))), - onPressed: () {}, + onPressed: () { + showModalBottomSheet( + backgroundColor: Colors.transparent, + context: context, + builder: (BuildContext context) { + return Container( + height: MediaQuery.of(context) + .size + .height * + 0.5 + MediaQuery.of(context).viewInsets.bottom, + decoration: const BoxDecoration( + color: Color(0xFFDAE4F7), + borderRadius: BorderRadius.only( + topRight: + Radius.circular(24.0), + topLeft: + Radius.circular(24.0), + ), + ), + child: Column( + crossAxisAlignment: + CrossAxisAlignment.center, + mainAxisAlignment: + MainAxisAlignment.start, + children: [ + Padding( + padding: + const EdgeInsets.only( + top: 18), + child: Container( + decoration: + const BoxDecoration( + color: + Color(0xFFB9C8E5), + borderRadius: + BorderRadius.only( + topRight: + Radius.circular( + 2.0), + topLeft: + Radius.circular( + 2.0), + ), + ), + width: 40, + height: 4, + ), + ), + Container( + width: double.infinity, + child: AutofillGroup( + child: Column( + crossAxisAlignment: + CrossAxisAlignment + .end, + children: [ + // username + Padding( + padding: + const EdgeInsets + .only( + bottom: + 6.0), + child: Row( + mainAxisAlignment: + MainAxisAlignment + .spaceBetween, + children: [ + Expanded( + child: Text( + "username" + .i18n, + maxLines: + 1, + style: + TextStyle( + color: AppColors.of(context) + .loginPrimary, + fontWeight: + FontWeight.w500, + fontSize: + 12.0, + ), + ), + ), + Expanded( + child: Text( + "usernameHint" + .i18n, + maxLines: + 1, + textAlign: + TextAlign + .right, + style: + TextStyle( + color: AppColors.of(context) + .loginSecondary, + fontWeight: + FontWeight.w500, + fontSize: + 12.0, + ), + ), + ), + ], + ), + ), + Padding( + padding: + const EdgeInsets + .only( + bottom: + 12.0), + child: LoginInput( + style: + LoginInputStyle + .username, + controller: + usernameController, + ), + ), + + // password + Padding( + padding: + const EdgeInsets + .only( + bottom: + 6.0), + child: Row( + mainAxisAlignment: + MainAxisAlignment + .spaceBetween, + children: [ + Expanded( + child: Text( + "password" + .i18n, + maxLines: + 1, + style: + TextStyle( + color: AppColors.of(context) + .loginPrimary, + fontWeight: + FontWeight.w500, + fontSize: + 12.0, + ), + ), + ), + Expanded( + child: Text( + "passwordHint" + .i18n, + maxLines: + 1, + textAlign: + TextAlign + .right, + style: + TextStyle( + color: AppColors.of(context) + .loginSecondary, + fontWeight: + FontWeight.w500, + fontSize: + 12.0, + ), + ), + ), + ], + ), + ), + Padding( + padding: + const EdgeInsets + .only( + bottom: + 12.0), + child: LoginInput( + style: + LoginInputStyle + .password, + controller: + passwordController, + ), + ), + + // school + Padding( + padding: + const EdgeInsets + .only( + bottom: + 6.0), + child: Text( + "school".i18n, + maxLines: 1, + style: + TextStyle( + color: AppColors.of( + context) + .loginPrimary, + fontWeight: + FontWeight + .w500, + fontSize: + 12.0, + ), + ), + ), + SchoolInput( + scroll: + _scrollController, + controller: + schoolController, + ), + ], + ), + ), + ), + const Padding( + padding: EdgeInsets.only( + left: 22.0, + right: 22.0, + top: 0.0, + ), + ), + Padding( + padding: + const EdgeInsets.only( + top: 35.0, + left: 22.0, + right: 22.0, + ), + child: Visibility( + visible: _loginState != + LoginState + .inProgress, + replacement: + const Padding( + padding: EdgeInsets + .symmetric( + vertical: + 6.0), + child: + CircularProgressIndicator( + valueColor: + AlwaysStoppedAnimation< + Color>( + Colors + .white), + ), + ), + child: LoginButton( + child: Text( + "login".i18n, + maxLines: 1, + style: + const TextStyle( + fontWeight: + FontWeight + .bold, + fontSize: 20.0, + )), + onPressed: () => + _loginAPI( + context: + context), + ), + ), + ), + ]), + ); + }, + ); + }, child: Text( "login".i18n, style: const TextStyle( @@ -242,163 +519,7 @@ class LoginScreenState extends State { ], ), ), - ) - // Column( - // //login buttons and ui starts here - // mainAxisAlignment: MainAxisAlignment.end, - // crossAxisAlignment: CrossAxisAlignment.end, - // children: [ - // Padding( - // padding: const EdgeInsets.only( - // left: 22.0, - // right: 22.0, - // top: 0.0, - // ), - // child: AutofillGroup( - // child: Column( - // crossAxisAlignment: CrossAxisAlignment.end, - // children: [ - // // username - // Padding( - // padding: - // const EdgeInsets.only(bottom: 6.0), - // child: Row( - // mainAxisAlignment: - // MainAxisAlignment.spaceBetween, - // children: [ - // Expanded( - // child: Text( - // "username".i18n, - // maxLines: 1, - // style: TextStyle( - // color: AppColors.of(context) - // .loginPrimary, - // fontWeight: FontWeight.w500, - // fontSize: 12.0, - // ), - // ), - // ), - // Expanded( - // child: Text( - // "usernameHint".i18n, - // maxLines: 1, - // textAlign: TextAlign.right, - // style: TextStyle( - // color: AppColors.of(context) - // .loginSecondary, - // fontWeight: FontWeight.w500, - // fontSize: 12.0, - // ), - // ), - // ), - // ], - // ), - // ), - // Padding( - // padding: - // const EdgeInsets.only(bottom: 12.0), - // child: LoginInput( - // style: LoginInputStyle.username, - // controller: usernameController, - // ), - // ), - - // // password - // Padding( - // padding: - // const EdgeInsets.only(bottom: 6.0), - // child: Row( - // mainAxisAlignment: - // MainAxisAlignment.spaceBetween, - // children: [ - // Expanded( - // child: Text( - // "password".i18n, - // maxLines: 1, - // style: TextStyle( - // color: AppColors.of(context) - // .loginPrimary, - // fontWeight: FontWeight.w500, - // fontSize: 12.0, - // ), - // ), - // ), - // Expanded( - // child: Text( - // "passwordHint".i18n, - // maxLines: 1, - // textAlign: TextAlign.right, - // style: TextStyle( - // color: AppColors.of(context) - // .loginSecondary, - // fontWeight: FontWeight.w500, - // fontSize: 12.0, - // ), - // ), - // ), - // ], - // ), - // ), - // Padding( - // padding: - // const EdgeInsets.only(bottom: 12.0), - // child: LoginInput( - // style: LoginInputStyle.password, - // controller: passwordController, - // ), - // ), - - // // school - // Padding( - // padding: - // const EdgeInsets.only(bottom: 6.0), - // child: Text( - // "school".i18n, - // maxLines: 1, - // style: TextStyle( - // color: AppColors.of(context) - // .loginPrimary, - // fontWeight: FontWeight.w500, - // fontSize: 12.0, - // ), - // ), - // ), - // SchoolInput( - // scroll: _scrollController, - // controller: schoolController, - // ), - // ], - // ), - // ), - // ), - // Padding( - // padding: const EdgeInsets.only( - // top: 35.0, - // left: 22.0, - // right: 22.0, - // ), - // child: Visibility( - // visible: _loginState != LoginState.inProgress, - // replacement: const Padding( - // padding: EdgeInsets.symmetric(vertical: 6.0), - // child: CircularProgressIndicator( - // valueColor: AlwaysStoppedAnimation( - // Colors.white), - // ), - // ), - // child: LoginButton( - // child: Text("login".i18n, - // maxLines: 1, - // style: const TextStyle( - // fontWeight: FontWeight.bold, - // fontSize: 20.0, - // )), - // onPressed: () => _loginAPI(context: context), - // ), - // ), - // ), - // ], - // ), + ), ], ), From 587c16fb1fcd723ab78d12bb1f8f76ca851fee77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Horv=C3=A1th=20Gergely?= Date: Fri, 3 May 2024 21:30:25 +0200 Subject: [PATCH 09/13] - LiveActivities fix --- refilc/ios/livecard/livecard.swift | 102 ++++++++++++++++------------- 1 file changed, 55 insertions(+), 47 deletions(-) diff --git a/refilc/ios/livecard/livecard.swift b/refilc/ios/livecard/livecard.swift index 7b47058..f5f27e7 100644 --- a/refilc/ios/livecard/livecard.swift +++ b/refilc/ios/livecard/livecard.swift @@ -38,59 +38,67 @@ extension Color { } struct LockScreenLiveActivityView: View { - let context: ActivityViewContext - var body: some View { - HStack(alignment: .center) { - Image(systemName: context.state.icon) - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: CGFloat(30), height: CGFloat(30)) - .padding(.leading, CGFloat(24)) + let context: ActivityViewContext - VStack(alignment: .leading) { + var body: some View { HStack(alignment: .center) { - Text(context.state.index + context.state.title) - .font(.title3) - .bold() + // Ikon + Image(systemName: context.state.icon) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: CGFloat(30), height: CGFloat(30)) + .padding(.leading, CGFloat(24)) - Text(context.state.subtitle) - .font(.subheadline) - .padding(.trailing, 12) - } - - if (context.state.description != "") { - Text(context.state.description) - .font(.subheadline) - } + VStack(alignment: .center) { + // Jelenlegi óra + VStack { + Text(context.state.index + " " + context.state.title) + .font(.body) + .bold() + .multilineTextAlignment(.center) + + Text("Terem: \(context.state.subtitle)") + .italic() + .font(.caption) + } - HStack { - Image(systemName: "arrow.right") - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: CGFloat(8), height: CGFloat(8)) - Text(context.state.nextSubject) - .font(.caption) - Text(context.state.nextRoom) - .font(.caption2) - } - }.padding(15) + // Leírás + if (context.state.description != "") { + Text(context.state.description) + .font(.subheadline) + } - Spacer() - - Text(timerInterval: context.state.date, countsDown: true) - .multilineTextAlignment(.center) - .frame(width: 85) - .font(.title2) - .monospacedDigit() - .padding(.trailing, CGFloat(24)) + // Következő óra + HStack { + Image(systemName: "arrow.right") + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: CGFloat(8), height: CGFloat(8)) + Text(context.state.nextSubject) + .font(.caption) + Text(context.state.nextRoom) + .font(.caption2) + } + .multilineTextAlignment(.center) + } + .padding(15) + + Spacer() + + // Visszaszámláló + Text(timerInterval: context.state.date, countsDown: true) + .multilineTextAlignment(.center) + .frame(width: 85) + .font(.title2) + .monospacedDigit() + .padding(.trailing, CGFloat(24)) + } + .activityBackgroundTint( + context.state.color != "#676767" + ? Color(hex: context.state.color) + : Color.clear + ) } - .activityBackgroundTint( - context.state.color != "#676767" - ? Color(hex: context.state.color) - // Ha nem megy hat nem megy - : Color.clear - ) - } } @available(iOSApplicationExtension 16.2, *) From 96ed4d0b4673e3931b9d92886d08e6ed7897af36 Mon Sep 17 00:00:00 2001 From: Tihanyi Marcell Date: Fri, 3 May 2024 22:40:12 +0200 Subject: [PATCH 10/13] Some iOS updates and Version Bump --- refilc/.gitignore | 30 +++++++++++++++- refilc/build-ipa.sh | 0 refilc/ios/Runner.xcodeproj/project.pbxproj | 36 +++++++++---------- .../xcschemes/xcschememanagement.plist | 2 +- refilc/ios/Runner/Runner.entitlements | 4 +-- refilc/ios/livecard/livecard.entitlements | 4 +-- refilc/pubspec.yaml | 2 +- 7 files changed, 51 insertions(+), 27 deletions(-) mode change 100644 => 100755 refilc/build-ipa.sh diff --git a/refilc/.gitignore b/refilc/.gitignore index f991fd7..4257c18 100644 --- a/refilc/.gitignore +++ b/refilc/.gitignore @@ -46,4 +46,32 @@ app.*.map.json .symlinks/ Pods Podfile.lock -UserInterfaceState.xcuserstate \ No newline at end of file +UserInterfaceState.xcuserstate +**/ios/**/*.mode1v3 +**/ios/**/*.mode2v3 +**/ios/**/*.moved-aside +**/ios/**/*.pbxuser +**/ios/**/*.perspectivev3 +**/ios/**/*sync/ +**/ios/**/.sconsign.dblite +**/ios/**/.tags* +**/ios/**/.vagrant/ +**/ios/**/DerivedData/ +**/ios/**/Icon? +**/ios/**/Pods/ +**/ios/**/.symlinks/ +**/ios/**/profile +**/ios/**/xcuserdata +**/ios/.generated/ +**/ios/Flutter/.last_build_id +**/ios/Flutter/App.framework +**/ios/Flutter/Flutter.framework +**/ios/Flutter/Flutter.podspec +**/ios/Flutter/Generated.xcconfig +**/ios/Flutter/ephemeral +**/ios/Flutter/app.flx +**/ios/Flutter/app.zip +**/ios/Flutter/flutter_assets/ +**/ios/Flutter/flutter_export_environment.sh +**/ios/ServiceDefinitions.json +**/ios/Runner/GeneratedPluginRegistrant.* diff --git a/refilc/build-ipa.sh b/refilc/build-ipa.sh old mode 100644 new mode 100755 diff --git a/refilc/ios/Runner.xcodeproj/project.pbxproj b/refilc/ios/Runner.xcodeproj/project.pbxproj index e4a26c0..2cc04b9 100644 --- a/refilc/ios/Runner.xcodeproj/project.pbxproj +++ b/refilc/ios/Runner.xcodeproj/project.pbxproj @@ -517,8 +517,8 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CURRENT_PROJECT_VERSION = 250; - DEVELOPMENT_TEAM = UT7MSP4GWZ; + CURRENT_PROJECT_VERSION = 255; + DEVELOPMENT_TEAM = 4DKAF249F3; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = reFilc; @@ -528,7 +528,7 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 5.0.0; - PRODUCT_BUNDLE_IDENTIFIER = com.refilcrel.naplo; + PRODUCT_BUNDLE_IDENTIFIER = com.refilc2.naplo; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -549,8 +549,8 @@ CODE_SIGN_ENTITLEMENTS = livecard/livecard.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 250; - DEVELOPMENT_TEAM = UT7MSP4GWZ; + CURRENT_PROJECT_VERSION = 255; + DEVELOPMENT_TEAM = 4DKAF249F3; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = livecard/Info.plist; @@ -566,7 +566,7 @@ MARKETING_VERSION = 5.0.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = com.refilcrel.naplo.livecardpro; + PRODUCT_BUNDLE_IDENTIFIER = com.refilc2.naplo.livecardpro; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; @@ -591,8 +591,8 @@ CODE_SIGN_ENTITLEMENTS = livecard/livecard.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 250; - DEVELOPMENT_TEAM = UT7MSP4GWZ; + CURRENT_PROJECT_VERSION = 255; + DEVELOPMENT_TEAM = 4DKAF249F3; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = livecard/Info.plist; @@ -607,7 +607,7 @@ ); MARKETING_VERSION = 5.0.0; MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = com.refilcrel.naplo.livecardpro; + PRODUCT_BUNDLE_IDENTIFIER = com.refilc2.naplo.livecardpro; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; @@ -631,8 +631,8 @@ CODE_SIGN_ENTITLEMENTS = livecard/livecard.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 250; - DEVELOPMENT_TEAM = UT7MSP4GWZ; + CURRENT_PROJECT_VERSION = 255; + DEVELOPMENT_TEAM = 4DKAF249F3; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = livecard/Info.plist; @@ -647,7 +647,7 @@ ); MARKETING_VERSION = 5.0.0; MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = com.refilcrel.naplo.livecardpro; + PRODUCT_BUNDLE_IDENTIFIER = com.refilc2.naplo.livecardpro; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; @@ -775,8 +775,8 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CURRENT_PROJECT_VERSION = 250; - DEVELOPMENT_TEAM = UT7MSP4GWZ; + CURRENT_PROJECT_VERSION = 255; + DEVELOPMENT_TEAM = 4DKAF249F3; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = reFilc; @@ -786,7 +786,7 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 5.0.0; - PRODUCT_BUNDLE_IDENTIFIER = com.refilcrel.naplo; + PRODUCT_BUNDLE_IDENTIFIER = com.refilc2.naplo; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -803,8 +803,8 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CURRENT_PROJECT_VERSION = 250; - DEVELOPMENT_TEAM = UT7MSP4GWZ; + CURRENT_PROJECT_VERSION = 255; + DEVELOPMENT_TEAM = 4DKAF249F3; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = reFilc; @@ -814,7 +814,7 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 5.0.0; - PRODUCT_BUNDLE_IDENTIFIER = com.refilcrel.naplo; + PRODUCT_BUNDLE_IDENTIFIER = com.refilc2.naplo; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; diff --git a/refilc/ios/Runner.xcodeproj/xcuserdata/tmarccci.xcuserdatad/xcschemes/xcschememanagement.plist b/refilc/ios/Runner.xcodeproj/xcuserdata/tmarccci.xcuserdatad/xcschemes/xcschememanagement.plist index 1c0c029..1970da8 100644 --- a/refilc/ios/Runner.xcodeproj/xcuserdata/tmarccci.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/refilc/ios/Runner.xcodeproj/xcuserdata/tmarccci.xcuserdatad/xcschemes/xcschememanagement.plist @@ -12,7 +12,7 @@ livecard.xcscheme_^#shared#^_ orderHint - 78 + 83 diff --git a/refilc/ios/Runner/Runner.entitlements b/refilc/ios/Runner/Runner.entitlements index 8e1d462..127fede 100644 --- a/refilc/ios/Runner/Runner.entitlements +++ b/refilc/ios/Runner/Runner.entitlements @@ -5,8 +5,6 @@ aps-environment development com.apple.security.application-groups - - group.refilc2.livecard - + diff --git a/refilc/ios/livecard/livecard.entitlements b/refilc/ios/livecard/livecard.entitlements index 8e1d462..127fede 100644 --- a/refilc/ios/livecard/livecard.entitlements +++ b/refilc/ios/livecard/livecard.entitlements @@ -5,8 +5,6 @@ aps-environment development com.apple.security.application-groups - - group.refilc2.livecard - + diff --git a/refilc/pubspec.yaml b/refilc/pubspec.yaml index 39cc8c6..dfc7c68 100644 --- a/refilc/pubspec.yaml +++ b/refilc/pubspec.yaml @@ -3,7 +3,7 @@ description: "Egy nem hivatalos e-KRÉTA kliens, diákoktól diákoknak." homepage: https://refilc.hu publish_to: "none" -version: 5.0.0+253 +version: 5.0.0+255 environment: sdk: ">=2.17.0 <=3.3.2" From 597c50bbf63aaf6391fdbf8766a43e7e15d8372e Mon Sep 17 00:00:00 2001 From: Tihanyi Marcell Date: Fri, 3 May 2024 22:42:11 +0200 Subject: [PATCH 11/13] Prepare for Production --- refilc_mobile_ui/lib/pages/home/home_page.dart | 2 +- .../lib/pages/home/live_card/live_card.dart | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/refilc_mobile_ui/lib/pages/home/home_page.dart b/refilc_mobile_ui/lib/pages/home/home_page.dart index 8939275..8a7218e 100644 --- a/refilc_mobile_ui/lib/pages/home/home_page.dart +++ b/refilc_mobile_ui/lib/pages/home/home_page.dart @@ -214,7 +214,7 @@ class HomePageState extends State with TickerProviderStateMixin { // TODO: REMOVE IN PRODUCTION BUILD!!! // print(_liveCard.currentState); - _liveCard.currentState = LiveCardState.duringLesson; + // _liveCard.currentState = LiveCardState.duringLesson; return Scaffold( body: Stack( diff --git a/refilc_mobile_ui/lib/pages/home/live_card/live_card.dart b/refilc_mobile_ui/lib/pages/home/live_card/live_card.dart index 3cf6f4b..1d36bd2 100644 --- a/refilc_mobile_ui/lib/pages/home/live_card/live_card.dart +++ b/refilc_mobile_ui/lib/pages/home/live_card/live_card.dart @@ -71,19 +71,19 @@ class LiveCardStateA extends State { // test // TODO: REMOVE IN PRODUCTION BUILD!!! - liveCard.currentState = LiveCardState.duringLesson; + /*liveCard.currentState = LiveCardState.duringLesson; liveCard.currentLesson = Lesson( - date: DateTime.now().add(Duration( + date: DateTime.now().add(const Duration( minutes: 30, )), subject: GradeSubject( category: Category(id: 'asd'), id: 'asd', name: 'Matematika'), lessonIndex: '1', teacher: Teacher(id: 'id', name: 'name'), - start: DateTime.now().subtract(Duration( + start: DateTime.now().subtract(const Duration( minutes: 30, )), - end: DateTime.now().add(Duration( + end: DateTime.now().add(const Duration( minutes: 15, )), homeworkId: 'homeworkId', @@ -92,7 +92,7 @@ class LiveCardStateA extends State { room: 'ABC69', groupName: 'groupName', name: 'name', - ); + );*/ liveCard.nextLesson = liveCard.currentLesson; From 14df196f340eb3fbd7768e6aefeb69c138756245 Mon Sep 17 00:00:00 2001 From: Tihanyi Marcell <47987707+TMarccci@users.noreply.github.com> Date: Fri, 3 May 2024 23:26:28 +0200 Subject: [PATCH 12/13] Delete refilc/ios/Runner.xcworkspace directory --- .../Runner.xcworkspace/contents.xcworkspacedata | 10 ---------- .../xcshareddata/IDEWorkspaceChecks.plist | 8 -------- .../xcshareddata/WorkspaceSettings.xcsettings | 8 -------- .../UserInterfaceState.xcuserstate | Bin 19987 -> 0 bytes 4 files changed, 26 deletions(-) delete mode 100644 refilc/ios/Runner.xcworkspace/contents.xcworkspacedata delete mode 100644 refilc/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist delete mode 100644 refilc/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings delete mode 100644 refilc/ios/Runner.xcworkspace/xcuserdata/tmarccci.xcuserdatad/UserInterfaceState.xcuserstate diff --git a/refilc/ios/Runner.xcworkspace/contents.xcworkspacedata b/refilc/ios/Runner.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 21a3cc1..0000000 --- a/refilc/ios/Runner.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - diff --git a/refilc/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/refilc/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d9810..0000000 --- a/refilc/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/refilc/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/refilc/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings deleted file mode 100644 index f9b0d7c..0000000 --- a/refilc/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +++ /dev/null @@ -1,8 +0,0 @@ - - - - - PreviewsEnabled - - - diff --git a/refilc/ios/Runner.xcworkspace/xcuserdata/tmarccci.xcuserdatad/UserInterfaceState.xcuserstate b/refilc/ios/Runner.xcworkspace/xcuserdata/tmarccci.xcuserdatad/UserInterfaceState.xcuserstate deleted file mode 100644 index 45ab2005ad4bcc57c7352580d9f5846172813306..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19987 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 Date: Fri, 3 May 2024 23:40:31 +0200 Subject: [PATCH 13/13] Workspace --- refilc/ios/Runner.xcworkspace/contents.xcworkspacedata | 10 ++++++++++ .../xcshareddata/IDEWorkspaceChecks.plist | 8 ++++++++ .../xcshareddata/WorkspaceSettings.xcsettings | 8 ++++++++ 3 files changed, 26 insertions(+) create mode 100644 refilc/ios/Runner.xcworkspace/contents.xcworkspacedata create mode 100644 refilc/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 refilc/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings diff --git a/refilc/ios/Runner.xcworkspace/contents.xcworkspacedata b/refilc/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..21a3cc1 --- /dev/null +++ b/refilc/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/refilc/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/refilc/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/refilc/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/refilc/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/refilc/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/refilc/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + +