@tduncan / Cortana / commits / d98fd52

feat: add iOS support for on-device models and implement entrance animations using cue library

abdulmominsakib committed Jun 6, 2026 at 11:49 UTC d98fd52db4a288553ae28e9284f342a6f1510cb5
14 files changed +1010 -793
ios/Runner.xcodeproj/project.pbxproj
+69 -65
@@ -10,13 +10,13 @@
10 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
11 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
12 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
13 - 593F905FB75129A384E40AED /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2F6AFCBB8B5168921CF0CF27 /* Pods_Runner.framework */; };
13 + 4D35725BDD0DD5019A931DB2 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A032784C9BB81D3315A79076 /* Pods_RunnerTests.framework */; };
14 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
15 7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */; };
16 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
17 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
18 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
19 - E426CC5FB0C84B59EA4C9455 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F3F7E264AC2540C04D71A2E0 /* Pods_RunnerTests.framework */; };
19 + AD5D2C9F4682DFDBC0E7E5C1 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9B309EF20AB394872F65BD5E /* Pods_Runner.framework */; };
20 /* End PBXBuildFile section */
21
22 /* Begin PBXContainerItemProxy section */
@@ -45,9 +45,7 @@
45 /* Begin PBXFileReference section */
46 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
47 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
48 - 1E28B77AE84C180CE237C2CF /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
49 - 2910D1CEAE7EC5D481C55D03 /* 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 = "<group>"; };
50 - 2F6AFCBB8B5168921CF0CF27 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
48 + 2E58D4AE73D0962B3323FB68 /* 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 = "<group>"; };
49 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
50 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
51 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
@@ -55,6 +53,8 @@
53 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
54 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
55 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
56 + 7CC868A42FD2D68B0007F1A7 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = "<group>"; };
57 + 86D4F01C5ED86C95435B62E4 /* 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 = "<group>"; };
58 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
59 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
60 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -62,11 +62,12 @@
62 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
63 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
64 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
65 - AA0C333EA96851D712658FE5 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
66 - AE6E886B14A635226D09D311 /* 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 = "<group>"; };
67 - C7571B6B9D4A156690DB988C /* 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 = "<group>"; };
68 - F0DD11FD888D55FAA2DAFD54 /* 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 = "<group>"; };
69 - F3F7E264AC2540C04D71A2E0 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
65 + 9B309EF20AB394872F65BD5E /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
66 + A032784C9BB81D3315A79076 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
67 + B01A7AB948A43BF5CF62AC78 /* 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 = "<group>"; };
68 + D7F09850547E66B715D2AE1C /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
69 + EB56E4A51F0E4D7E0193C01F /* 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 = "<group>"; };
70 + F63A76ABB8D4E03F11C4084E /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
71 /* End PBXFileReference section */
72
73 /* Begin PBXFrameworksBuildPhase section */
@@ -74,7 +75,7 @@
75 isa = PBXFrameworksBuildPhase;
76 buildActionMask = 2147483647;
77 files = (
77 - 593F905FB75129A384E40AED /* Pods_Runner.framework in Frameworks */,
78 + AD5D2C9F4682DFDBC0E7E5C1 /* Pods_Runner.framework in Frameworks */,
79 );
80 runOnlyForDeploymentPostprocessing = 0;
81 };
@@ -82,13 +83,22 @@
83 isa = PBXFrameworksBuildPhase;
84 buildActionMask = 2147483647;
85 files = (
85 - E426CC5FB0C84B59EA4C9455 /* Pods_RunnerTests.framework in Frameworks */,
86 + 4D35725BDD0DD5019A931DB2 /* Pods_RunnerTests.framework in Frameworks */,
87 );
88 runOnlyForDeploymentPostprocessing = 0;
89 };
90 /* End PBXFrameworksBuildPhase section */
91
92 /* Begin PBXGroup section */
93 + 1149248C99B08ECDAC23FF9D /* Frameworks */ = {
94 + isa = PBXGroup;
95 + children = (
96 + 9B309EF20AB394872F65BD5E /* Pods_Runner.framework */,
97 + A032784C9BB81D3315A79076 /* Pods_RunnerTests.framework */,
98 + );
99 + name = Frameworks;
100 + sourceTree = "<group>";
101 + };
102 331C8082294A63A400263BE5 /* RunnerTests */ = {
103 isa = PBXGroup;
104 children = (
@@ -116,7 +126,7 @@
126 97C146EF1CF9000F007C117D /* Products */,
127 331C8082294A63A400263BE5 /* RunnerTests */,
128 B5F0F4932A555741EAC185FD /* Pods */,
119 - C9B81A28CEFDE833459BBAE4 /* Frameworks */,
129 + 1149248C99B08ECDAC23FF9D /* Frameworks */,
130 );
131 sourceTree = "<group>";
132 };
@@ -132,6 +142,7 @@
142 97C146F01CF9000F007C117D /* Runner */ = {
143 isa = PBXGroup;
144 children = (
145 + 7CC868A42FD2D68B0007F1A7 /* Runner.entitlements */,
146 97C146FA1CF9000F007C117D /* Main.storyboard */,
147 97C146FD1CF9000F007C117D /* Assets.xcassets */,
148 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
@@ -148,24 +159,14 @@
159 B5F0F4932A555741EAC185FD /* Pods */ = {
160 isa = PBXGroup;
161 children = (
151 - F0DD11FD888D55FAA2DAFD54 /* Pods-Runner.debug.xcconfig */,
152 - C7571B6B9D4A156690DB988C /* Pods-Runner.release.xcconfig */,
153 - AE6E886B14A635226D09D311 /* Pods-Runner.profile.xcconfig */,
154 - 1E28B77AE84C180CE237C2CF /* Pods-RunnerTests.debug.xcconfig */,
155 - AA0C333EA96851D712658FE5 /* Pods-RunnerTests.release.xcconfig */,
156 - 2910D1CEAE7EC5D481C55D03 /* Pods-RunnerTests.profile.xcconfig */,
157 - );
158 - name = Pods;
159 - path = Pods;
160 - sourceTree = "<group>";
161 - };
162 - C9B81A28CEFDE833459BBAE4 /* Frameworks */ = {
163 - isa = PBXGroup;
164 - children = (
165 - 2F6AFCBB8B5168921CF0CF27 /* Pods_Runner.framework */,
166 - F3F7E264AC2540C04D71A2E0 /* Pods_RunnerTests.framework */,
162 + EB56E4A51F0E4D7E0193C01F /* Pods-Runner.debug.xcconfig */,
163 + 2E58D4AE73D0962B3323FB68 /* Pods-Runner.release.xcconfig */,
164 + B01A7AB948A43BF5CF62AC78 /* Pods-Runner.profile.xcconfig */,
165 + F63A76ABB8D4E03F11C4084E /* Pods-RunnerTests.debug.xcconfig */,
166 + D7F09850547E66B715D2AE1C /* Pods-RunnerTests.release.xcconfig */,
167 + 86D4F01C5ED86C95435B62E4 /* Pods-RunnerTests.profile.xcconfig */,
168 );
168 - name = Frameworks;
169 + path = Pods;
170 sourceTree = "<group>";
171 };
172 /* End PBXGroup section */
@@ -175,7 +176,7 @@
176 isa = PBXNativeTarget;
177 buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
178 buildPhases = (
178 - E66B1B93294383A7AD82BEE0 /* [CP] Check Pods Manifest.lock */,
179 + D2249D2FD0465B2C76420590 /* [CP] Check Pods Manifest.lock */,
180 331C807D294A63A400263BE5 /* Sources */,
181 331C807F294A63A400263BE5 /* Resources */,
182 9AA24F4969F6FB2D761614D7 /* Frameworks */,
@@ -194,15 +195,15 @@
195 isa = PBXNativeTarget;
196 buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
197 buildPhases = (
197 - AF1553571D404C2B52FA3EE8 /* [CP] Check Pods Manifest.lock */,
198 + FE21A8B6035F35A6E6A38E92 /* [CP] Check Pods Manifest.lock */,
199 9740EEB61CF901F6004384FC /* Run Script */,
200 97C146EA1CF9000F007C117D /* Sources */,
201 97C146EB1CF9000F007C117D /* Frameworks */,
202 97C146EC1CF9000F007C117D /* Resources */,
203 9705A1C41CF9048500538489 /* Embed Frameworks */,
204 3B06AD1E1E4923F5004D2608 /* Thin Binary */,
204 - 6F568F0CE167E332F9CAF0D6 /* [CP] Embed Pods Frameworks */,
205 - 91D3378898CA91FDC6BCE7FE /* [CP] Copy Pods Resources */,
205 + 1E56B4EBA88B451F7B385BA9 /* [CP] Embed Pods Frameworks */,
206 + D0F9B4752B25C3F455EAAB34 /* [CP] Copy Pods Resources */,
207 );
208 buildRules = (
209 );
@@ -274,6 +275,23 @@
275 /* End PBXResourcesBuildPhase section */
276
277 /* Begin PBXShellScriptBuildPhase section */
278 + 1E56B4EBA88B451F7B385BA9 /* [CP] Embed Pods Frameworks */ = {
279 + isa = PBXShellScriptBuildPhase;
280 + buildActionMask = 2147483647;
281 + files = (
282 + );
283 + inputFileListPaths = (
284 + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
285 + );
286 + name = "[CP] Embed Pods Frameworks";
287 + outputFileListPaths = (
288 + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
289 + );
290 + runOnlyForDeploymentPostprocessing = 0;
291 + shellPath = /bin/sh;
292 + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
293 + showEnvVarsInLog = 0;
294 + };
295 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
296 isa = PBXShellScriptBuildPhase;
297 alwaysOutOfDate = 1;
@@ -290,24 +308,22 @@
308 shellPath = /bin/sh;
309 shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
310 };
293 - 6F568F0CE167E332F9CAF0D6 /* [CP] Embed Pods Frameworks */ = {
311 + 9740EEB61CF901F6004384FC /* Run Script */ = {
312 isa = PBXShellScriptBuildPhase;
313 + alwaysOutOfDate = 1;
314 buildActionMask = 2147483647;
315 files = (
316 );
298 - inputFileListPaths = (
299 - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
317 + inputPaths = (
318 );
301 - name = "[CP] Embed Pods Frameworks";
302 - outputFileListPaths = (
303 - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
319 + name = "Run Script";
320 + outputPaths = (
321 );
322 runOnlyForDeploymentPostprocessing = 0;
323 shellPath = /bin/sh;
307 - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
308 - showEnvVarsInLog = 0;
324 + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
325 };
310 - 91D3378898CA91FDC6BCE7FE /* [CP] Copy Pods Resources */ = {
326 + D0F9B4752B25C3F455EAAB34 /* [CP] Copy Pods Resources */ = {
327 isa = PBXShellScriptBuildPhase;
328 buildActionMask = 2147483647;
329 files = (
@@ -324,22 +340,7 @@
340 shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
341 showEnvVarsInLog = 0;
342 };
327 - 9740EEB61CF901F6004384FC /* Run Script */ = {
328 - isa = PBXShellScriptBuildPhase;
329 - alwaysOutOfDate = 1;
330 - buildActionMask = 2147483647;
331 - files = (
332 - );
333 - inputPaths = (
334 - );
335 - name = "Run Script";
336 - outputPaths = (
337 - );
338 - runOnlyForDeploymentPostprocessing = 0;
339 - shellPath = /bin/sh;
340 - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
341 - };
342 - AF1553571D404C2B52FA3EE8 /* [CP] Check Pods Manifest.lock */ = {
343 + D2249D2FD0465B2C76420590 /* [CP] Check Pods Manifest.lock */ = {
344 isa = PBXShellScriptBuildPhase;
345 buildActionMask = 2147483647;
346 files = (
@@ -354,14 +355,14 @@
355 outputFileListPaths = (
356 );
357 outputPaths = (
357 - "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
358 + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
359 );
360 runOnlyForDeploymentPostprocessing = 0;
361 shellPath = /bin/sh;
362 shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
363 showEnvVarsInLog = 0;
364 };
364 - E66B1B93294383A7AD82BEE0 /* [CP] Check Pods Manifest.lock */ = {
365 + FE21A8B6035F35A6E6A38E92 /* [CP] Check Pods Manifest.lock */ = {
366 isa = PBXShellScriptBuildPhase;
367 buildActionMask = 2147483647;
368 files = (
@@ -376,7 +377,7 @@
377 outputFileListPaths = (
378 );
379 outputPaths = (
379 - "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
380 + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
381 );
382 runOnlyForDeploymentPostprocessing = 0;
383 shellPath = /bin/sh;
@@ -492,6 +493,7 @@
493 buildSettings = {
494 ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
495 CLANG_ENABLE_MODULES = YES;
496 + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
497 CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
498 DEVELOPMENT_TEAM = TZX5LG7ZW5;
499 ENABLE_BITCODE = NO;
@@ -510,7 +512,7 @@
512 };
513 331C8088294A63A400263BE5 /* Debug */ = {
514 isa = XCBuildConfiguration;
513 - baseConfigurationReference = 1E28B77AE84C180CE237C2CF /* Pods-RunnerTests.debug.xcconfig */;
515 + baseConfigurationReference = F63A76ABB8D4E03F11C4084E /* Pods-RunnerTests.debug.xcconfig */;
516 buildSettings = {
517 BUNDLE_LOADER = "$(TEST_HOST)";
518 CODE_SIGN_STYLE = Automatic;
@@ -528,7 +530,7 @@
530 };
531 331C8089294A63A400263BE5 /* Release */ = {
532 isa = XCBuildConfiguration;
531 - baseConfigurationReference = AA0C333EA96851D712658FE5 /* Pods-RunnerTests.release.xcconfig */;
533 + baseConfigurationReference = D7F09850547E66B715D2AE1C /* Pods-RunnerTests.release.xcconfig */;
534 buildSettings = {
535 BUNDLE_LOADER = "$(TEST_HOST)";
536 CODE_SIGN_STYLE = Automatic;
@@ -544,7 +546,7 @@
546 };
547 331C808A294A63A400263BE5 /* Profile */ = {
548 isa = XCBuildConfiguration;
547 - baseConfigurationReference = 2910D1CEAE7EC5D481C55D03 /* Pods-RunnerTests.profile.xcconfig */;
549 + baseConfigurationReference = 86D4F01C5ED86C95435B62E4 /* Pods-RunnerTests.profile.xcconfig */;
550 buildSettings = {
551 BUNDLE_LOADER = "$(TEST_HOST)";
552 CODE_SIGN_STYLE = Automatic;
@@ -675,6 +677,7 @@
677 buildSettings = {
678 ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
679 CLANG_ENABLE_MODULES = YES;
680 + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
681 CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
682 DEVELOPMENT_TEAM = TZX5LG7ZW5;
683 ENABLE_BITCODE = NO;
@@ -698,6 +701,7 @@
701 buildSettings = {
702 ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
703 CLANG_ENABLE_MODULES = YES;
704 + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
705 CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
706 DEVELOPMENT_TEAM = TZX5LG7ZW5;
707 ENABLE_BITCODE = NO;
ios/Runner/Info.plist
+4 -1
@@ -76,12 +76,15 @@
76 <key>UIBackgroundModes</key>
77 <array>
78 <string>fetch</string>
79 - <string>processing</string>
79 <string>audio</string>
80 </array>
81 <key>UIFileSharingEnabled</key>
82 <true/>
83 <key>NSLocalNetworkUsageDescription</key>
84 <string>This app requires local network access for model inference services.</string>
85 + <key>NSPhotoLibraryUsageDescription</key>
86 + <string>This app requires access to your photo library to let you select and import files, documents, or images into your chats.</string>
87 + <key>ITSAppUsesNonExemptEncryption</key>
88 + <false/>
89 </dict>
90 </plist>
ios/Runner/Runner.entitlements new
+8
@@ -0,0 +1,8 @@
1 +<?xml version="1.0" encoding="UTF-8"?>
2 +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3 +<plist version="1.0">
4 +<dict>
5 + <key>aps-environment</key>
6 + <string>development</string>
7 +</dict>
8 +</plist>
lib/app.dart
+2 -2
@@ -58,12 +58,12 @@ final routerProvider = Provider<GoRouter>((ref) {
58 GoRoute(
59 path: AppRoutes.onboarding,
60 pageBuilder: (context, state) =>
61 - const NoTransitionPage(child: OnboardingLanguageScreen()),
61 + const MaterialPage(child: OnboardingLanguageScreen()),
62 ),
63 GoRoute(
64 path: AppRoutes.onboardingServerType,
65 pageBuilder: (context, state) =>
66 - const NoTransitionPage(child: OnboardingServerTypeScreen()),
66 + const MaterialPage(child: OnboardingServerTypeScreen()),
67 ),
68 GoRoute(
69 path: AppRoutes.onboardingSetup,
lib/features/models/screens/model_picker_sheet.dart
+4 -2
@@ -1,3 +1,5 @@
1 +import 'dart:io';
2 +
3 import 'package:flutter/material.dart';
4 import 'package:flutter_riverpod/flutter_riverpod.dart';
5 import 'package:localmind/l10n/app_localizations.dart';
@@ -837,7 +839,7 @@ class _OnDeviceModelTile extends ConsumerWidget {
839 ),
840 ],
841 ),
840 - if (deviceMemory != null && deviceMemory!.isOversized(model.minRamMb))
842 + if (!Platform.isIOS && deviceMemory != null && deviceMemory!.isOversized(model.minRamMb))
843 Padding(
844 padding: const EdgeInsets.only(top: 2),
845 child: Row(
@@ -980,7 +982,7 @@ class _OnDeviceModelTile extends ConsumerWidget {
982 }
983
984 void _loadModel(BuildContext context, WidgetRef ref) async {
983 - if (deviceMemory != null) {
985 + if (!Platform.isIOS && deviceMemory != null) {
986 if (!deviceMemory!.hasEnoughRam(model.minRamMb)) {
987 final proceed = await _showRamWarning(context);
988 if (!proceed) return;
lib/features/on_device/views/model_manager_screen.dart
+5 -3
@@ -1,3 +1,5 @@
1 +import 'dart:io';
2 +
3 import 'package:flutter/material.dart';
4 import 'package:flutter_riverpod/flutter_riverpod.dart';
5 import 'package:localmind/l10n/app_localizations.dart';
@@ -460,7 +462,7 @@ class _ModelCard extends ConsumerWidget {
462 ),
463 ],
464 ),
463 - if (deviceMemory != null && deviceMemory!.isOversized(model.minRamMb))
465 + if (!Platform.isIOS && deviceMemory != null && deviceMemory!.isOversized(model.minRamMb))
466 Padding(
467 padding: const EdgeInsets.only(top: 6),
468 child: Container(
@@ -697,7 +699,7 @@ class _ModelCard extends ConsumerWidget {
699
700 Future<void> _startDownload(BuildContext context, WidgetRef ref) async {
701 final l10n = AppLocalizations.of(context)!;
700 - if (deviceMemory != null) {
702 + if (!Platform.isIOS && deviceMemory != null) {
703 if (deviceMemory!.isOversized(model.minRamMb)) {
704 final proceed = await _showRamWarning(
705 context,
@@ -717,7 +719,7 @@ class _ModelCard extends ConsumerWidget {
719
720 Future<void> _loadModel(BuildContext context, WidgetRef ref) async {
721 final l10n = AppLocalizations.of(context)!;
720 - if (deviceMemory != null) {
722 + if (!Platform.isIOS && deviceMemory != null) {
723 if (!deviceMemory!.hasEnoughRam(model.minRamMb)) {
724 final proceed = await _showRamWarning(
725 context,
lib/features/onboarding/screens/onboarding_language_screen.dart
+238 -209
@@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
3 import 'package:go_router/go_router.dart';
4 import 'package:hugeicons/hugeicons.dart';
5 import 'package:shadcn_ui/shadcn_ui.dart';
6 +import 'package:cue/cue.dart';
7
8 import '../../../core/providers/app_providers.dart';
9 import '../../../core/routes/app_routes.dart';
@@ -174,237 +175,265 @@ class _OnboardingLanguageScreenState
175 ),
176 ],
177 ),
177 - body: Column(
178 - crossAxisAlignment: CrossAxisAlignment.stretch,
179 - children: [
180 - Expanded(
181 - child: SingleChildScrollView(
182 - padding: const EdgeInsets.symmetric(horizontal: 24.0),
183 - child: Column(
184 - crossAxisAlignment: CrossAxisAlignment.stretch,
185 - children: [
186 - const SizedBox(height: 24),
187 - Text(
188 - l10n.onboarding_welcome,
189 - textAlign: TextAlign.center,
190 - style: theme.textTheme.headlineLarge?.copyWith(
191 - fontWeight: FontWeight.w800,
192 - height: 1.1,
193 - ),
194 - ),
195 - const SizedBox(height: 12),
196 - Text(
197 - l10n.onboarding_choose_language,
198 - textAlign: TextAlign.center,
199 - style: theme.textTheme.titleMedium?.copyWith(
200 - fontWeight: FontWeight.w600,
201 - color: theme.colorScheme.primary,
202 - ),
203 - ),
204 - const SizedBox(height: 8),
205 - Text(
206 - l10n.onboarding_choose_language_desc,
207 - textAlign: TextAlign.center,
208 - style: theme.textTheme.bodySmall?.copyWith(
209 - color: theme.colorScheme.onSurface.withValues(alpha: 0.7),
210 - height: 1.5,
211 - ),
212 - ),
213 - const SizedBox(height: 32),
214 - ListView.separated(
215 - shrinkWrap: true,
216 - physics: const NeverScrollableScrollPhysics(),
217 - itemCount: _languages.length,
218 - separatorBuilder: (context, index) => const SizedBox(height: 12),
219 - itemBuilder: (context, index) {
220 - final lang = _languages[index];
221 - final isSelected = _selectedCode == lang.code;
222 -
223 - return GestureDetector(
224 - onTap: () {
225 - setState(() {
226 - _selectedCode = lang.code;
227 - });
228 - // Dynamically apply settings localization update
229 - ref
230 - .read(settingsProvider.notifier)
231 - .setLocaleCode(lang.code);
232 - },
233 - child: AnimatedContainer(
234 - duration: const Duration(milliseconds: 200),
235 - padding: const EdgeInsets.symmetric(
236 - horizontal: 16,
237 - vertical: 12,
178 + body: Cue.onMount(
179 + motion: .smooth(),
180 + child: Column(
181 + crossAxisAlignment: CrossAxisAlignment.stretch,
182 + children: [
183 + Expanded(
184 + child: SingleChildScrollView(
185 + padding: const EdgeInsets.symmetric(horizontal: 24.0),
186 + child: Column(
187 + crossAxisAlignment: CrossAxisAlignment.stretch,
188 + children: [
189 + const SizedBox(height: 24),
190 + Actor(
191 + acts: [
192 + .fadeIn(),
193 + .slideY(from: 0.08),
194 + ],
195 + child: Column(
196 + crossAxisAlignment: CrossAxisAlignment.stretch,
197 + children: [
198 + Text(
199 + l10n.onboarding_welcome,
200 + textAlign: TextAlign.center,
201 + style: theme.textTheme.headlineLarge?.copyWith(
202 + fontWeight: FontWeight.w800,
203 + height: 1.1,
204 + ),
205 ),
239 - decoration: BoxDecoration(
240 - color: theme.colorScheme.surface,
241 - borderRadius: BorderRadius.circular(16),
242 - border: Border.all(
243 - color: isSelected
244 - ? theme.colorScheme.primary
245 - : theme.colorScheme.outline
246 - .withValues(alpha: 0.15),
247 - width: isSelected ? 2 : 1,
206 + const SizedBox(height: 12),
207 + Text(
208 + l10n.onboarding_choose_language,
209 + textAlign: TextAlign.center,
210 + style: theme.textTheme.titleMedium?.copyWith(
211 + fontWeight: FontWeight.w600,
212 + color: theme.colorScheme.primary,
213 ),
249 - boxShadow: isSelected
250 - ? [
251 - BoxShadow(
252 - color: theme.colorScheme.primary
253 - .withValues(alpha: 0.1),
254 - blurRadius: 8,
255 - offset: const Offset(0, 2),
256 - )
257 - ]
258 - : null,
214 ),
260 - child: Row(
261 - children: [
262 - Container(
263 - width: 48,
264 - height: 32,
265 - decoration: BoxDecoration(
266 - color: Colors.white,
267 - borderRadius: BorderRadius.circular(6),
268 - border: Border.all(
269 - color: theme.colorScheme.outline
270 - .withValues(alpha: 0.15),
271 - width: 1,
272 - ),
273 - boxShadow: [
274 - BoxShadow(
275 - color: Colors.black.withValues(alpha: 0.05),
276 - blurRadius: 4,
277 - offset: const Offset(0, 2),
278 - ),
279 - ],
215 + const SizedBox(height: 8),
216 + Text(
217 + l10n.onboarding_choose_language_desc,
218 + textAlign: TextAlign.center,
219 + style: theme.textTheme.bodySmall?.copyWith(
220 + color: theme.colorScheme.onSurface.withValues(alpha: 0.7),
221 + height: 1.5,
222 + ),
223 + ),
224 + ],
225 + ),
226 + ),
227 + const SizedBox(height: 32),
228 + Actor(
229 + delay: 60.ms,
230 + acts: [
231 + .fadeIn(),
232 + .slideY(from: 0.08),
233 + ],
234 + child: ListView.separated(
235 + shrinkWrap: true,
236 + physics: const NeverScrollableScrollPhysics(),
237 + itemCount: _languages.length,
238 + separatorBuilder: (context, index) => const SizedBox(height: 12),
239 + itemBuilder: (context, index) {
240 + final lang = _languages[index];
241 + final isSelected = _selectedCode == lang.code;
242 +
243 + return GestureDetector(
244 + onTap: () {
245 + setState(() {
246 + _selectedCode = lang.code;
247 + });
248 + // Dynamically apply settings localization update
249 + ref
250 + .read(settingsProvider.notifier)
251 + .setLocaleCode(lang.code);
252 + },
253 + child: AnimatedContainer(
254 + duration: const Duration(milliseconds: 200),
255 + padding: const EdgeInsets.symmetric(
256 + horizontal: 16,
257 + vertical: 12,
258 + ),
259 + decoration: BoxDecoration(
260 + color: theme.colorScheme.surface,
261 + borderRadius: BorderRadius.circular(16),
262 + border: Border.all(
263 + color: isSelected
264 + ? theme.colorScheme.primary
265 + : theme.colorScheme.outline
266 + .withValues(alpha: 0.15),
267 + width: isSelected ? 2 : 1,
268 ),
281 - alignment: Alignment.center,
282 - child: ClipRRect(
283 - borderRadius: BorderRadius.circular(5),
284 - child: Image.asset(
285 - lang.flagAsset,
269 + boxShadow: isSelected
270 + ? [
271 + BoxShadow(
272 + color: theme.colorScheme.primary
273 + .withValues(alpha: 0.1),
274 + blurRadius: 8,
275 + offset: const Offset(0, 2),
276 + )
277 + ]
278 + : null,
279 + ),
280 + child: Row(
281 + children: [
282 + Container(
283 width: 48,
284 height: 32,
288 - fit: BoxFit.cover,
289 - errorBuilder: (context, error, stackTrace) {
290 - return Text(
291 - lang.flag,
292 - style: const TextStyle(fontSize: 18),
293 - );
294 - },
295 - ),
296 - ),
297 - ),
298 - const SizedBox(width: 16),
299 - Expanded(
300 - child: Column(
301 - crossAxisAlignment: CrossAxisAlignment.start,
302 - mainAxisSize: MainAxisSize.min,
303 - children: [
304 - Text(
305 - lang.nativeName,
306 - style:
307 - theme.textTheme.titleMedium?.copyWith(
308 - fontWeight: FontWeight.bold,
285 + decoration: BoxDecoration(
286 + color: Colors.white,
287 + borderRadius: BorderRadius.circular(6),
288 + border: Border.all(
289 + color: theme.colorScheme.outline
290 + .withValues(alpha: 0.15),
291 + width: 1,
292 ),
293 + boxShadow: [
294 + BoxShadow(
295 + color: Colors.black.withValues(alpha: 0.05),
296 + blurRadius: 4,
297 + offset: const Offset(0, 2),
298 + ),
299 + ],
300 ),
311 - const SizedBox(height: 2),
312 - Text(
313 - '${lang.englishName} • ${lang.shortText.toUpperCase()}',
314 - style: theme.textTheme.labelSmall?.copyWith(
315 - color: theme.colorScheme.onSurface
316 - .withValues(alpha: 0.5),
301 + alignment: Alignment.center,
302 + child: ClipRRect(
303 + borderRadius: BorderRadius.circular(5),
304 + child: Image.asset(
305 + lang.flagAsset,
306 + width: 48,
307 + height: 32,
308 + fit: BoxFit.cover,
309 + errorBuilder: (context, error, stackTrace) {
310 + return Text(
311 + lang.flag,
312 + style: const TextStyle(fontSize: 18),
313 + );
314 + },
315 ),
316 ),
319 - ],
320 - ),
321 - ),
322 - const SizedBox(width: 8),
323 - AnimatedContainer(
324 - duration: const Duration(milliseconds: 200),
325 - width: 22,
326 - height: 22,
327 - decoration: BoxDecoration(
328 - shape: BoxShape.circle,
329 - color: isSelected
330 - ? theme.colorScheme.primary
331 - : Colors.transparent,
332 - border: Border.all(
333 - color: isSelected
334 - ? theme.colorScheme.primary
335 - : theme.colorScheme.outline
336 - .withValues(alpha: 0.3),
337 - width: 2,
317 ),
339 - ),
340 - child: isSelected
341 - ? const Icon(
342 - Icons.check,
343 - color: Colors.white,
344 - size: 14,
345 - )
346 - : null,
318 + const SizedBox(width: 16),
319 + Expanded(
320 + child: Column(
321 + crossAxisAlignment: CrossAxisAlignment.start,
322 + mainAxisSize: MainAxisSize.min,
323 + children: [
324 + Text(
325 + lang.nativeName,
326 + style:
327 + theme.textTheme.titleMedium?.copyWith(
328 + fontWeight: FontWeight.bold,
329 + ),
330 + ),
331 + const SizedBox(height: 2),
332 + Text(
333 + '${lang.englishName} • ${lang.shortText.toUpperCase()}',
334 + style: theme.textTheme.labelSmall?.copyWith(
335 + color: theme.colorScheme.onSurface
336 + .withValues(alpha: 0.5),
337 + ),
338 + ),
339 + ],
340 + ),
341 + ),
342 + const SizedBox(width: 8),
343 + AnimatedContainer(
344 + duration: const Duration(milliseconds: 200),
345 + width: 22,
346 + height: 22,
347 + decoration: BoxDecoration(
348 + shape: BoxShape.circle,
349 + color: isSelected
350 + ? theme.colorScheme.primary
351 + : Colors.transparent,
352 + border: Border.all(
353 + color: isSelected
354 + ? theme.colorScheme.primary
355 + : theme.colorScheme.outline
356 + .withValues(alpha: 0.3),
357 + width: 2,
358 + ),
359 + ),
360 + child: isSelected
361 + ? const Icon(
362 + Icons.check,
363 + color: Colors.white,
364 + size: 14,
365 + )
366 + : null,
367 + ),
368 + ],
369 ),
348 - ],
349 - ),
350 - ),
351 - );
352 - },
353 - ),
354 - const SizedBox(height: 32),
355 - ],
370 + ),
371 + );
372 + },
373 + ),
374 + ),
375 + const SizedBox(height: 32),
376 + ],
377 + ),
378 ),
379 ),
358 - ),
359 - Padding(
360 - padding: const EdgeInsets.symmetric(horizontal: 24.0),
361 - child: Column(
362 - children: [
363 - Row(
364 - mainAxisAlignment: MainAxisAlignment.center,
380 + Actor(
381 + delay: 120.ms,
382 + acts: [
383 + .fadeIn(),
384 + .slideY(from: 0.08),
385 + ],
386 + child: Padding(
387 + padding: const EdgeInsets.symmetric(horizontal: 24.0),
388 + child: Column(
389 children: [
366 - Container(
367 - width: 8,
368 - height: 8,
369 - decoration: BoxDecoration(
370 - color: theme.colorScheme.primary,
371 - shape: BoxShape.circle,
372 - ),
390 + Row(
391 + mainAxisAlignment: MainAxisAlignment.center,
392 + children: [
393 + Container(
394 + width: 8,
395 + height: 8,
396 + decoration: BoxDecoration(
397 + color: theme.colorScheme.primary,
398 + shape: BoxShape.circle,
399 + ),
400 + ),
401 + const SizedBox(width: 8),
402 + Text(
403 + l10n.ready_continue,
404 + style: theme.textTheme.labelSmall?.copyWith(
405 + color: theme.colorScheme.onSurface.withValues(
406 + alpha: 0.5,
407 + ),
408 + letterSpacing: 2,
409 + fontWeight: FontWeight.w600,
410 + ),
411 + ),
412 + ],
413 ),
374 - const SizedBox(width: 8),
375 - Text(
376 - l10n.ready_continue,
377 - style: theme.textTheme.labelSmall?.copyWith(
378 - color: theme.colorScheme.onSurface.withValues(
379 - alpha: 0.5,
414 + const SizedBox(height: 16),
415 + ShadButton(
416 + width: double.infinity,
417 + onPressed: () {
418 + context.push(AppRoutes.onboardingServerType);
419 + },
420 + child: Text(
421 + l10n.continue_action,
422 + style: const TextStyle(
423 + fontSize: 16,
424 + fontWeight: FontWeight.w600,
425 ),
381 - letterSpacing: 2,
382 - fontWeight: FontWeight.w600,
426 ),
427 ),
385 - ],
386 - ),
387 - const SizedBox(height: 16),
388 - ShadButton(
389 - width: double.infinity,
390 - onPressed: () {
391 - context.push(AppRoutes.onboardingServerType);
392 - },
393 - child: Text(
394 - l10n.continue_action,
395 - style: const TextStyle(
396 - fontSize: 16,
397 - fontWeight: FontWeight.w600,
428 + SizedBox(
429 + height: MediaQuery.of(context).padding.bottom,
430 ),
399 - ),
400 - ),
401 - SizedBox(
402 - height: MediaQuery.of(context).padding.bottom,
431 + ],
432 ),
404 - ],
433 + ),
434 ),
406 - ),
407 - ],
435 + ],
436 + ),
437 ),
438 );
439 }
lib/features/onboarding/screens/onboarding_model_download_screen.dart
+94 -58
@@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
5 import 'package:flutter_riverpod/flutter_riverpod.dart';
6 import 'package:go_router/go_router.dart';
7 import 'package:shadcn_ui/shadcn_ui.dart';
8 +import 'package:cue/cue.dart';
9
10 import '../../../core/models/enums.dart';
11 import '../../../core/routes/app_routes.dart';
@@ -38,77 +39,112 @@ class _OnboardingModelDownloadScreenState
39 final models = ref.watch(onDeviceModelsProvider);
40 final downloadedModelsAsync = ref.watch(downloadedModelsProvider);
41 final downloadStates = ref.watch(foregroundDownloadNotifierProvider);
41 - final isAndroid = Platform.isAndroid;
42 final deviceMemoryAsync = ref.watch(deviceMemoryProvider);
43
44 return Scaffold(
45 appBar: AppBar(title: Text(l10n.download_model_title)),
46 body: SafeArea(
47 - child: ListView(
48 - padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 16.0),
49 - children: [
50 - Text(
51 - l10n.download_model_desc,
52 - style: theme.textTheme.bodyMedium?.copyWith(
53 - color: theme.colorScheme.onSurface.withValues(alpha: 0.7),
54 - ),
55 - ),
56 - const SizedBox(height: 24),
57 - if (!isAndroid)
58 - Container(
59 - padding: const EdgeInsets.all(12),
60 - margin: const EdgeInsets.only(bottom: 16),
61 - decoration: BoxDecoration(
62 - color: Colors.orange.withValues(alpha: 0.1),
63 - borderRadius: BorderRadius.circular(8),
64 - border: Border.all(color: Colors.orange),
65 - ),
66 - child: Row(
47 + child: Cue.onMount(
48 + motion: .smooth(),
49 + child: ListView(
50 + padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 16.0),
51 + children: [
52 + Actor(
53 + acts: [
54 + .fadeIn(),
55 + .slideY(from: 0.08),
56 + ],
57 + child: Column(
58 + crossAxisAlignment: CrossAxisAlignment.stretch,
59 children: [
68 - const Icon(Icons.info_outline, color: Colors.orange, size: 20),
69 - const SizedBox(width: 8),
70 - Expanded(
71 - child: Text(
72 - l10n.on_device_android_only,
73 - style: const TextStyle(color: Colors.orange),
60 + Text(
61 + l10n.download_model_desc,
62 + style: theme.textTheme.bodyMedium?.copyWith(
63 + color: theme.colorScheme.onSurface.withValues(alpha: 0.7),
64 ),
65 ),
66 + const SizedBox(height: 24),
67 + if (!Platform.isAndroid && !Platform.isIOS)
68 + Container(
69 + padding: const EdgeInsets.all(12),
70 + margin: const EdgeInsets.only(bottom: 16),
71 + decoration: BoxDecoration(
72 + color: Colors.orange.withValues(alpha: 0.1),
73 + borderRadius: BorderRadius.circular(8),
74 + border: Border.all(color: Colors.orange),
75 + ),
76 + child: Row(
77 + children: [
78 + const Icon(Icons.info_outline, color: Colors.orange, size: 20),
79 + const SizedBox(width: 8),
80 + Expanded(
81 + child: Text(
82 + l10n.on_device_android_only,
83 + style: const TextStyle(color: Colors.orange),
84 + ),
85 + ),
86 + ],
87 + ),
88 + ),
89 + _buildMemoryInfo(context, ref, deviceMemoryAsync),
90 ],
91 ),
92 ),
79 - _buildMemoryInfo(context, ref, deviceMemoryAsync),
80 - ...models.map((model) {
81 - final isDownloaded = downloadedModelsAsync.when(
82 - data: (set) => set.contains(model.id),
83 - loading: () => false,
84 - error: (_, _) => false,
85 - );
86 - final progressInfo = downloadStates[model.id];
93 + Actor(
94 + delay: 60.ms,
95 + acts: [
96 + .fadeIn(),
97 + .slideY(from: 0.08),
98 + ],
99 + child: Column(
100 + crossAxisAlignment: CrossAxisAlignment.stretch,
101 + children: models.map((model) {
102 + final isDownloaded = downloadedModelsAsync.when(
103 + data: (set) => set.contains(model.id),
104 + loading: () => false,
105 + error: (_, _) => false,
106 + );
107 + final progressInfo = downloadStates[model.id];
108
88 - return Padding(
89 - padding: const EdgeInsets.only(bottom: 12),
90 - child: _ModelCard(
91 - model: model,
92 - isDownloaded: isDownloaded,
93 - progressInfo: progressInfo,
94 - theme: theme,
95 - deviceMemory: deviceMemoryAsync.value,
109 + return Padding(
110 + padding: const EdgeInsets.only(bottom: 12),
111 + child: _ModelCard(
112 + model: model,
113 + isDownloaded: isDownloaded,
114 + progressInfo: progressInfo,
115 + theme: theme,
116 + deviceMemory: deviceMemoryAsync.value,
117 + ),
118 + );
119 + }).toList(),
120 ),
97 - );
98 - }),
99 - const SizedBox(height: 24),
100 - if (_isCreatingServer)
101 - const Center(child: CircularProgressIndicator())
102 - else
103 - ShadButton(
104 - width: double.infinity,
105 - onPressed: _canContinue() ? _createOnDeviceServer : null,
106 - child: Text(
107 - l10n.continue_action,
108 - style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
121 + ),
122 + Actor(
123 + delay: 120.ms,
124 + acts: [
125 + .fadeIn(),
126 + .slideY(from: 0.08),
127 + ],
128 + child: Column(
129 + crossAxisAlignment: CrossAxisAlignment.stretch,
130 + children: [
131 + const SizedBox(height: 24),
132 + if (_isCreatingServer)
133 + const Center(child: CircularProgressIndicator())
134 + else
135 + ShadButton(
136 + width: double.infinity,
137 + onPressed: _canContinue() ? _createOnDeviceServer : null,
138 + child: Text(
139 + l10n.continue_action,
140 + style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
141 + ),
142 + ),
143 + ],
144 ),
145 ),
111 - ],
146 + ],
147 + ),
148 ),
149 ),
150 );
@@ -311,7 +347,7 @@ class _ModelCard extends ConsumerWidget {
347 ),
348 ],
349 ),
314 - if (deviceMemory != null && deviceMemory!.isOversized(model.minRamMb))
350 + if (!Platform.isIOS && deviceMemory != null && deviceMemory!.isOversized(model.minRamMb))
351 Padding(
352 padding: const EdgeInsets.only(top: 4),
353 child: Row(
@@ -438,7 +474,7 @@ class _ModelCard extends ConsumerWidget {
474 }
475
476 Future<void> _startDownload(BuildContext context, WidgetRef ref) async {
441 - if (deviceMemory != null) {
477 + if (!Platform.isIOS && deviceMemory != null) {
478 if (deviceMemory!.isOversized(model.minRamMb)) {
479 final proceed = await _showRamWarning(context);
480 if (!proceed) return;
lib/features/onboarding/screens/onboarding_notification_permission_screen.dart
+125 -86
@@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
3 import 'package:go_router/go_router.dart';
4 import 'package:hugeicons/hugeicons.dart';
5 import 'package:shadcn_ui/shadcn_ui.dart';
6 +import 'package:cue/cue.dart';
7
8 import '../../../core/providers/app_providers.dart';
9 import '../../../core/routes/app_routes.dart';
@@ -57,101 +58,139 @@ class _OnboardingNotificationPermissionScreenState
58
59 return Scaffold(
60 body: SafeArea(
60 - child: Padding(
61 - padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 32.0),
62 - child: Column(
63 - crossAxisAlignment: CrossAxisAlignment.center,
64 - children: [
65 - const Spacer(),
66 - // Visual element
67 - Container(
68 - padding: const EdgeInsets.all(32),
69 - decoration: BoxDecoration(
70 - color: theme.colorScheme.primaryContainer.withValues(
71 - alpha: 0.2,
61 + child: Cue.onMount(
62 + motion: .smooth(),
63 + child: Padding(
64 + padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 32.0),
65 + child: Column(
66 + crossAxisAlignment: CrossAxisAlignment.center,
67 + children: [
68 + const Spacer(),
69 + // Visual element
70 + Actor(
71 + acts: [
72 + .fadeIn(),
73 + .scale(from: 0.8),
74 + ],
75 + child: Container(
76 + padding: const EdgeInsets.all(32),
77 + decoration: BoxDecoration(
78 + color: theme.colorScheme.primaryContainer.withValues(
79 + alpha: 0.2,
80 + ),
81 + shape: BoxShape.circle,
82 + ),
83 + child: HugeIcon(
84 + icon: HugeIcons.strokeRoundedNotification03,
85 + size: 80,
86 + color: theme.colorScheme.primary,
87 + ),
88 ),
73 - shape: BoxShape.circle,
89 ),
75 - child: HugeIcon(
76 - icon: HugeIcons.strokeRoundedNotification03,
77 - size: 80,
78 - color: theme.colorScheme.primary,
79 - ),
80 - ),
81 - const SizedBox(height: 48),
82 - Text(
83 - l10n.stay_updated,
84 - style: theme.textTheme.displaySmall?.copyWith(
85 - fontWeight: FontWeight.bold,
90 + const SizedBox(height: 48),
91 + Actor(
92 + delay: 60.ms,
93 + acts: [
94 + .fadeIn(),
95 + .slideY(from: 0.08),
96 + ],
97 + child: Column(
98 + children: [
99 + Text(
100 + l10n.stay_updated,
101 + style: theme.textTheme.displaySmall?.copyWith(
102 + fontWeight: FontWeight.bold,
103 + ),
104 + textAlign: TextAlign.center,
105 + ),
106 + const SizedBox(height: 16),
107 + Text(
108 + l10n.stay_updated_desc,
109 + style: theme.textTheme.titleMedium?.copyWith(
110 + color: theme.colorScheme.onSurface.withValues(alpha: 0.7),
111 + ),
112 + textAlign: TextAlign.center,
113 + ),
114 + ],
115 + ),
116 ),
87 - textAlign: TextAlign.center,
88 - ),
89 - const SizedBox(height: 16),
90 - Text(
91 - l10n.stay_updated_desc,
92 - style: theme.textTheme.titleMedium?.copyWith(
93 - color: theme.colorScheme.onSurface.withValues(alpha: 0.7),
117 + const SizedBox(height: 48),
118 + // Benefits list
119 + Actor(
120 + delay: 120.ms,
121 + acts: [
122 + .fadeIn(),
123 + .slideY(from: 0.08),
124 + ],
125 + child: Column(
126 + children: [
127 + _buildBenefitRow(
128 + context,
129 + HugeIcons.strokeRoundedDownload01,
130 + l10n.notification_benefit_downloads,
131 + ),
132 + const SizedBox(height: 16),
133 + _buildBenefitRow(
134 + context,
135 + HugeIcons.strokeRoundedAiChat01,
136 + l10n.notification_benefit_completions,
137 + ),
138 + const SizedBox(height: 16),
139 + _buildBenefitRow(
140 + context,
141 + HugeIcons.strokeRoundedClock01,
142 + l10n.notification_benefit_background,
143 + ),
144 + ],
145 + ),
146 ),
95 - textAlign: TextAlign.center,
96 - ),
97 - const SizedBox(height: 48),
98 - // Benefits list
99 - _buildBenefitRow(
100 - context,
101 - HugeIcons.strokeRoundedDownload01,
102 - l10n.notification_benefit_downloads,
103 - ),
104 - const SizedBox(height: 16),
105 - _buildBenefitRow(
106 - context,
107 - HugeIcons.strokeRoundedAiChat01,
108 - l10n.notification_benefit_completions,
109 - ),
110 - const SizedBox(height: 16),
111 - _buildBenefitRow(
112 - context,
113 - HugeIcons.strokeRoundedClock01,
114 - l10n.notification_benefit_background,
115 - ),
116 - const Spacer(),
117 - const SizedBox(height: 32),
118 - Column(
119 - crossAxisAlignment: CrossAxisAlignment.stretch,
120 - children: [
121 - ShadButton(
122 - onPressed: _isProcessing ? null : _requestPermission,
123 - child: _isProcessing
124 - ? const SizedBox(
125 - height: 20,
126 - width: 20,
127 - child: CircularProgressIndicator(
128 - strokeWidth: 2,
129 - valueColor: AlwaysStoppedAnimation(Colors.white),
130 - ),
131 - )
132 - : Text(
133 - l10n.allow_notifications,
134 - style: const TextStyle(
135 - fontSize: 16,
136 - fontWeight: FontWeight.w600,
147 + const Spacer(),
148 + const SizedBox(height: 32),
149 + Actor(
150 + delay: 180.ms,
151 + acts: [
152 + .fadeIn(),
153 + .slideY(from: 0.08),
154 + ],
155 + child: Column(
156 + crossAxisAlignment: CrossAxisAlignment.stretch,
157 + children: [
158 + ShadButton(
159 + onPressed: _isProcessing ? null : _requestPermission,
160 + child: _isProcessing
161 + ? const SizedBox(
162 + height: 20,
163 + width: 20,
164 + child: CircularProgressIndicator(
165 + strokeWidth: 2,
166 + valueColor: AlwaysStoppedAnimation(Colors.white),
167 + ),
168 + )
169 + : Text(
170 + l10n.allow_notifications,
171 + style: const TextStyle(
172 + fontSize: 16,
173 + fontWeight: FontWeight.w600,
174 + ),
175 + ),
176 + ),
177 + const SizedBox(height: 12),
178 + ShadButton.ghost(
179 + onPressed: _isProcessing ? null : _completeOnboarding,
180 + child: Text(
181 + l10n.not_now,
182 + style: TextStyle(
183 + color: theme.colorScheme.onSurface.withValues(
184 + alpha: 0.6,
185 ),
186 ),
139 - ),
140 - const SizedBox(height: 12),
141 - ShadButton.ghost(
142 - onPressed: _isProcessing ? null : _completeOnboarding,
143 - child: Text(
144 - l10n.not_now,
145 - style: TextStyle(
146 - color: theme.colorScheme.onSurface.withValues(
147 - alpha: 0.6,
187 ),
188 ),
150 - ),
189 + ],
190 ),
152 - ],
153 - ),
154 - ],
191 + ),
192 + ],
193 + ),
194 ),
195 ),
196 ),
lib/features/onboarding/screens/onboarding_server_setup_screen.dart
+164 -137
@@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
4 import 'package:flutter_riverpod/flutter_riverpod.dart';
5 import 'package:go_router/go_router.dart';
6 import 'package:shadcn_ui/shadcn_ui.dart';
7 +import 'package:cue/cue.dart';
8
9 import '../../../core/constants/app_constants.dart';
10 import '../../../core/models/enums.dart';
@@ -181,156 +182,182 @@ class _OnboardingServerSetupScreenState
182 return Scaffold(
183 appBar: AppBar(title: Text(l10n.setup_connection)),
184 body: SafeArea(
184 - child: Form(
185 - key: _formKey,
186 - child: ListView(
187 - padding: const EdgeInsets.symmetric(
188 - horizontal: 24.0,
189 - vertical: 16.0,
190 - ),
191 - children: [
192 - Text(
193 - l10n.setup_connection_desc(widget.selectedType.name),
194 - style: theme.textTheme.titleMedium?.copyWith(
195 - color: theme.colorScheme.onSurface.withValues(alpha: 0.7),
196 - ),
197 - ),
198 - const SizedBox(height: 32),
199 -
200 - TextFormField(
201 - controller: _nameController,
202 - decoration: InputDecoration(
203 - labelText: l10n.server_name,
204 - filled: true,
205 - fillColor: theme.colorScheme.surface,
206 - ),
207 - validator: (val) => val == null || val.trim().isEmpty
208 - ? l10n.name_required
209 - : null,
210 - ),
211 - const SizedBox(height: 16),
212 -
213 - if (!isCloud) ...[
214 - TextFormField(
215 - controller: _hostController,
216 - decoration: InputDecoration(
217 - labelText: l10n.host_label,
218 - filled: true,
219 - fillColor: theme.colorScheme.surface,
220 - ),
221 - validator: (val) => val == null || val.trim().isEmpty
222 - ? l10n.host_required
223 - : null,
224 - ),
225 - const SizedBox(height: 16),
226 -
227 - TextFormField(
228 - controller: _portController,
229 - decoration: InputDecoration(
230 - labelText: l10n.port_label,
231 - filled: true,
232 - fillColor: theme.colorScheme.surface,
233 - ),
234 - validator: (val) {
235 - if (val == null || val.trim().isEmpty) {
236 - return l10n.port_required;
237 - } else {
238 - if (int.tryParse(val) == null) {
239 - return l10n.port_invalid;
240 - } else {
241 - return null;
242 - }
243 - }
244 - },
245 - ),
246 - const SizedBox(height: 16),
247 - ],
248 -
249 - TextFormField(
250 - controller: _apiKeyController,
251 - decoration: InputDecoration(
252 - labelText: isCloud
253 - ? l10n.api_key_required
254 - : l10n.api_key_optional,
255 - filled: true,
256 - fillColor: theme.colorScheme.surface,
257 - ),
258 - validator: (val) {
259 - if (isCloud && (val == null || val.trim().isEmpty)) {
260 - return l10n.api_key_required_openrouter;
261 - }
262 - return null;
263 - },
264 - obscureText: true,
185 + child: Cue.onMount(
186 + motion: .smooth(),
187 + child: Form(
188 + key: _formKey,
189 + child: ListView(
190 + padding: const EdgeInsets.symmetric(
191 + horizontal: 24.0,
192 + vertical: 16.0,
193 ),
266 - const SizedBox(height: 24),
267 -
268 - if (_testResult != null)
269 - Container(
270 - padding: const EdgeInsets.all(12),
271 - margin: const EdgeInsets.only(bottom: 24),
272 - decoration: BoxDecoration(
273 - color: _testSuccess
274 - ? Colors.green.withValues(alpha: 0.1)
275 - : Colors.red.withValues(alpha: 0.1),
276 - borderRadius: BorderRadius.circular(8),
277 - border: Border.all(
278 - color: _testSuccess ? Colors.green : Colors.red,
194 + children: [
195 + Actor(
196 + acts: [
197 + .fadeIn(),
198 + .slideY(from: 0.08),
199 + ],
200 + child: Text(
201 + l10n.setup_connection_desc(widget.selectedType.name),
202 + style: theme.textTheme.titleMedium?.copyWith(
203 + color: theme.colorScheme.onSurface.withValues(alpha: 0.7),
204 ),
205 ),
281 - child: Row(
206 + ),
207 + Actor(
208 + delay: 60.ms,
209 + acts: [
210 + .fadeIn(),
211 + .slideY(from: 0.08),
212 + ],
213 + child: Column(
214 + crossAxisAlignment: CrossAxisAlignment.stretch,
215 children: [
283 - Icon(
284 - _testSuccess ? Icons.check_circle : Icons.error,
285 - color: _testSuccess ? Colors.green : Colors.red,
216 + const SizedBox(height: 32),
217 + TextFormField(
218 + controller: _nameController,
219 + decoration: InputDecoration(
220 + labelText: l10n.server_name,
221 + filled: true,
222 + fillColor: theme.colorScheme.surface,
223 + ),
224 + validator: (val) => val == null || val.trim().isEmpty
225 + ? l10n.name_required
226 + : null,
227 ),
287 - const SizedBox(width: 8),
288 - Expanded(
289 - child: Text(
290 - _testResult!,
291 - style: TextStyle(
292 - color: _testSuccess ? Colors.green : Colors.red,
228 + const SizedBox(height: 16),
229 + if (!isCloud) ...[
230 + TextFormField(
231 + controller: _hostController,
232 + decoration: InputDecoration(
233 + labelText: l10n.host_label,
234 + filled: true,
235 + fillColor: theme.colorScheme.surface,
236 ),
237 + validator: (val) => val == null || val.trim().isEmpty
238 + ? l10n.host_required
239 + : null,
240 ),
241 + const SizedBox(height: 16),
242 + TextFormField(
243 + controller: _portController,
244 + decoration: InputDecoration(
245 + labelText: l10n.port_label,
246 + filled: true,
247 + fillColor: theme.colorScheme.surface,
248 + ),
249 + validator: (val) {
250 + if (val == null || val.trim().isEmpty) {
251 + return l10n.port_required;
252 + } else {
253 + if (int.tryParse(val) == null) {
254 + return l10n.port_invalid;
255 + } else {
256 + return null;
257 + }
258 + }
259 + },
260 + ),
261 + const SizedBox(height: 16),
262 + ],
263 + TextFormField(
264 + controller: _apiKeyController,
265 + decoration: InputDecoration(
266 + labelText: isCloud
267 + ? l10n.api_key_required
268 + : l10n.api_key_optional,
269 + filled: true,
270 + fillColor: theme.colorScheme.surface,
271 + ),
272 + validator: (val) {
273 + if (isCloud && (val == null || val.trim().isEmpty)) {
274 + return l10n.api_key_required_openrouter;
275 + }
276 + return null;
277 + },
278 + obscureText: true,
279 ),
280 ],
281 ),
282 ),
299 -
300 - ShadButton.outline(
301 - width: double.infinity,
302 - onPressed: _isTesting ? null : _testConnection,
303 - child: _isTesting
304 - ? const SizedBox(
305 - width: 20,
306 - height: 20,
307 - child: CircularProgressIndicator(strokeWidth: 2),
308 - )
309 - : Text(l10n.test_connection),
310 - ),
311 - const SizedBox(height: 16),
312 -
313 - ShadButton(
314 - width: double.infinity,
315 - onPressed: _isSaving ? null : _saveAndContinue,
316 - child: _isSaving
317 - ? const SizedBox(
318 - width: 20,
319 - height: 20,
320 - child: CircularProgressIndicator(
321 - strokeWidth: 2,
322 - color: Colors.white,
323 - ),
324 - )
325 - : Text(
326 - l10n.save_continue,
327 - style: const TextStyle(
328 - fontSize: 16,
329 - fontWeight: FontWeight.w600,
283 + Actor(
284 + delay: 120.ms,
285 + acts: [
286 + .fadeIn(),
287 + .slideY(from: 0.08),
288 + ],
289 + child: Column(
290 + crossAxisAlignment: CrossAxisAlignment.stretch,
291 + children: [
292 + const SizedBox(height: 24),
293 + if (_testResult != null)
294 + Container(
295 + padding: const EdgeInsets.all(12),
296 + margin: const EdgeInsets.only(bottom: 24),
297 + decoration: BoxDecoration(
298 + color: _testSuccess
299 + ? Colors.green.withValues(alpha: 0.1)
300 + : Colors.red.withValues(alpha: 0.1),
301 + borderRadius: BorderRadius.circular(8),
302 + border: Border.all(
303 + color: _testSuccess ? Colors.green : Colors.red,
304 + ),
305 + ),
306 + child: Row(
307 + children: [
308 + Icon(
309 + _testSuccess ? Icons.check_circle : Icons.error,
310 + color: _testSuccess ? Colors.green : Colors.red,
311 + ),
312 + const SizedBox(width: 8),
313 + Expanded(
314 + child: Text(
315 + _testResult!,
316 + style: TextStyle(
317 + color: _testSuccess ? Colors.green : Colors.red,
318 + ),
319 + ),
320 + ),
321 + ],
322 + ),
323 ),
324 + ShadButton.outline(
325 + width: double.infinity,
326 + onPressed: _isTesting ? null : _testConnection,
327 + child: _isTesting
328 + ? const SizedBox(
329 + width: 20,
330 + height: 20,
331 + child: CircularProgressIndicator(strokeWidth: 2),
332 + )
333 + : Text(l10n.test_connection),
334 ),
332 - ),
333 - ],
335 + const SizedBox(height: 16),
336 + ShadButton(
337 + width: double.infinity,
338 + onPressed: _isSaving ? null : _saveAndContinue,
339 + child: _isSaving
340 + ? const SizedBox(
341 + width: 20,
342 + height: 20,
343 + child: CircularProgressIndicator(
344 + strokeWidth: 2,
345 + color: Colors.white,
346 + ),
347 + )
348 + : Text(
349 + l10n.save_continue,
350 + style: const TextStyle(
351 + fontSize: 16,
352 + fontWeight: FontWeight.w600,
353 + ),
354 + ),
355 + ),
356 + ],
357 + ),
358 + ),
359 + ],
360 + ),
361 ),
362 ),
363 ),
lib/features/onboarding/screens/onboarding_server_type_screen.dart
+172 -138
@@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
3 import 'package:go_router/go_router.dart';
4 import 'package:hugeicons/hugeicons.dart';
5 import 'package:shadcn_ui/shadcn_ui.dart';
6 +import 'package:cue/cue.dart';
7
8 import '../../../core/models/enums.dart';
9 import '../../../core/providers/app_providers.dart';
@@ -60,156 +61,189 @@ class _OnboardingServerTypeScreenState
61 ),
62 ],
63 ),
63 - body: Column(
64 - crossAxisAlignment: CrossAxisAlignment.stretch,
65 - children: [
66 - Expanded(
67 - child: SingleChildScrollView(
68 - padding: const EdgeInsets.symmetric(horizontal: 24.0),
69 - child: Column(
70 - crossAxisAlignment: CrossAxisAlignment.stretch,
71 - children: [
72 - const SizedBox(height: 24),
73 - Text(
74 - l10n.onboarding_connect_server,
75 - textAlign: TextAlign.center,
76 - style: theme.textTheme.headlineLarge?.copyWith(
77 - fontWeight: FontWeight.w800,
78 - height: 1.1,
79 - ),
80 - ),
81 - const SizedBox(height: 16),
82 - Text(
83 - l10n.onboarding_connect_desc,
84 - textAlign: TextAlign.center,
85 - style: theme.textTheme.bodySmall?.copyWith(
86 - color: theme.colorScheme.onSurface.withValues(alpha: 0.7),
87 - height: 1.5,
88 - ),
89 - ),
90 - const SizedBox(height: 24),
91 - _buildServerCard(
92 - type: ServerType.onDevice,
93 - title: l10n.server_type_on_device,
94 - subtitle: l10n.server_type_on_device_sub,
95 - iconWidget: Icon(
96 - Icons.phone_android_rounded,
97 - color: _selectedType == ServerType.onDevice
98 - ? theme.colorScheme.primary
99 - : theme.colorScheme.onSurface,
100 - ),
101 - theme: theme,
102 - ),
103 - const SizedBox(height: 16),
104 - _buildServerCard(
105 - type: ServerType.lmStudio,
106 - title: l10n.server_type_lm_studio,
107 - subtitle: l10n.server_type_lm_studio_sub,
108 - iconWidget: Icon(
109 - Icons.terminal_rounded,
110 - color: _selectedType == ServerType.lmStudio
111 - ? theme.colorScheme.primary
112 - : theme.colorScheme.onSurface,
113 - ),
114 - theme: theme,
115 - ),
116 - const SizedBox(height: 16),
117 - _buildServerCard(
118 - type: ServerType.ollama,
119 - title: l10n.server_type_ollama,
120 - subtitle: l10n.server_type_ollama_sub,
121 - iconWidget: Icon(
122 - Icons.smart_toy_rounded,
123 - color: _selectedType == ServerType.ollama
124 - ? theme.colorScheme.primary
125 - : theme.colorScheme.onSurface,
64 + body: Cue.onMount(
65 + motion: .smooth(),
66 + child: Column(
67 + crossAxisAlignment: CrossAxisAlignment.stretch,
68 + children: [
69 + Expanded(
70 + child: SingleChildScrollView(
71 + padding: const EdgeInsets.symmetric(horizontal: 24.0),
72 + child: Column(
73 + crossAxisAlignment: CrossAxisAlignment.stretch,
74 + children: [
75 + Actor(
76 + acts: [
77 + .fadeIn(),
78 + .slideY(from: 0.08),
79 + ],
80 + child: Column(
81 + crossAxisAlignment: CrossAxisAlignment.stretch,
82 + children: [
83 + const SizedBox(height: 24),
84 + Text(
85 + l10n.onboarding_connect_server,
86 + textAlign: TextAlign.center,
87 + style: theme.textTheme.headlineLarge?.copyWith(
88 + fontWeight: FontWeight.w800,
89 + height: 1.1,
90 + ),
91 + ),
92 + const SizedBox(height: 16),
93 + Text(
94 + l10n.onboarding_connect_desc,
95 + textAlign: TextAlign.center,
96 + style: theme.textTheme.bodySmall?.copyWith(
97 + color: theme.colorScheme.onSurface.withValues(alpha: 0.7),
98 + height: 1.5,
99 + ),
100 + ),
101 + ],
102 + ),
103 ),
127 - theme: theme,
128 - ),
129 - const SizedBox(height: 16),
130 - _buildServerCard(
131 - type: ServerType.openRouter,
132 - title: l10n.server_type_openrouter,
133 - subtitle: l10n.server_type_openrouter_sub,
134 - iconWidget: HugeIcon(
135 - icon: HugeIcons.strokeRoundedCloudServer,
136 - color: _selectedType == ServerType.openRouter
137 - ? theme.colorScheme.primary
138 - : theme.colorScheme.onSurface,
104 + Actor(
105 + delay: 60.ms,
106 + acts: [
107 + .fadeIn(),
108 + .slideY(from: 0.08),
109 + ],
110 + child: Column(
111 + crossAxisAlignment: CrossAxisAlignment.stretch,
112 + children: [
113 + const SizedBox(height: 24),
114 + _buildServerCard(
115 + type: ServerType.onDevice,
116 + title: l10n.server_type_on_device,
117 + subtitle: l10n.server_type_on_device_sub,
118 + iconWidget: Icon(
119 + Icons.phone_android_rounded,
120 + color: _selectedType == ServerType.onDevice
121 + ? theme.colorScheme.primary
122 + : theme.colorScheme.onSurface,
123 + ),
124 + theme: theme,
125 + ),
126 + const SizedBox(height: 16),
127 + _buildServerCard(
128 + type: ServerType.lmStudio,
129 + title: l10n.server_type_lm_studio,
130 + subtitle: l10n.server_type_lm_studio_sub,
131 + iconWidget: Icon(
132 + Icons.terminal_rounded,
133 + color: _selectedType == ServerType.lmStudio
134 + ? theme.colorScheme.primary
135 + : theme.colorScheme.onSurface,
136 + ),
137 + theme: theme,
138 + ),
139 + const SizedBox(height: 16),
140 + _buildServerCard(
141 + type: ServerType.ollama,
142 + title: l10n.server_type_ollama,
143 + subtitle: l10n.server_type_ollama_sub,
144 + iconWidget: Icon(
145 + Icons.smart_toy_rounded,
146 + color: _selectedType == ServerType.ollama
147 + ? theme.colorScheme.primary
148 + : theme.colorScheme.onSurface,
149 + ),
150 + theme: theme,
151 + ),
152 + const SizedBox(height: 16),
153 + _buildServerCard(
154 + type: ServerType.openRouter,
155 + title: l10n.server_type_openrouter,
156 + subtitle: l10n.server_type_openrouter_sub,
157 + iconWidget: HugeIcon(
158 + icon: HugeIcons.strokeRoundedCloudServer,
159 + color: _selectedType == ServerType.openRouter
160 + ? theme.colorScheme.primary
161 + : theme.colorScheme.onSurface,
162 + ),
163 + theme: theme,
164 + ),
165 + ],
166 + ),
167 ),
140 - theme: theme,
141 - ),
142 - const SizedBox(height: 32),
143 - ],
168 + const SizedBox(height: 32),
169 + ],
170 + ),
171 ),
172 ),
146 - ),
147 - Padding(
148 - padding: const EdgeInsets.symmetric(horizontal: 24.0),
149 - child: Column(
150 - children: [
151 - // Status Indicator
152 - Row(
153 - mainAxisAlignment: MainAxisAlignment.center,
173 + Actor(
174 + delay: 120.ms,
175 + acts: [
176 + .fadeIn(),
177 + .slideY(from: 0.08),
178 + ],
179 + child: Padding(
180 + padding: const EdgeInsets.symmetric(horizontal: 24.0),
181 + child: Column(
182 children: [
155 - Container(
156 - width: 8,
157 - height: 8,
158 - decoration: BoxDecoration(
159 - color: _selectedType != null
160 - ? theme.colorScheme.primary
161 - : theme.colorScheme.onSurface.withValues(
162 - alpha: 0.4,
163 - ),
164 - shape: BoxShape.circle,
165 - ),
183 + // Status Indicator
184 + Row(
185 + mainAxisAlignment: MainAxisAlignment.center,
186 + children: [
187 + Container(
188 + width: 8,
189 + height: 8,
190 + decoration: BoxDecoration(
191 + color: _selectedType != null
192 + ? theme.colorScheme.primary
193 + : theme.colorScheme.onSurface.withValues(
194 + alpha: 0.4,
195 + ),
196 + shape: BoxShape.circle,
197 + ),
198 + ),
199 + const SizedBox(width: 8),
200 + Text(
201 + _selectedType != null
202 + ? l10n.ready_continue
203 + : l10n.waiting_selection,
204 + style: theme.textTheme.labelSmall?.copyWith(
205 + color: theme.colorScheme.onSurface.withValues(
206 + alpha: 0.5,
207 + ),
208 + letterSpacing: 2,
209 + fontWeight: FontWeight.w600,
210 + ),
211 + ),
212 + ],
213 ),
167 - const SizedBox(width: 8),
168 - Text(
169 - _selectedType != null
170 - ? l10n.ready_continue
171 - : l10n.waiting_selection,
172 - style: theme.textTheme.labelSmall?.copyWith(
173 - color: theme.colorScheme.onSurface.withValues(
174 - alpha: 0.5,
214 + const SizedBox(height: 16),
215 + ShadButton(
216 + width: double.infinity,
217 + enabled: _selectedType != null,
218 + onPressed: () {
219 + if (_selectedType != null) {
220 + if (_selectedType == ServerType.onDevice) {
221 + context.push(AppRoutes.onboardingModelDownload);
222 + } else {
223 + context.push(
224 + AppRoutes.onboardingSetup,
225 + extra: _selectedType,
226 + );
227 + }
228 + }
229 + },
230 + child: Text(
231 + l10n.continue_action,
232 + style: const TextStyle(
233 + fontSize: 16,
234 + fontWeight: FontWeight.w600,
235 ),
176 - letterSpacing: 2,
177 - fontWeight: FontWeight.w600,
236 ),
237 ),
238 + SizedBox(
239 + height: MediaQuery.of(context).padding.bottom,
240 + ), // Reduced bottom padding slightly for small screens
241 ],
242 ),
182 - const SizedBox(height: 16),
183 - ShadButton(
184 - width: double.infinity,
185 - enabled: _selectedType != null,
186 - onPressed: () {
187 - if (_selectedType != null) {
188 - if (_selectedType == ServerType.onDevice) {
189 - context.push(AppRoutes.onboardingModelDownload);
190 - } else {
191 - context.push(
192 - AppRoutes.onboardingSetup,
193 - extra: _selectedType,
194 - );
195 - }
196 - }
197 - },
198 - child: Text(
199 - l10n.continue_action,
200 - style: const TextStyle(
201 - fontSize: 16,
202 - fontWeight: FontWeight.w600,
203 - ),
204 - ),
205 - ),
206 - SizedBox(
207 - height: MediaQuery.of(context).padding.bottom,
208 - ), // Reduced bottom padding slightly for small screens
209 - ],
243 + ),
244 ),
211 - ),
212 - ],
245 + ],
246 + ),
247 ),
248 );
249 }
lib/features/onboarding/screens/onboarding_theme_screen.dart
+107 -83
@@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
3 import 'package:go_router/go_router.dart';
4 import 'package:hugeicons/hugeicons.dart';
5 import 'package:shadcn_ui/shadcn_ui.dart';
6 +import 'package:cue/cue.dart';
7
8 import '../../../core/providers/app_providers.dart';
9 import '../../../core/routes/app_routes.dart';
@@ -35,95 +36,118 @@ class _OnboardingThemeScreenState extends ConsumerState<OnboardingThemeScreen> {
36 ),
37 ),
38 body: SafeArea(
38 - child: Padding(
39 - padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 16.0),
40 - child: Column(
41 - crossAxisAlignment: CrossAxisAlignment.stretch,
42 - children: [
43 - Text(
44 - l10n.choose_theme_desc,
45 - style: theme.textTheme.titleMedium?.copyWith(
46 - color: theme.colorScheme.onSurface.withValues(alpha: 0.7),
39 + child: Cue.onMount(
40 + motion: .smooth(),
41 + child: Padding(
42 + padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 16.0),
43 + child: Column(
44 + crossAxisAlignment: CrossAxisAlignment.stretch,
45 + children: [
46 + Actor(
47 + acts: [
48 + .fadeIn(),
49 + .slideY(from: 0.08),
50 + ],
51 + child: Text(
52 + l10n.choose_theme_desc,
53 + style: theme.textTheme.titleMedium?.copyWith(
54 + color: theme.colorScheme.onSurface.withValues(alpha: 0.7),
55 + ),
56 + ),
57 ),
48 - ),
49 - const SizedBox(height: 32),
58 + const SizedBox(height: 32),
59
51 - Expanded(
52 - child: ListView(
53 - children: [
54 - _buildThemeCard(
55 - type: AppThemeType.system,
56 - title: l10n.theme_card_system,
57 - subtitle: l10n.theme_card_system_sub,
58 - iconWidget: HugeIcon(
59 - icon: HugeIcons.strokeRoundedSettings01,
60 - color: currentTheme == AppThemeType.system
61 - ? theme.colorScheme.primary
62 - : theme.colorScheme.onSurface,
63 - ),
64 - currentTheme: currentTheme,
65 - context: context,
66 - ),
67 - const SizedBox(height: 16),
68 - _buildThemeCard(
69 - type: AppThemeType.light,
70 - title: l10n.theme_card_light,
71 - subtitle: l10n.theme_card_light_sub,
72 - iconWidget: HugeIcon(
73 - icon: HugeIcons.strokeRoundedSun01,
74 - color: currentTheme == AppThemeType.light
75 - ? theme.colorScheme.primary
76 - : theme.colorScheme.onSurface,
77 - ),
78 - currentTheme: currentTheme,
79 - context: context,
80 - ),
81 - const SizedBox(height: 16),
82 - _buildThemeCard(
83 - type: AppThemeType.dark,
84 - title: l10n.theme_card_dark,
85 - subtitle: l10n.theme_card_dark_sub,
86 - iconWidget: HugeIcon(
87 - icon: HugeIcons.strokeRoundedMoon02,
88 - color: currentTheme == AppThemeType.dark
89 - ? theme.colorScheme.primary
90 - : theme.colorScheme.onSurface,
91 - ),
92 - currentTheme: currentTheme,
93 - context: context,
94 - ),
95 - const SizedBox(height: 16),
96 - _buildThemeCard(
97 - type: AppThemeType.claude,
98 - title: l10n.theme_card_claude,
99 - subtitle: l10n.theme_card_claude_sub,
100 - iconWidget: HugeIcon(
101 - icon: HugeIcons.strokeRoundedPaintBrush02,
102 - color: currentTheme == AppThemeType.claude
103 - ? theme.colorScheme.primary
104 - : theme.colorScheme.onSurface,
105 - ),
106 - currentTheme: currentTheme,
107 - context: context,
60 + Expanded(
61 + child: Actor(
62 + delay: 60.ms,
63 + acts: [
64 + .fadeIn(),
65 + .slideY(from: 0.08),
66 + ],
67 + child: ListView(
68 + children: [
69 + _buildThemeCard(
70 + type: AppThemeType.system,
71 + title: l10n.theme_card_system,
72 + subtitle: l10n.theme_card_system_sub,
73 + iconWidget: HugeIcon(
74 + icon: HugeIcons.strokeRoundedSettings01,
75 + color: currentTheme == AppThemeType.system
76 + ? theme.colorScheme.primary
77 + : theme.colorScheme.onSurface,
78 + ),
79 + currentTheme: currentTheme,
80 + context: context,
81 + ),
82 + const SizedBox(height: 16),
83 + _buildThemeCard(
84 + type: AppThemeType.light,
85 + title: l10n.theme_card_light,
86 + subtitle: l10n.theme_card_light_sub,
87 + iconWidget: HugeIcon(
88 + icon: HugeIcons.strokeRoundedSun01,
89 + color: currentTheme == AppThemeType.light
90 + ? theme.colorScheme.primary
91 + : theme.colorScheme.onSurface,
92 + ),
93 + currentTheme: currentTheme,
94 + context: context,
95 + ),
96 + const SizedBox(height: 16),
97 + _buildThemeCard(
98 + type: AppThemeType.dark,
99 + title: l10n.theme_card_dark,
100 + subtitle: l10n.theme_card_dark_sub,
101 + iconWidget: HugeIcon(
102 + icon: HugeIcons.strokeRoundedMoon02,
103 + color: currentTheme == AppThemeType.dark
104 + ? theme.colorScheme.primary
105 + : theme.colorScheme.onSurface,
106 + ),
107 + currentTheme: currentTheme,
108 + context: context,
109 + ),
110 + const SizedBox(height: 16),
111 + _buildThemeCard(
112 + type: AppThemeType.claude,
113 + title: l10n.theme_card_claude,
114 + subtitle: l10n.theme_card_claude_sub,
115 + iconWidget: HugeIcon(
116 + icon: HugeIcons.strokeRoundedPaintBrush02,
117 + color: currentTheme == AppThemeType.claude
118 + ? theme.colorScheme.primary
119 + : theme.colorScheme.onSurface,
120 + ),
121 + currentTheme: currentTheme,
122 + context: context,
123 + ),
124 + ],
125 ),
109 - ],
126 + ),
127 ),
111 - ),
128
113 - const SizedBox(height: 24),
114 - ShadButton(
115 - width: double.infinity,
116 - onPressed: () async {
117 - if (context.mounted) {
118 - context.push(AppRoutes.onboardingNotifications);
119 - }
120 - },
121 - child: Text(
122 - l10n.finish_setup,
123 - style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
129 + const SizedBox(height: 24),
130 + Actor(
131 + delay: 120.ms,
132 + acts: [
133 + .fadeIn(),
134 + .slideY(from: 0.08),
135 + ],
136 + child: ShadButton(
137 + width: double.infinity,
138 + onPressed: () async {
139 + if (context.mounted) {
140 + context.push(AppRoutes.onboardingNotifications);
141 + }
142 + },
143 + child: Text(
144 + l10n.finish_setup,
145 + style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
146 + ),
147 + ),
148 ),
125 - ),
126 - ],
149 + ],
150 + ),
151 ),
152 ),
153 ),
pubspec.lock
+16 -8
@@ -273,6 +273,14 @@ packages:
273 url: "https://pub.dev"
274 source: hosted
275 version: "3.0.7"
276 + cue:
277 + dependency: "direct main"
278 + description:
279 + name: cue
280 + sha256: c98b4a3fffcc1726113d0b4d94d70fa2f9c8061382bddb2e456726392053eb4c
281 + url: "https://pub.dev"
282 + source: hosted
283 + version: "0.3.1"
284 cupertino_icons:
285 dependency: "direct main"
286 description:
@@ -801,10 +809,10 @@ packages:
809 dependency: transitive
810 description:
811 name: matcher
804 - sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6"
812 + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
813 url: "https://pub.dev"
814 source: hosted
807 - version: "0.12.18"
815 + version: "0.12.19"
816 material_color_utilities:
817 dependency: transitive
818 description:
@@ -1367,26 +1375,26 @@ packages:
1375 dependency: transitive
1376 description:
1377 name: test
1370 - sha256: "54c516bbb7cee2754d327ad4fca637f78abfc3cbcc5ace83b3eda117e42cd71a"
1378 + sha256: "280d6d890011ca966ad08df7e8a4ddfab0fb3aa49f96ed6de56e3521347a9ae7"
1379 url: "https://pub.dev"
1380 source: hosted
1373 - version: "1.29.0"
1381 + version: "1.30.0"
1382 test_api:
1383 dependency: transitive
1384 description:
1385 name: test_api
1378 - sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636"
1386 + sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a"
1387 url: "https://pub.dev"
1388 source: hosted
1381 - version: "0.7.9"
1389 + version: "0.7.10"
1390 test_core:
1391 dependency: transitive
1392 description:
1393 name: test_core
1386 - sha256: "394f07d21f0f2255ec9e3989f21e54d3c7dc0e6e9dbce160e5a9c1a6be0e2943"
1394 + sha256: "0381bd1585d1a924763c308100f2138205252fb90c9d4eeaf28489ee65ccde51"
1395 url: "https://pub.dev"
1396 source: hosted
1389 - version: "0.6.15"
1397 + version: "0.6.16"
1398 theme_extensions_builder_annotation:
1399 dependency: transitive
1400 description:
pubspec.yaml
+2 -1
@@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
16 # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
17 # In Windows, build-name is used as the major, minor, and patch parts
18 # of the product and file versions while build-number is used as the build suffix.
19 -version: 1.3.4+36
19 +version: 1.3.6+38
20
21 environment:
22 sdk: ^3.11.0
@@ -53,6 +53,7 @@ dependencies:
53 git:
54 url: https://github.com/abdulmominsakib/syntax_highlight.git
55 animations: 2.1.2
56 + cue: ^0.3.1
57 logger: ^2.7.0
58 shared_preferences: ^2.5.5
59 objectbox: ^5.3.1