diff --git a/README.md b/README.md
index b36a8ff..19faf71 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,3 @@
-
@@ -78,12 +77,12 @@ export HOS_SDK_HOME="$HOME/dev/command-line-tools/sdk"
- macOS
- Xcode
- CocoaPods
-- iOS Deployment Target 13.0
+- iOS Deployment Target 14.0
### HarmonyOS
- Flutter (OHOS patch) 3.27.5-ohos-1.0.5
-- [Huawei Command Tools 6.1.1 Beta1](https://developer.huawei.com/consumer/cn/download/command-line-tools-for-hmos)
+- [Huawei Command Tools 6.1.1 Beta1](https://developer.huawei.com/consumer/cn/download/command-line-tools-for-hmos)
## License
diff --git a/ios/Podfile b/ios/Podfile
index 2dbf7d7..c2c1dc5 100644
--- a/ios/Podfile
+++ b/ios/Podfile
@@ -1,5 +1,5 @@
# Uncomment this line to define a global platform for your project
-platform :ios, '13.0'
+platform :ios, '14.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index 112a9bf..ec25b3f 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -2,6 +2,8 @@ PODS:
- Flutter (1.0.0)
- flutter_secure_storage (6.0.0):
- Flutter
+ - home_widget (0.0.1):
+ - Flutter
- open_filex (0.0.2):
- Flutter
- package_info_plus (0.4.5):
@@ -21,6 +23,7 @@ PODS:
DEPENDENCIES:
- Flutter (from `Flutter`)
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
+ - home_widget (from `.symlinks/plugins/home_widget/ios`)
- open_filex (from `.symlinks/plugins/open_filex/ios`)
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
@@ -33,6 +36,8 @@ EXTERNAL SOURCES:
:path: Flutter
flutter_secure_storage:
:path: ".symlinks/plugins/flutter_secure_storage/ios"
+ home_widget:
+ :path: ".symlinks/plugins/home_widget/ios"
open_filex:
:path: ".symlinks/plugins/open_filex/ios"
package_info_plus:
@@ -49,6 +54,7 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13
+ home_widget: 54b4f6b36ed8d64cfee594a476225c35c3e45091
open_filex: 432f3cd11432da3e39f47fcc0df2b1603854eff1
package_info_plus: 566e1b7a2f3900e4b0020914ad3fc051dcc95596
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
@@ -56,6 +62,6 @@ SPEC CHECKSUMS:
url_launcher_ios: 694010445543906933d732453a59da0a173ae33d
webview_flutter_wkwebview: 1821ceac936eba6f7984d89a9f3bcb4dea99ebb2
-PODFILE CHECKSUM: 251cb053df7158f337c0712f2ab29f4e0fa474ce
+PODFILE CHECKSUM: e30f02f9d1c72c47bb6344a0a748c9d268180865
COCOAPODS: 1.16.2
diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj
index 2d632d9..ae58d88 100644
--- a/ios/Runner.xcodeproj/project.pbxproj
+++ b/ios/Runner.xcodeproj/project.pbxproj
@@ -25,11 +25,14 @@
5A00101E2F1A000100000001 /* NativeTextFieldGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A00101D2F1A000100000001 /* NativeTextFieldGroup.swift */; };
6BFBE954DB868241E9E9810B /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 052EF181B45C1F8AB028A91F /* Pods_Runner.framework */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
+ 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; };
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 */; };
+ AE1A35252FDAC803007C86E7 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AE1A35242FDAC803007C86E7 /* WidgetKit.framework */; };
+ AE1A35272FDAC803007C86E7 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AE1A35262FDAC803007C86E7 /* SwiftUI.framework */; };
+ AE1A35382FDAC804007C86E7 /* TechPieWidgetExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = AE1A35232FDAC803007C86E7 /* TechPieWidgetExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
AE9BB45FC9E1D531003A2DF5 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 95DFA3F27C43BF6B489182B8 /* Pods_RunnerTests.framework */; };
- 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -40,6 +43,13 @@
remoteGlobalIDString = 97C146ED1CF9000F007C117D;
remoteInfo = Runner;
};
+ AE1A35362FDAC804007C86E7 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 97C146E61CF9000F007C117D /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = AE1A35222FDAC803007C86E7;
+ remoteInfo = TechPieWidgetExtension;
+ };
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
@@ -53,6 +63,17 @@
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
+ AE1A35392FDAC804007C86E7 /* Embed Foundation Extensions */ = {
+ isa = PBXCopyFilesBuildPhase;
+ buildActionMask = 2147483647;
+ dstPath = "";
+ dstSubfolderSpec = 13;
+ files = (
+ AE1A35382FDAC804007C86E7 /* TechPieWidgetExtension.appex in Embed Foundation Extensions */,
+ );
+ name = "Embed Foundation Extensions";
+ runOnlyForDeploymentPostprocessing = 0;
+ };
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
@@ -80,6 +101,7 @@
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 = ""; };
776CD06E7F9DDAC30B1D93AD /* 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 = ""; };
+ 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; };
95DFA3F27C43BF6B489182B8 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; };
@@ -89,12 +111,41 @@
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ AE1A35232FDAC803007C86E7 /* TechPieWidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = TechPieWidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
+ AE1A35242FDAC803007C86E7 /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; };
+ AE1A35262FDAC803007C86E7 /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; };
+ AE1A35412FDAC98F007C86E7 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; };
+ AE1A35422FDAC9B5007C86E7 /* TechPieWidgetExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = TechPieWidgetExtension.entitlements; sourceTree = ""; };
C6ECDBC06E9BDB1CBA58B7DB /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; };
C7BBB453CDB9F73A63A4A327 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; };
D3EC6B66BB17FD8FCFF14373 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; };
- 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; };
/* End PBXFileReference section */
+/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
+ AE1A353D2FDAC804007C86E7 /* Exceptions for "TechPieWidget" folder in "TechPieWidgetExtension" target */ = {
+ isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
+ membershipExceptions = (
+ Info.plist,
+ );
+ target = AE1A35222FDAC803007C86E7 /* TechPieWidgetExtension */;
+ };
+/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
+
+/* Begin PBXFileSystemSynchronizedRootGroup section */
+ AE1A35282FDAC803007C86E7 /* TechPieWidget */ = {
+ isa = PBXFileSystemSynchronizedRootGroup;
+ exceptions = (
+ AE1A353D2FDAC804007C86E7 /* Exceptions for "TechPieWidget" folder in "TechPieWidgetExtension" target */,
+ );
+ explicitFileTypes = {
+ };
+ explicitFolders = (
+ );
+ path = TechPieWidget;
+ sourceTree = "";
+ };
+/* End PBXFileSystemSynchronizedRootGroup section */
+
/* Begin PBXFrameworksBuildPhase section */
37EE0E03704066D35E7DAB45 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
@@ -113,6 +164,15 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ AE1A35202FDAC803007C86E7 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ AE1A35272FDAC803007C86E7 /* SwiftUI.framework in Frameworks */,
+ AE1A35252FDAC803007C86E7 /* WidgetKit.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
@@ -121,6 +181,8 @@
children = (
052EF181B45C1F8AB028A91F /* Pods_Runner.framework */,
95DFA3F27C43BF6B489182B8 /* Pods_RunnerTests.framework */,
+ AE1A35242FDAC803007C86E7 /* WidgetKit.framework */,
+ AE1A35262FDAC803007C86E7 /* SwiftUI.framework */,
);
name = Frameworks;
sourceTree = "";
@@ -181,8 +243,10 @@
97C146E51CF9000F007C117D = {
isa = PBXGroup;
children = (
+ AE1A35422FDAC9B5007C86E7 /* TechPieWidgetExtension.entitlements */,
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
+ AE1A35282FDAC803007C86E7 /* TechPieWidget */,
97C146EF1CF9000F007C117D /* Products */,
331C8082294A63A400263BE5 /* RunnerTests */,
1FD71643C385FC3B24A7C891 /* Pods */,
@@ -195,6 +259,7 @@
children = (
97C146EE1CF9000F007C117D /* Runner.app */,
331C8081294A63A400263BE5 /* RunnerTests.xctest */,
+ AE1A35232FDAC803007C86E7 /* TechPieWidgetExtension.appex */,
);
name = Products;
sourceTree = "";
@@ -202,6 +267,7 @@
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
+ AE1A35412FDAC98F007C86E7 /* Runner.entitlements */,
97C146FA1CF9000F007C117D /* Main.storyboard */,
97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
@@ -238,9 +304,6 @@
productType = "com.apple.product-type.bundle.unit-test";
};
97C146ED1CF9000F007C117D /* Runner */ = {
- packageProductDependencies = (
- 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */,
- );
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
@@ -250,28 +313,51 @@
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
+ AE1A35392FDAC804007C86E7 /* Embed Foundation Extensions */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
1C676109FBCDA2409F8A534A /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
dependencies = (
+ AE1A35372FDAC804007C86E7 /* PBXTargetDependency */,
);
name = Runner;
+ packageProductDependencies = (
+ 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */,
+ );
productName = Runner;
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
productType = "com.apple.product-type.application";
};
+ AE1A35222FDAC803007C86E7 /* TechPieWidgetExtension */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = AE1A353E2FDAC804007C86E7 /* Build configuration list for PBXNativeTarget "TechPieWidgetExtension" */;
+ buildPhases = (
+ AE1A351F2FDAC803007C86E7 /* Sources */,
+ AE1A35202FDAC803007C86E7 /* Frameworks */,
+ AE1A35212FDAC803007C86E7 /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ fileSystemSynchronizedGroups = (
+ AE1A35282FDAC803007C86E7 /* TechPieWidget */,
+ );
+ name = TechPieWidgetExtension;
+ productName = TechPieWidgetExtension;
+ productReference = AE1A35232FDAC803007C86E7 /* TechPieWidgetExtension.appex */;
+ productType = "com.apple.product-type.app-extension";
+ };
/* End PBXNativeTarget section */
/* Begin PBXProject section */
97C146E61CF9000F007C117D /* Project object */ = {
- packageReferences = (
- 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */,
- );
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = YES;
+ LastSwiftUpdateCheck = 1640;
LastUpgradeCheck = 1510;
ORGANIZATIONNAME = "";
TargetAttributes = {
@@ -283,6 +369,9 @@
CreatedOnToolsVersion = 7.3.1;
LastSwiftMigration = 1100;
};
+ AE1A35222FDAC803007C86E7 = {
+ CreatedOnToolsVersion = 16.4;
+ };
};
};
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
@@ -294,12 +383,16 @@
Base,
);
mainGroup = 97C146E51CF9000F007C117D;
+ packageReferences = (
+ 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */,
+ );
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
97C146ED1CF9000F007C117D /* Runner */,
331C8080294A63A400263BE5 /* RunnerTests */,
+ AE1A35222FDAC803007C86E7 /* TechPieWidgetExtension */,
);
};
/* End PBXProject section */
@@ -323,6 +416,13 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ AE1A35212FDAC803007C86E7 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
@@ -451,6 +551,13 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ AE1A351F2FDAC803007C86E7 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
@@ -459,6 +566,11 @@
target = 97C146ED1CF9000F007C117D /* Runner */;
targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */;
};
+ AE1A35372FDAC804007C86E7 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = AE1A35222FDAC803007C86E7 /* TechPieWidgetExtension */;
+ targetProxy = AE1A35362FDAC804007C86E7 /* PBXContainerItemProxy */;
+ };
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
@@ -525,7 +637,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 13.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 14.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
@@ -541,6 +653,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
+ CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
@@ -656,7 +769,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 13.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 14.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
@@ -709,7 +822,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 13.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 14.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
@@ -727,6 +840,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
+ CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
@@ -749,6 +863,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
+ CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
@@ -764,6 +879,123 @@
};
name = Release;
};
+ AE1A353A2FDAC804007C86E7 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CODE_SIGN_ENTITLEMENTS = TechPieWidgetExtension.entitlements;
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ ENABLE_USER_SCRIPT_SANDBOXING = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu17;
+ GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_FILE = TechPieWidget/Info.plist;
+ INFOPLIST_KEY_CFBundleDisplayName = TechPieWidget;
+ INFOPLIST_KEY_NSHumanReadableCopyright = "";
+ IPHONEOS_DEPLOYMENT_TARGET = 14.0;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@executable_path/../../Frameworks",
+ );
+ LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
+ MARKETING_VERSION = 1.0;
+ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
+ MTL_FAST_MATH = YES;
+ PRODUCT_BUNDLE_IDENTIFIER = com.example.techpie.TechPieWidget;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SKIP_INSTALL = YES;
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Debug;
+ };
+ AE1A353B2FDAC804007C86E7 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CODE_SIGN_ENTITLEMENTS = TechPieWidgetExtension.entitlements;
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ ENABLE_USER_SCRIPT_SANDBOXING = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu17;
+ GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_FILE = TechPieWidget/Info.plist;
+ INFOPLIST_KEY_CFBundleDisplayName = TechPieWidget;
+ INFOPLIST_KEY_NSHumanReadableCopyright = "";
+ IPHONEOS_DEPLOYMENT_TARGET = 14.0;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@executable_path/../../Frameworks",
+ );
+ LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
+ MARKETING_VERSION = 1.0;
+ MTL_FAST_MATH = YES;
+ PRODUCT_BUNDLE_IDENTIFIER = com.example.techpie.TechPieWidget;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SKIP_INSTALL = YES;
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Release;
+ };
+ AE1A353C2FDAC804007C86E7 /* Profile */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CODE_SIGN_ENTITLEMENTS = TechPieWidgetExtension.entitlements;
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ ENABLE_USER_SCRIPT_SANDBOXING = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu17;
+ GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_FILE = TechPieWidget/Info.plist;
+ INFOPLIST_KEY_CFBundleDisplayName = TechPieWidget;
+ INFOPLIST_KEY_NSHumanReadableCopyright = "";
+ IPHONEOS_DEPLOYMENT_TARGET = 14.0;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@executable_path/../../Frameworks",
+ );
+ LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
+ MARKETING_VERSION = 1.0;
+ MTL_FAST_MATH = YES;
+ PRODUCT_BUNDLE_IDENTIFIER = com.example.techpie.TechPieWidget;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SKIP_INSTALL = YES;
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Profile;
+ };
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
@@ -797,13 +1029,25 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
+ AE1A353E2FDAC804007C86E7 /* Build configuration list for PBXNativeTarget "TechPieWidgetExtension" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ AE1A353A2FDAC804007C86E7 /* Debug */,
+ AE1A353B2FDAC804007C86E7 /* Release */,
+ AE1A353C2FDAC804007C86E7 /* Profile */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
/* End XCConfigurationList section */
+
/* Begin XCLocalSwiftPackageReference section */
- 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = {
+ 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */ = {
isa = XCLocalSwiftPackageReference;
relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage;
};
/* End XCLocalSwiftPackageReference section */
+
/* Begin XCSwiftPackageProductDependency section */
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = {
isa = XCSwiftPackageProductDependency;
diff --git a/ios/Runner/Runner.entitlements b/ios/Runner/Runner.entitlements
new file mode 100644
index 0000000..fbb0586
--- /dev/null
+++ b/ios/Runner/Runner.entitlements
@@ -0,0 +1,10 @@
+
+
+
+
+ com.apple.security.application-groups
+
+ group.com.example.techpie
+
+
+
diff --git a/ios/TechPieWidget/Assets.xcassets/AccentColor.colorset/Contents.json b/ios/TechPieWidget/Assets.xcassets/AccentColor.colorset/Contents.json
new file mode 100644
index 0000000..eb87897
--- /dev/null
+++ b/ios/TechPieWidget/Assets.xcassets/AccentColor.colorset/Contents.json
@@ -0,0 +1,11 @@
+{
+ "colors" : [
+ {
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/ios/TechPieWidget/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios/TechPieWidget/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 0000000..2305880
--- /dev/null
+++ b/ios/TechPieWidget/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,35 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "platform" : "ios",
+ "size" : "1024x1024"
+ },
+ {
+ "appearances" : [
+ {
+ "appearance" : "luminosity",
+ "value" : "dark"
+ }
+ ],
+ "idiom" : "universal",
+ "platform" : "ios",
+ "size" : "1024x1024"
+ },
+ {
+ "appearances" : [
+ {
+ "appearance" : "luminosity",
+ "value" : "tinted"
+ }
+ ],
+ "idiom" : "universal",
+ "platform" : "ios",
+ "size" : "1024x1024"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/ios/TechPieWidget/Assets.xcassets/Contents.json b/ios/TechPieWidget/Assets.xcassets/Contents.json
new file mode 100644
index 0000000..73c0059
--- /dev/null
+++ b/ios/TechPieWidget/Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/ios/TechPieWidget/Assets.xcassets/WidgetBackground.colorset/Contents.json b/ios/TechPieWidget/Assets.xcassets/WidgetBackground.colorset/Contents.json
new file mode 100644
index 0000000..eb87897
--- /dev/null
+++ b/ios/TechPieWidget/Assets.xcassets/WidgetBackground.colorset/Contents.json
@@ -0,0 +1,11 @@
+{
+ "colors" : [
+ {
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/ios/TechPieWidget/Info.plist b/ios/TechPieWidget/Info.plist
new file mode 100644
index 0000000..0f118fb
--- /dev/null
+++ b/ios/TechPieWidget/Info.plist
@@ -0,0 +1,11 @@
+
+
+
+
+ NSExtension
+
+ NSExtensionPointIdentifier
+ com.apple.widgetkit-extension
+
+
+
diff --git a/ios/TechPieWidget/TechPieWidget.swift b/ios/TechPieWidget/TechPieWidget.swift
new file mode 100644
index 0000000..7704427
--- /dev/null
+++ b/ios/TechPieWidget/TechPieWidget.swift
@@ -0,0 +1,215 @@
+import WidgetKit
+import SwiftUI
+
+struct Provider: TimelineProvider {
+ func placeholder(in context: Context) -> SimpleEntry {
+ SimpleEntry(date: Date(), courses: [], assignments: [])
+ }
+
+ func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) {
+ let entry = SimpleEntry(date: Date(),
+ courses: WidgetDataManager.shared.courses,
+ assignments: WidgetDataManager.shared.assignments)
+ completion(entry)
+ }
+
+ func getTimeline(in context: Context, completion: @escaping (Timeline) -> ()) {
+ let entry = SimpleEntry(date: Date(),
+ courses: WidgetDataManager.shared.courses,
+ assignments: WidgetDataManager.shared.assignments)
+
+ // Update widget occasionally or when told by Flutter
+ let nextUpdate = Calendar.current.date(byAdding: .minute, value: 30, to: Date()) ?? Date().addingTimeInterval(30 * 60)
+ let timeline = Timeline(entries: [entry], policy: .after(nextUpdate))
+ completion(timeline)
+ }
+}
+
+struct SimpleEntry: TimelineEntry {
+ let date: Date
+ let courses: [Course]
+ let assignments: [Assignment]
+}
+
+struct TechPieWidgetEntryView : View {
+ var entry: Provider.Entry
+ @Environment(\.widgetFamily) var family
+
+ var body: some View {
+ ZStack {
+ VStack(alignment: .leading, spacing: 10) {
+ if family == .systemSmall {
+ smallView
+ } else {
+ mediumView
+ }
+ }
+ .padding()
+ }
+ }
+
+ var smallView: some View {
+ VStack(alignment: .leading, spacing: 8) {
+ HStack {
+ Image(systemName: "book.fill")
+ .foregroundColor(.blue)
+ Text("Next Class")
+ .font(.headline)
+ }
+
+ if let firstClass = entry.courses.first {
+ Text(firstClass.name)
+ .font(.subheadline)
+ .fontWeight(.bold)
+ .lineLimit(2)
+ Text("\(firstClass.startTime) - \(firstClass.endTime)")
+ .font(.caption)
+ .foregroundColor(.secondary)
+ Text(firstClass.location)
+ .font(.caption)
+ .foregroundColor(.secondary)
+ } else {
+ Text("No more classes today! 🎉")
+ .font(.subheadline)
+ .foregroundColor(.secondary)
+ }
+ }
+ }
+
+ var mediumView: some View {
+ HStack(alignment: .top, spacing: 16) {
+ // Left Side: Schedule
+ VStack(alignment: .leading, spacing: 8) {
+ HStack {
+ Image(systemName: "calendar")
+ .foregroundColor(.blue)
+ Text("Today's Classes")
+ .font(.headline)
+ }
+
+ if entry.courses.isEmpty {
+ Text("Free day! 🎉")
+ .font(.subheadline)
+ .foregroundColor(.secondary)
+ } else {
+ ForEach(entry.courses.prefix(2)) { course in
+ VStack(alignment: .leading, spacing: 2) {
+ Text(course.name)
+ .font(.subheadline)
+ .fontWeight(.medium)
+ .lineLimit(1)
+ Text("\(course.startTime) | \(course.location)")
+ .font(.caption2)
+ .foregroundColor(.secondary)
+ }
+ }
+ }
+ }
+ .frame(maxWidth: .infinity, alignment: .leading)
+
+ Divider()
+
+ // Right Side: Assignments
+ VStack(alignment: .leading, spacing: 8) {
+ HStack {
+ Image(systemName: "doc.text.fill")
+ .foregroundColor(.orange)
+ Text("Pending Work")
+ .font(.headline)
+ }
+
+ if entry.assignments.isEmpty {
+ Text("All caught up! ✅")
+ .font(.subheadline)
+ .foregroundColor(.secondary)
+ } else {
+ ForEach(entry.assignments.prefix(2)) { assignment in
+ VStack(alignment: .leading, spacing: 2) {
+ Text(assignment.title)
+ .font(.subheadline)
+ .fontWeight(.medium)
+ .lineLimit(1)
+ Text(assignment.course)
+ .font(.caption2)
+ .foregroundColor(.secondary)
+ .lineLimit(1)
+ }
+ }
+ }
+ }
+ .frame(maxWidth: .infinity, alignment: .leading)
+ }
+ }
+}
+
+struct TechPieWidget: Widget {
+ let kind: String = "TechPieWidget"
+
+ var body: some WidgetConfiguration {
+ StaticConfiguration(kind: kind, provider: Provider()) { entry in
+ if #available(iOS 17.0, *) {
+ TechPieWidgetEntryView(entry: entry)
+ .containerBackground(Color(UIColor.systemBackground), for: .widget)
+ } else {
+ TechPieWidgetEntryView(entry: entry)
+ .background(Color(UIColor.systemBackground))
+ }
+ }
+ .configurationDisplayName("TechPie Tracker")
+ .description("Track your daily schedule and pending assignments.")
+ .supportedFamilies([.systemSmall, .systemMedium])
+ }
+}
+
+// MARK: - Data Models
+
+struct Course: Codable, Identifiable {
+ var id: String { name + startTime }
+ let name: String
+ let location: String
+ let startTime: String
+ let endTime: String
+}
+
+struct Assignment: Codable, Identifiable {
+ var id: String { title + course }
+ let title: String
+ let course: String
+ let due: String
+}
+
+class WidgetDataManager {
+ static let shared = WidgetDataManager()
+ // IMPORTANT: Match this with your App Group ID configured in Xcode
+ let appGroupID = "group.com.example.techpie"
+
+ var courses: [Course] {
+ guard let defaults = UserDefaults(suiteName: appGroupID),
+ let dataString = defaults.string(forKey: "schedule_data"),
+ let data = dataString.data(using: .utf8) else {
+ return []
+ }
+
+ do {
+ return try JSONDecoder().decode([Course].self, from: data)
+ } catch {
+ print("Error decoding courses: \(error)")
+ return []
+ }
+ }
+
+ var assignments: [Assignment] {
+ guard let defaults = UserDefaults(suiteName: appGroupID),
+ let dataString = defaults.string(forKey: "assignment_data"),
+ let data = dataString.data(using: .utf8) else {
+ return []
+ }
+
+ do {
+ return try JSONDecoder().decode([Assignment].self, from: data)
+ } catch {
+ print("Error decoding assignments: \(error)")
+ return []
+ }
+ }
+}
diff --git a/ios/TechPieWidget/TechPieWidgetBundle.swift b/ios/TechPieWidget/TechPieWidgetBundle.swift
new file mode 100644
index 0000000..64449a7
--- /dev/null
+++ b/ios/TechPieWidget/TechPieWidgetBundle.swift
@@ -0,0 +1,16 @@
+//
+// TechPieWidgetBundle.swift
+// TechPieWidget
+//
+// Created by Henry Li on 2026/6/11.
+//
+
+import WidgetKit
+import SwiftUI
+
+@main
+struct TechPieWidgetBundle: WidgetBundle {
+ var body: some Widget {
+ TechPieWidget()
+ }
+}
diff --git a/ios/TechPieWidgetExtension.entitlements b/ios/TechPieWidgetExtension.entitlements
new file mode 100644
index 0000000..fbb0586
--- /dev/null
+++ b/ios/TechPieWidgetExtension.entitlements
@@ -0,0 +1,10 @@
+
+
+
+
+ com.apple.security.application-groups
+
+ group.com.example.techpie
+
+
+
diff --git a/lib/services/assignment_service.dart b/lib/services/assignment_service.dart
index e6430ef..39f23f9 100644
--- a/lib/services/assignment_service.dart
+++ b/lib/services/assignment_service.dart
@@ -14,6 +14,7 @@ import 'http_client.dart';
import 'schedule_service.dart';
import 'storage_service.dart';
import 'third_party_auth_service.dart';
+import 'widget_sync_service.dart';
class AssignmentService extends ChangeNotifier {
final StorageService _storage;
@@ -91,6 +92,7 @@ class AssignmentService extends ChangeNotifier {
_error = null;
await _storage.clearCachedAssignments();
notifyListeners();
+ _syncWidget();
}
/// Hydrate from local cache so the UI doesn't flash empty on app start
@@ -100,17 +102,21 @@ class AssignmentService extends ChangeNotifier {
final raw = _storage.loadCachedAssignments();
if (raw.isEmpty) {
notifyListeners();
+ _syncWidget();
return;
}
_assignments = raw.map((e) => Assignment.fromJson(e)).toList()
..sort((a, b) => a.due.compareTo(b.due));
+ _syncWidget();
notifyListeners();
}
// -- Override mutators --
- Future _persistOverrides() =>
- _storage.saveAssignmentOverrides(_overrides);
+ Future _persistOverrides() async {
+ await _storage.saveAssignmentOverrides(_overrides);
+ _syncWidget();
+ }
Future setCompleted(Assignment a, bool completed) async {
_overrides.completed[AssignmentOverrides.keyFor(a)] = completed;
@@ -221,6 +227,7 @@ class AssignmentService extends ChangeNotifier {
await _storage.saveCachedAssignments(
merged.map((a) => a.toJson()).toList(),
);
+ _syncWidget();
} catch (e) {
_error = '同步失败,请检查网络或稍后重试';
} finally {
@@ -289,6 +296,7 @@ class AssignmentService extends ChangeNotifier {
await _storage.saveCachedAssignments(
merged.map((a) => a.toJson()).toList(),
);
+ _syncWidget();
} catch (e) {
_error = e.toString();
}
@@ -567,4 +575,11 @@ class AssignmentService extends ChangeNotifier {
? (baseCookies.isNotEmpty ? '$baseCookies; CASTGC=$tgc' : 'CASTGC=$tgc')
: baseCookies;
}
+
+ void _syncWidget() {
+ unawaited(
+ WidgetSyncService.syncAssignments(_assignments, _overrides)
+ .catchError((_) {}),
+ );
+ }
}
diff --git a/lib/services/schedule_service.dart b/lib/services/schedule_service.dart
index 8219edf..d3fae9e 100644
--- a/lib/services/schedule_service.dart
+++ b/lib/services/schedule_service.dart
@@ -1,3 +1,4 @@
+import 'dart:async';
import 'dart:convert';
import 'package:flutter/foundation.dart';
@@ -8,6 +9,7 @@ import 'api_base_url.dart';
import 'auth_service.dart';
import 'http_client.dart';
import 'storage_service.dart';
+import 'widget_sync_service.dart';
class ScheduleService extends ChangeNotifier {
final StorageService _storage;
@@ -62,6 +64,7 @@ class ScheduleService extends ChangeNotifier {
_courseTable = _storage.loadCourseTable(_selectedSemesterId!);
}
_termBegin = _storage.loadTermBegin(_selectedSemesterId ?? '');
+ _syncWidget();
notifyListeners();
}
@@ -128,6 +131,7 @@ class ScheduleService extends ChangeNotifier {
data['data'] as Map,
);
await _storage.saveCourseTable(semesterId, _courseTable!);
+ _syncWidget();
notifyListeners();
}
@@ -240,4 +244,13 @@ class ScheduleService extends ChangeNotifier {
return resp;
}
+
+ void _syncWidget() {
+ if (_courseTable != null) {
+ unawaited(
+ WidgetSyncService.syncSchedule(_courseTable!, currentWeek())
+ .catchError((_) {}),
+ );
+ }
+ }
}
diff --git a/lib/services/widget_sync_service.dart b/lib/services/widget_sync_service.dart
new file mode 100644
index 0000000..8e356f0
--- /dev/null
+++ b/lib/services/widget_sync_service.dart
@@ -0,0 +1,107 @@
+import 'dart:convert';
+import 'dart:io' show Platform;
+
+import 'package:flutter/foundation.dart';
+import 'package:home_widget/home_widget.dart';
+
+import '../models/assignment.dart';
+import '../models/assignment_overrides.dart';
+import '../models/course.dart';
+import '../models/course_table.dart';
+
+class WidgetSyncService {
+ static const String _groupId = 'group.com.example.techpie';
+ // IMPORTANT: Ensure this matches the App Group identifier configured in Xcode
+
+ static Future syncSchedule(CourseTable table, int currentWeek) async {
+ if (kIsWeb || (!Platform.isIOS && !Platform.isAndroid)) return;
+
+ try {
+ final now = DateTime.now();
+ final weekday = now.weekday; // 1 = Mon, 7 = Sun
+
+ // Convert to display courses, filtering for current week
+ final displayCourses = eamsToDisplayCourses(table.courses, currentWeek);
+
+ // Get today's active courses, sorted by period
+ final todayCourses = displayCourses
+ .where((c) => c.dayOfWeek == weekday && !c.isGhost)
+ .toList();
+ todayCourses.sort((a, b) => a.startPeriod.compareTo(b.startPeriod));
+
+ final periods = table.periods.isNotEmpty
+ ? table.periods.map((p) => p.toPeriod()).toList()
+ : defaultPeriods;
+
+ final coursesJson = todayCourses.map((c) {
+ final startTime = periods
+ .firstWhere(
+ (p) => p.number == c.startPeriod,
+ orElse: () => const Period(number: 0, startTime: '', endTime: ''),
+ )
+ .startTime;
+ final endTime = periods
+ .firstWhere(
+ (p) => p.number == c.endPeriod,
+ orElse: () => const Period(number: 0, startTime: '', endTime: ''),
+ )
+ .endTime;
+ return {
+ 'name': c.name,
+ 'location': c.location,
+ 'startTime': startTime,
+ 'endTime': endTime,
+ };
+ }).toList();
+
+ await HomeWidget.setAppGroupId(_groupId);
+ await HomeWidget.saveWidgetData('schedule_data', jsonEncode(coursesJson));
+ await HomeWidget.updateWidget(
+ name: 'TechPieWidget',
+ iOSName: 'TechPieWidget',
+ );
+ } catch (e) {
+ // Swallow platform-channel errors to avoid unhandled async exceptions
+ }
+ }
+
+ static Future syncAssignments(
+ List assignments,
+ AssignmentOverrides overrides,
+ ) async {
+ if (kIsWeb || (!Platform.isIOS && !Platform.isAndroid)) return;
+
+ try {
+ final now = DateTime.now();
+
+ final pending = assignments.where((a) {
+ if (overrides.isHidden(a)) return false;
+ if (overrides.effectiveCompleted(a)) return false;
+ return a.due.isAfter(now);
+ }).toList();
+
+ pending.sort((a, b) => a.due.compareTo(b.due));
+ final topPending = pending.take(3).toList();
+
+ final assignmentsJson = topPending.map((a) {
+ return {
+ 'title': a.title,
+ 'course': a.course,
+ 'due': a.due.toIso8601String(),
+ };
+ }).toList();
+
+ await HomeWidget.setAppGroupId(_groupId);
+ await HomeWidget.saveWidgetData(
+ 'assignment_data',
+ jsonEncode(assignmentsJson),
+ );
+ await HomeWidget.updateWidget(
+ name: 'TechPieWidget',
+ iOSName: 'TechPieWidget',
+ );
+ } catch (e) {
+ // Swallow platform-channel errors to avoid unhandled async exceptions
+ }
+ }
+}
diff --git a/pubspec.lock b/pubspec.lock
index c6b48fa..baac116 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -217,6 +217,14 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.2.1"
+ home_widget:
+ dependency: "direct main"
+ description:
+ name: home_widget
+ sha256: "7a32f7d6a3afd542126fb0004acba939a41ee57d874172926212774fbce684d3"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "0.9.2"
http:
dependency: "direct main"
description:
@@ -671,7 +679,7 @@ packages:
description:
path: "packages/webview_flutter/webview_flutter_android"
ref: "br_webview_flutter-v4.13.0_ohos"
- resolved-ref: "8d70938aa190eb7d7cd4d66f0d16fddb3c2d03ba"
+ resolved-ref: cd84e4ca392f666bc728c32c49fb328127058826
url: "https://gitcode.com/openharmony-tpc/flutter_packages.git"
source: git
version: "4.7.0"
@@ -680,7 +688,7 @@ packages:
description:
path: "packages/webview_flutter/webview_flutter_ohos"
ref: "br_webview_flutter-v4.13.0_ohos"
- resolved-ref: "8d70938aa190eb7d7cd4d66f0d16fddb3c2d03ba"
+ resolved-ref: cd84e4ca392f666bc728c32c49fb328127058826
url: "https://gitcode.com/openharmony-tpc/flutter_packages.git"
source: git
version: "4.7.0"
@@ -698,7 +706,7 @@ packages:
description:
path: "packages/webview_flutter/webview_flutter_wkwebview"
ref: "br_webview_flutter-v4.13.0_ohos"
- resolved-ref: "8d70938aa190eb7d7cd4d66f0d16fddb3c2d03ba"
+ resolved-ref: cd84e4ca392f666bc728c32c49fb328127058826
url: "https://gitcode.com/openharmony-tpc/flutter_packages.git"
source: git
version: "3.22.0"
diff --git a/pubspec.yaml b/pubspec.yaml
index 4cba3ee..d67fa2f 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -41,6 +41,7 @@ dependencies:
url: https://gitcode.com/openharmony-sig/fluttertpc_flutter_secure_storage.git
path: flutter_secure_storage_ohos
ref: br_v9.2.2_ohos
+ home_widget: 0.9.2
http: ^1.4.0
intl: ^0.20.2
open_filex: ^4.5.0