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/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..2cc04b9 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; }; @@ -495,7 +517,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CURRENT_PROJECT_VERSION = 250; + CURRENT_PROJECT_VERSION = 255; DEVELOPMENT_TEAM = 4DKAF249F3; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; @@ -527,7 +549,7 @@ CODE_SIGN_ENTITLEMENTS = livecard/livecard.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 250; + CURRENT_PROJECT_VERSION = 255; DEVELOPMENT_TEAM = 4DKAF249F3; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -535,7 +557,7 @@ 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", @@ -569,7 +591,7 @@ CODE_SIGN_ENTITLEMENTS = livecard/livecard.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 250; + CURRENT_PROJECT_VERSION = 255; DEVELOPMENT_TEAM = 4DKAF249F3; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -577,7 +599,7 @@ 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", @@ -609,7 +631,7 @@ CODE_SIGN_ENTITLEMENTS = livecard/livecard.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 250; + CURRENT_PROJECT_VERSION = 255; DEVELOPMENT_TEAM = 4DKAF249F3; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -617,7 +639,7 @@ 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", @@ -753,7 +775,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CURRENT_PROJECT_VERSION = 250; + CURRENT_PROJECT_VERSION = 255; DEVELOPMENT_TEAM = 4DKAF249F3; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; @@ -781,7 +803,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CURRENT_PROJECT_VERSION = 250; + CURRENT_PROJECT_VERSION = 255; DEVELOPMENT_TEAM = 4DKAF249F3; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; 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.xcworkspace/xcuserdata/tmarccci.xcuserdatad/UserInterfaceState.xcuserstate b/refilc/ios/Runner.xcworkspace/xcuserdata/tmarccci.xcuserdatad/UserInterfaceState.xcuserstate deleted file mode 100644 index 45ab200..0000000 Binary files a/refilc/ios/Runner.xcworkspace/xcuserdata/tmarccci.xcuserdatad/UserInterfaceState.xcuserstate and /dev/null differ 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/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/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.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/ios/livecard/livecard.swift b/refilc/ios/livecard/livecard.swift index 3781b3e..f5f27e7 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,169 +37,161 @@ 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 context: ActivityViewContext - let lesson = LessonData() - - var body: some View { - HStack(alignment: .center) { - Image(systemName: lesson!.icon) - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: CGFloat(30), height: CGFloat(30)) - .padding(.leading, CGFloat(24)) - - VStack(alignment: .leading) { + var body: some View { HStack(alignment: .center) { - Text(lesson!.index + lesson!.title) - .font(.title3) - .bold() - - Text(lesson!.subtitle) - .font(.subheadline) - .padding(.trailing, 12) - } - - if (lesson!.description != "") { - Text(lesson!.description) - .font(.subheadline) - } - - HStack { - Image(systemName: "arrow.right") - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: CGFloat(8), height: CGFloat(8)) - Text(lesson!.nextSubject) - .font(.caption) - Text(lesson!.nextRoom) - .font(.caption2) - } - }.padding(15) - - Spacer() - - Text(timerInterval: lesson!.date, countsDown: true) - .multilineTextAlignment(.center) - .frame(width: 85) - .font(.title2) - .monospacedDigit() - .padding(.trailing, CGFloat(24)) - } - .activityBackgroundTint( - lesson!.color != "#676767" - ? Color(hex: lesson!.color) - // Ha nem megy hat nem megy - : Color.clear - ) - } -} - -@available(iOSApplicationExtension 16.1, *) -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) + // Ikon + Image(systemName: context.state.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)) + .frame(width: CGFloat(30), height: CGFloat(30)) + .padding(.leading, CGFloat(24)) + + 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) + } + + // Leírás + if (context.state.description != "") { + Text(context.state.description) + .font(.subheadline) + } + + // 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) } - ).progressViewStyle(.circular) - }) - } - .keylineTint( - lesson!.color != "#676767" - ? Color(hex: lesson!.color) - : Color.clear - ) + .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 + ) + } +} + +@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 + + /// 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) + } + 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 ebe191e..9cea67f 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 } @@ -691,6 +694,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/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/lib/ui/widgets/lesson/lesson_tile.dart b/refilc/lib/ui/widgets/lesson/lesson_tile.dart index 43a494b..f2041da 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: [ @@ -245,7 +246,7 @@ class LessonTile extends StatelessWidget { ? accent.withOpacity(.15) : Theme.of(context) .colorScheme - .secondary + .tertiary .withOpacity(.15), borderRadius: BorderRadius.circular(10.0), ), @@ -397,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/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" 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, + ), + ), + ], + ), + ), + ), + ], + ), + ), + ), + ], + ), + ); + } +} 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/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..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.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..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 @@ -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(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(const Duration( + minutes: 30, + )), + end: DateTime.now().add(const 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 && @@ -520,7 +525,7 @@ class LiveCardStateA extends State { decoration: BoxDecoration( color: Theme.of(context) .colorScheme - .secondary + .tertiary .withOpacity(.15), borderRadius: BorderRadius.circular(10.0), 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, + ), )); } diff --git a/refilc_mobile_ui/lib/screens/login/login_screen.dart b/refilc_mobile_ui/lib/screens/login/login_screen.dart index 13307f8..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,366 +83,479 @@ 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 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: [ - 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), - ), - ), - ), - ], - ), - // TODO: OLD LOGIN FROM HERE - ], - ), - - 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, + 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), + 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: 300, + width: double.infinity, + decoration: const BoxDecoration( + gradient: LinearGradient( + colors: [Color(0x00DAE4F7), Color(0xFFDAE4F7)], + stops: [0, 0.12], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ), + ), + child: Padding( + padding: EdgeInsets.only(top: 50, bottom: MediaQuery.of(context).viewInsets.bottom), + 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: () { + 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( + fontFamily: 'Montserrat', + fontSize: 20, + fontWeight: FontWeight.w700), + )), + ), + ), + const SizedBox(height: 8), + ], ), - textAlign: TextAlign.center, ), ), - // privacy policy - GestureDetector( - onTap: () => PrivacyView.show(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, + ), + ), + ), + ], ), ), ), 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),