添加 Solian for Apple Watch #8
15
ios/Podfile
15
ios/Podfile
@@ -1,6 +1,3 @@
|
|||||||
# Uncomment this line to define a global platform for your project
|
|
||||||
platform :ios, '15.0'
|
|
||||||
|
|
||||||
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||||
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||||
|
|
||||||
@@ -28,6 +25,8 @@ require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelpe
|
|||||||
flutter_ios_podfile_setup
|
flutter_ios_podfile_setup
|
||||||
|
|
||||||
target 'Runner' do
|
target 'Runner' do
|
||||||
|
platform :ios, '15.0'
|
||||||
|
|
||||||
use_frameworks!
|
use_frameworks!
|
||||||
use_modular_headers!
|
use_modular_headers!
|
||||||
|
|
||||||
@@ -50,6 +49,16 @@ target 'Runner' do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
target 'WatchRunner Watch App' do
|
||||||
|
platform :watchos, '11.0'
|
||||||
|
|
||||||
|
use_frameworks!
|
||||||
|
use_modular_headers!
|
||||||
|
|
||||||
|
pod 'Kingfisher', '~> 8.0'
|
||||||
|
pod 'KingfisherWebP'
|
||||||
|
end
|
||||||
|
|
||||||
post_install do |installer|
|
post_install do |installer|
|
||||||
installer.pods_project.targets.each do |target|
|
installer.pods_project.targets.each do |target|
|
||||||
flutter_additional_ios_build_settings(target)
|
flutter_additional_ios_build_settings(target)
|
||||||
|
|||||||
@@ -219,6 +219,21 @@ PODS:
|
|||||||
- irondash_engine_context (0.0.1):
|
- irondash_engine_context (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- Kingfisher (8.6.0)
|
- Kingfisher (8.6.0)
|
||||||
|
- KingfisherWebP (1.7.2):
|
||||||
|
- Kingfisher (~> 8.0)
|
||||||
|
- libwebp (>= 1.1.0)
|
||||||
|
- libwebp (1.5.0):
|
||||||
|
- libwebp/demux (= 1.5.0)
|
||||||
|
- libwebp/mux (= 1.5.0)
|
||||||
|
- libwebp/sharpyuv (= 1.5.0)
|
||||||
|
- libwebp/webp (= 1.5.0)
|
||||||
|
- libwebp/demux (1.5.0):
|
||||||
|
- libwebp/webp
|
||||||
|
- libwebp/mux (1.5.0):
|
||||||
|
- libwebp/demux
|
||||||
|
- libwebp/sharpyuv (1.5.0)
|
||||||
|
- libwebp/webp (1.5.0):
|
||||||
|
- libwebp/sharpyuv
|
||||||
- livekit_client (2.5.3):
|
- livekit_client (2.5.3):
|
||||||
- Flutter
|
- Flutter
|
||||||
- flutter_webrtc
|
- flutter_webrtc
|
||||||
@@ -333,6 +348,7 @@ DEPENDENCIES:
|
|||||||
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
|
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
|
||||||
- irondash_engine_context (from `.symlinks/plugins/irondash_engine_context/ios`)
|
- irondash_engine_context (from `.symlinks/plugins/irondash_engine_context/ios`)
|
||||||
- Kingfisher (~> 8.0)
|
- Kingfisher (~> 8.0)
|
||||||
|
- KingfisherWebP
|
||||||
- livekit_client (from `.symlinks/plugins/livekit_client/ios`)
|
- livekit_client (from `.symlinks/plugins/livekit_client/ios`)
|
||||||
- local_auth_darwin (from `.symlinks/plugins/local_auth_darwin/darwin`)
|
- local_auth_darwin (from `.symlinks/plugins/local_auth_darwin/darwin`)
|
||||||
- media_kit_libs_ios_video (from `.symlinks/plugins/media_kit_libs_ios_video/ios`)
|
- media_kit_libs_ios_video (from `.symlinks/plugins/media_kit_libs_ios_video/ios`)
|
||||||
@@ -375,6 +391,8 @@ SPEC REPOS:
|
|||||||
- GoogleDataTransport
|
- GoogleDataTransport
|
||||||
- GoogleUtilities
|
- GoogleUtilities
|
||||||
- Kingfisher
|
- Kingfisher
|
||||||
|
- KingfisherWebP
|
||||||
|
- libwebp
|
||||||
- nanopb
|
- nanopb
|
||||||
- OrderedSet
|
- OrderedSet
|
||||||
- PromisesObjC
|
- PromisesObjC
|
||||||
@@ -520,6 +538,8 @@ SPEC CHECKSUMS:
|
|||||||
image_picker_ios: e0ece4aa2a75771a7de3fa735d26d90817041326
|
image_picker_ios: e0ece4aa2a75771a7de3fa735d26d90817041326
|
||||||
irondash_engine_context: 8e58ca8e0212ee9d1c7dc6a42121849986c88486
|
irondash_engine_context: 8e58ca8e0212ee9d1c7dc6a42121849986c88486
|
||||||
Kingfisher: 64278f126a815d0e2d391cdf71311b85882c4de0
|
Kingfisher: 64278f126a815d0e2d391cdf71311b85882c4de0
|
||||||
|
KingfisherWebP: 38b9721821947f547afb78f933f75f4f9e0ae402
|
||||||
|
libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8
|
||||||
livekit_client: 86c8af579274e4b7a215185a8080db2d4e176f40
|
livekit_client: 86c8af579274e4b7a215185a8080db2d4e176f40
|
||||||
local_auth_darwin: c3ee6cce0a8d56be34c8ccb66ba31f7f180aaebb
|
local_auth_darwin: c3ee6cce0a8d56be34c8ccb66ba31f7f180aaebb
|
||||||
media_kit_libs_ios_video: 5a18affdb97d1f5d466dc79988b13eff6c5e2854
|
media_kit_libs_ios_video: 5a18affdb97d1f5d466dc79988b13eff6c5e2854
|
||||||
@@ -551,6 +571,6 @@ SPEC CHECKSUMS:
|
|||||||
wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556
|
wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556
|
||||||
WebRTC-SDK: 40d4f5ba05cadff14e4db5614aec402a633f007e
|
WebRTC-SDK: 40d4f5ba05cadff14e4db5614aec402a633f007e
|
||||||
|
|
||||||
PODFILE CHECKSUM: c818292390b02fa379036ea099713a332bd7193f
|
PODFILE CHECKSUM: 3096dc559be56aca856e757e1dc65ca039801e2e
|
||||||
|
|
||||||
COCOAPODS: 1.16.2
|
COCOAPODS: 1.16.2
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
||||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
||||||
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
||||||
|
A1D34487886D362AC8B99B2E /* Pods_WatchRunner_Watch_App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 802C1CFCA7F1E069AAEFB454 /* Pods_WatchRunner_Watch_App.framework */; };
|
||||||
B87C0E607033790E71B54D73 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F6D834CA86410B09796B312B /* Pods_Runner.framework */; };
|
B87C0E607033790E71B54D73 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F6D834CA86410B09796B312B /* Pods_Runner.framework */; };
|
||||||
D174D53FF3E8EA943491A5CC /* Pods_SolianShareExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7B40764A2C4CC0E7DC70A0D3 /* Pods_SolianShareExtension.framework */; };
|
D174D53FF3E8EA943491A5CC /* Pods_SolianShareExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7B40764A2C4CC0E7DC70A0D3 /* Pods_SolianShareExtension.framework */; };
|
||||||
D1772CE196985AE8E8C9F2E5 /* Pods_SolianNotificationService.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 39FE4CC6223F0D3C0E1FFD04 /* Pods_SolianNotificationService.framework */; };
|
D1772CE196985AE8E8C9F2E5 /* Pods_SolianNotificationService.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 39FE4CC6223F0D3C0E1FFD04 /* Pods_SolianNotificationService.framework */; };
|
||||||
@@ -96,6 +97,7 @@
|
|||||||
/* End PBXCopyFilesBuildPhase section */
|
/* End PBXCopyFilesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
|
103EA2362B9E9F127016A1F1 /* Pods-WatchRunner Watch App.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WatchRunner Watch App.profile.xcconfig"; path = "Target Support Files/Pods-WatchRunner Watch App/Pods-WatchRunner Watch App.profile.xcconfig"; sourceTree = "<group>"; };
|
||||||
14118AC858B441AB16B7309E /* 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>"; };
|
14118AC858B441AB16B7309E /* 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>"; };
|
||||||
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
||||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
||||||
@@ -124,6 +126,8 @@
|
|||||||
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
|
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
|
||||||
7B40764A2C4CC0E7DC70A0D3 /* Pods_SolianShareExtension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SolianShareExtension.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
7B40764A2C4CC0E7DC70A0D3 /* Pods_SolianShareExtension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SolianShareExtension.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
802C1CFCA7F1E069AAEFB454 /* Pods_WatchRunner_Watch_App.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_WatchRunner_Watch_App.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
86D60BA96DA647E1B11AA7F0 /* Pods-WatchRunner Watch App.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WatchRunner Watch App.debug.xcconfig"; path = "Target Support Files/Pods-WatchRunner Watch App/Pods-WatchRunner Watch App.debug.xcconfig"; sourceTree = "<group>"; };
|
||||||
8B40620B1EEBB09456406A3C /* Pods-SolianNotificationService.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SolianNotificationService.profile.xcconfig"; path = "Target Support Files/Pods-SolianNotificationService/Pods-SolianNotificationService.profile.xcconfig"; sourceTree = "<group>"; };
|
8B40620B1EEBB09456406A3C /* Pods-SolianNotificationService.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SolianNotificationService.profile.xcconfig"; path = "Target Support Files/Pods-SolianNotificationService/Pods-SolianNotificationService.profile.xcconfig"; sourceTree = "<group>"; };
|
||||||
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
|
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
|
||||||
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
|
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
|
||||||
@@ -133,6 +137,7 @@
|
|||||||
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||||
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
9AE244813FCDFAA941430393 /* GoogleService-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "Runner/GoogleService-Info.plist"; sourceTree = "<group>"; };
|
9AE244813FCDFAA941430393 /* GoogleService-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "Runner/GoogleService-Info.plist"; sourceTree = "<group>"; };
|
||||||
|
A2EB1DAFDE9B8E6D88BBF7A3 /* Pods-WatchRunner Watch App.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WatchRunner Watch App.release.xcconfig"; path = "Target Support Files/Pods-WatchRunner Watch App/Pods-WatchRunner Watch App.release.xcconfig"; sourceTree = "<group>"; };
|
||||||
A499FDB2082EB000933AA8C5 /* 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>"; };
|
A499FDB2082EB000933AA8C5 /* 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>"; };
|
||||||
A85FF612AE7623A9934E57CE /* Pods-SolianShareExtension.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SolianShareExtension.profile.xcconfig"; path = "Target Support Files/Pods-SolianShareExtension/Pods-SolianShareExtension.profile.xcconfig"; sourceTree = "<group>"; };
|
A85FF612AE7623A9934E57CE /* Pods-SolianShareExtension.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SolianShareExtension.profile.xcconfig"; path = "Target Support Files/Pods-SolianShareExtension/Pods-SolianShareExtension.profile.xcconfig"; sourceTree = "<group>"; };
|
||||||
AA0CA8A3E15DEE023BB27438 /* Pods_NotificationService.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_NotificationService.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
AA0CA8A3E15DEE023BB27438 /* Pods_NotificationService.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_NotificationService.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
@@ -227,6 +232,7 @@
|
|||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
A1D34487886D362AC8B99B2E /* Pods_WatchRunner_Watch_App.framework in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@@ -283,6 +289,7 @@
|
|||||||
7B40764A2C4CC0E7DC70A0D3 /* Pods_SolianShareExtension.framework */,
|
7B40764A2C4CC0E7DC70A0D3 /* Pods_SolianShareExtension.framework */,
|
||||||
73ACDFAC2E3D0E6100B63535 /* ReplayKit.framework */,
|
73ACDFAC2E3D0E6100B63535 /* ReplayKit.framework */,
|
||||||
73ACDFB82E3D0E6100B63535 /* UIKit.framework */,
|
73ACDFB82E3D0E6100B63535 /* UIKit.framework */,
|
||||||
|
802C1CFCA7F1E069AAEFB454 /* Pods_WatchRunner_Watch_App.framework */,
|
||||||
);
|
);
|
||||||
name = Frameworks;
|
name = Frameworks;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -305,6 +312,9 @@
|
|||||||
17FAB080A9C53193ABD9C15B /* Pods-SolianShareExtension.debug.xcconfig */,
|
17FAB080A9C53193ABD9C15B /* Pods-SolianShareExtension.debug.xcconfig */,
|
||||||
27C66EFB5A705F1A822C3EB0 /* Pods-SolianShareExtension.release.xcconfig */,
|
27C66EFB5A705F1A822C3EB0 /* Pods-SolianShareExtension.release.xcconfig */,
|
||||||
A85FF612AE7623A9934E57CE /* Pods-SolianShareExtension.profile.xcconfig */,
|
A85FF612AE7623A9934E57CE /* Pods-SolianShareExtension.profile.xcconfig */,
|
||||||
|
86D60BA96DA647E1B11AA7F0 /* Pods-WatchRunner Watch App.debug.xcconfig */,
|
||||||
|
A2EB1DAFDE9B8E6D88BBF7A3 /* Pods-WatchRunner Watch App.release.xcconfig */,
|
||||||
|
103EA2362B9E9F127016A1F1 /* Pods-WatchRunner Watch App.profile.xcconfig */,
|
||||||
);
|
);
|
||||||
path = Pods;
|
path = Pods;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -394,9 +404,11 @@
|
|||||||
isa = PBXNativeTarget;
|
isa = PBXNativeTarget;
|
||||||
buildConfigurationList = 7310A7E32EB10963002C0FD3 /* Build configuration list for PBXNativeTarget "WatchRunner Watch App" */;
|
buildConfigurationList = 7310A7E32EB10963002C0FD3 /* Build configuration list for PBXNativeTarget "WatchRunner Watch App" */;
|
||||||
buildPhases = (
|
buildPhases = (
|
||||||
|
DDEDA1BA6278B94F0F7B9B61 /* [CP] Check Pods Manifest.lock */,
|
||||||
7310A7D02EB10962002C0FD3 /* Sources */,
|
7310A7D02EB10962002C0FD3 /* Sources */,
|
||||||
7310A7D12EB10962002C0FD3 /* Frameworks */,
|
7310A7D12EB10962002C0FD3 /* Frameworks */,
|
||||||
7310A7D22EB10962002C0FD3 /* Resources */,
|
7310A7D22EB10962002C0FD3 /* Resources */,
|
||||||
|
C74B07D6587D29C67A198025 /* [CP] Embed Pods Frameworks */,
|
||||||
);
|
);
|
||||||
buildRules = (
|
buildRules = (
|
||||||
);
|
);
|
||||||
@@ -750,6 +762,49 @@
|
|||||||
shellPath = /bin/sh;
|
shellPath = /bin/sh;
|
||||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
|
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
|
||||||
};
|
};
|
||||||
|
C74B07D6587D29C67A198025 /* [CP] Embed Pods Frameworks */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputFileListPaths = (
|
||||||
|
"${PODS_ROOT}/Target Support Files/Pods-WatchRunner Watch App/Pods-WatchRunner Watch App-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
);
|
||||||
|
name = "[CP] Embed Pods Frameworks";
|
||||||
|
outputFileListPaths = (
|
||||||
|
"${PODS_ROOT}/Target Support Files/Pods-WatchRunner Watch App/Pods-WatchRunner Watch App-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
||||||
|
);
|
||||||
|
outputPaths = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-WatchRunner Watch App/Pods-WatchRunner Watch App-frameworks.sh\"\n";
|
||||||
|
showEnvVarsInLog = 0;
|
||||||
|
};
|
||||||
|
DDEDA1BA6278B94F0F7B9B61 /* [CP] Check Pods Manifest.lock */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputFileListPaths = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||||
|
"${PODS_ROOT}/Manifest.lock",
|
||||||
|
);
|
||||||
|
name = "[CP] Check Pods Manifest.lock";
|
||||||
|
outputFileListPaths = (
|
||||||
|
);
|
||||||
|
outputPaths = (
|
||||||
|
"$(DERIVED_FILE_DIR)/Pods-WatchRunner Watch App-checkManifestLockResult.txt",
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
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";
|
||||||
|
showEnvVarsInLog = 0;
|
||||||
|
};
|
||||||
E86CDE9D6464F4F52B910856 /* FlutterFire: "flutterfire upload-crashlytics-symbols" */ = {
|
E86CDE9D6464F4F52B910856 /* FlutterFire: "flutterfire upload-crashlytics-symbols" */ = {
|
||||||
isa = PBXShellScriptBuildPhase;
|
isa = PBXShellScriptBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
@@ -1019,6 +1074,7 @@
|
|||||||
};
|
};
|
||||||
7310A7E02EB10963002C0FD3 /* Debug */ = {
|
7310A7E02EB10963002C0FD3 /* Debug */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 86D60BA96DA647E1B11AA7F0 /* Pods-WatchRunner Watch App.debug.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||||
@@ -1033,7 +1089,7 @@
|
|||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEVELOPMENT_TEAM = W7HPZ53V6B;
|
DEVELOPMENT_TEAM = W7HPZ53V6B;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = WatchRunner;
|
INFOPLIST_KEY_CFBundleDisplayName = WatchRunner;
|
||||||
@@ -1066,6 +1122,7 @@
|
|||||||
};
|
};
|
||||||
7310A7E12EB10963002C0FD3 /* Release */ = {
|
7310A7E12EB10963002C0FD3 /* Release */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = A2EB1DAFDE9B8E6D88BBF7A3 /* Pods-WatchRunner Watch App.release.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||||
@@ -1080,7 +1137,7 @@
|
|||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEVELOPMENT_TEAM = W7HPZ53V6B;
|
DEVELOPMENT_TEAM = W7HPZ53V6B;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = WatchRunner;
|
INFOPLIST_KEY_CFBundleDisplayName = WatchRunner;
|
||||||
@@ -1110,6 +1167,7 @@
|
|||||||
};
|
};
|
||||||
7310A7E22EB10963002C0FD3 /* Profile */ = {
|
7310A7E22EB10963002C0FD3 /* Profile */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 103EA2362B9E9F127016A1F1 /* Pods-WatchRunner Watch App.profile.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||||
@@ -1124,7 +1182,7 @@
|
|||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEVELOPMENT_TEAM = W7HPZ53V6B;
|
DEVELOPMENT_TEAM = W7HPZ53V6B;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = WatchRunner;
|
INFOPLIST_KEY_CFBundleDisplayName = WatchRunner;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
//
|
//
|
||||||
// ContentView.swift
|
// ContentView.swift
|
||||||
// WatchRunner Watch App
|
// WatchRunner Watch App
|
||||||
@@ -8,6 +9,8 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import Combine
|
import Combine
|
||||||
import WatchConnectivity
|
import WatchConnectivity
|
||||||
|
import Kingfisher // Import Kingfisher
|
||||||
|
import KingfisherWebP // Import KingfisherWebP
|
||||||
|
|
||||||
// MARK: - App State
|
// MARK: - App State
|
||||||
|
|
||||||
@@ -139,8 +142,11 @@ enum ActivityData: Codable {
|
|||||||
|
|
||||||
struct SnPost: Codable, Identifiable {
|
struct SnPost: Codable, Identifiable {
|
||||||
let id: String
|
let id: String
|
||||||
let content: String?
|
|
||||||
let title: String?
|
let title: String?
|
||||||
|
let content: String?
|
||||||
|
let publisher: SnPublisher
|
||||||
|
let attachments: [SnCloudFile]
|
||||||
|
let tags: [SnPostTag]
|
||||||
}
|
}
|
||||||
|
|
||||||
struct DiscoveryData: Codable {
|
struct DiscoveryData: Codable {
|
||||||
@@ -194,7 +200,20 @@ struct SnRealm: Codable, Identifiable {
|
|||||||
struct SnPublisher: Codable, Identifiable {
|
struct SnPublisher: Codable, Identifiable {
|
||||||
let id: String
|
let id: String
|
||||||
let name: String
|
let name: String
|
||||||
|
let nick: String?
|
||||||
let description: String?
|
let description: String?
|
||||||
|
let picture: SnCloudFile?
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SnCloudFile: Codable, Identifiable {
|
||||||
|
let id: String
|
||||||
|
let mimeType: String?
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SnPostTag: Codable, Identifiable {
|
||||||
|
let id: String
|
||||||
|
let slug: String
|
||||||
|
let name: String?
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SnWebArticle: Codable, Identifiable {
|
struct SnWebArticle: Codable, Identifiable {
|
||||||
@@ -203,6 +222,18 @@ struct SnWebArticle: Codable, Identifiable {
|
|||||||
let url: String
|
let url: String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Helper Functions
|
||||||
|
|
||||||
|
func getAttachmentUrl(for fileId: String, serverUrl: String) -> URL? {
|
||||||
|
let urlString: String
|
||||||
|
if fileId.starts(with: "http") {
|
||||||
|
urlString = fileId
|
||||||
|
} else {
|
||||||
|
urlString = "\(serverUrl)/drive/files/\(fileId)"
|
||||||
|
}
|
||||||
|
print("[watchOS] Generated image URL: \(urlString)")
|
||||||
|
return URL(string: urlString)
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Network Service
|
// MARK: - Network Service
|
||||||
|
|
||||||
@@ -250,13 +281,19 @@ class ActivityViewModel: ObservableObject {
|
|||||||
|
|
||||||
private let networkService = NetworkService()
|
private let networkService = NetworkService()
|
||||||
let filter: String
|
let filter: String
|
||||||
|
private var isMock = false
|
||||||
|
|
||||||
init(filter: String) {
|
init(filter: String, mockActivities: [SnActivity]? = nil) {
|
||||||
self.filter = filter
|
self.filter = filter
|
||||||
|
if let mockActivities = mockActivities {
|
||||||
|
self.activities = mockActivities
|
||||||
|
self.isMock = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchActivities(appState: AppState) async {
|
func fetchActivities(token: String, serverUrl: String) async {
|
||||||
guard !isLoading, appState.isReady, let token = appState.token, let serverUrl = appState.serverUrl else { return }
|
if isMock { return }
|
||||||
|
guard !isLoading else { return }
|
||||||
isLoading = true
|
isLoading = true
|
||||||
errorMessage = nil
|
errorMessage = nil
|
||||||
|
|
||||||
@@ -272,14 +309,178 @@ class ActivityViewModel: ObservableObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Custom Layouts
|
||||||
|
|
||||||
|
struct FlowLayout: Layout {
|
||||||
|
var alignment: HorizontalAlignment = .leading
|
||||||
|
var spacing: CGFloat = 10
|
||||||
|
|
||||||
|
func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize {
|
||||||
|
let containerWidth = proposal.width ?? 0
|
||||||
|
let sizes = subviews.map { $0.sizeThatFits(.unspecified) }
|
||||||
|
|
||||||
|
var currentX: CGFloat = 0
|
||||||
|
var currentY: CGFloat = 0
|
||||||
|
var lineHeight: CGFloat = 0
|
||||||
|
var totalHeight: CGFloat = 0
|
||||||
|
|
||||||
|
for size in sizes {
|
||||||
|
if currentX + size.width > containerWidth {
|
||||||
|
// New line
|
||||||
|
currentX = 0
|
||||||
|
currentY += lineHeight + spacing
|
||||||
|
totalHeight = currentY + size.height
|
||||||
|
lineHeight = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
currentX += size.width + spacing
|
||||||
|
lineHeight = max(lineHeight, size.height)
|
||||||
|
}
|
||||||
|
totalHeight = currentY + lineHeight
|
||||||
|
|
||||||
|
return CGSize(width: containerWidth, height: totalHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) {
|
||||||
|
let containerWidth = bounds.width
|
||||||
|
let sizes = subviews.map { $0.sizeThatFits(.unspecified) }
|
||||||
|
|
||||||
|
var currentX: CGFloat = 0
|
||||||
|
var currentY: CGFloat = 0
|
||||||
|
var lineHeight: CGFloat = 0
|
||||||
|
var lineElements: [(offset: Int, size: CGSize)] = []
|
||||||
|
|
||||||
|
func placeLine() {
|
||||||
|
let lineWidth = lineElements.map { $0.size.width }.reduce(0, +) + CGFloat(lineElements.count - 1) * spacing
|
||||||
|
var startX: CGFloat = 0
|
||||||
|
switch alignment {
|
||||||
|
case .leading:
|
||||||
|
startX = bounds.minX
|
||||||
|
case .center:
|
||||||
|
startX = bounds.minX + (containerWidth - lineWidth) / 2
|
||||||
|
case .trailing:
|
||||||
|
startX = bounds.maxX - lineWidth
|
||||||
|
default:
|
||||||
|
startX = bounds.minX
|
||||||
|
}
|
||||||
|
|
||||||
|
var xOffset = startX
|
||||||
|
for (offset, size) in lineElements {
|
||||||
|
subviews[offset].place(at: CGPoint(x: xOffset, y: bounds.minY + currentY), proposal: ProposedViewSize(size)) // Use bounds.minY + currentY
|
||||||
|
xOffset += size.width + spacing
|
||||||
|
}
|
||||||
|
lineElements.removeAll() // Clear elements for the next line
|
||||||
|
}
|
||||||
|
|
||||||
|
for (offset, size) in sizes.enumerated() {
|
||||||
|
if currentX + size.width > containerWidth && !lineElements.isEmpty {
|
||||||
|
// New line
|
||||||
|
placeLine()
|
||||||
|
currentX = 0
|
||||||
|
currentY += lineHeight + spacing
|
||||||
|
lineHeight = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
lineElements.append((offset, size))
|
||||||
|
currentX += size.width + spacing
|
||||||
|
lineHeight = max(lineHeight, size.height)
|
||||||
|
}
|
||||||
|
placeLine() // Place the last line
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Image Loader
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
class ImageLoader: ObservableObject {
|
||||||
|
@Published var image: Image?
|
||||||
|
@Published var errorMessage: String?
|
||||||
|
@Published var isLoading = false
|
||||||
|
|
||||||
|
private var dataTask: URLSessionDataTask?
|
||||||
|
private let session: URLSession
|
||||||
|
|
||||||
|
init(session: URLSession = .shared) {
|
||||||
|
self.session = session
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadImage(from initialUrl: URL, token: String) async {
|
||||||
|
isLoading = true
|
||||||
|
errorMessage = nil
|
||||||
|
image = nil
|
||||||
|
|
||||||
|
do {
|
||||||
|
// First request with Authorization header
|
||||||
|
var request = URLRequest(url: initialUrl)
|
||||||
|
request.setValue("AtField \(token)", forHTTPHeaderField: "Authorization")
|
||||||
|
request.setValue("SolianWatch/1.0", forHTTPHeaderField: "User-Agent")
|
||||||
|
|
||||||
|
let (data, response) = try await session.data(for: request)
|
||||||
|
|
||||||
|
if let httpResponse = response as? HTTPURLResponse {
|
||||||
|
if httpResponse.statusCode == 302, let redirectLocation = httpResponse.allHeaderFields["Location"] as? String, let redirectUrl = URL(string: redirectLocation) {
|
||||||
|
print("[watchOS] Redirecting to: \(redirectUrl)")
|
||||||
|
// Second request to the redirected URL (S3 signed URL) without Authorization header
|
||||||
|
let (redirectData, _) = try await session.data(from: redirectUrl)
|
||||||
|
if let uiImage = UIImage(data: redirectData) {
|
||||||
|
self.image = Image(uiImage: uiImage)
|
||||||
|
} else {
|
||||||
|
// Try KingfisherWebP for WebP
|
||||||
|
let processor = WebPProcessor.default // Correct usage
|
||||||
|
if let kfImage = processor.process(item: .data(redirectData), options: KingfisherParsedOptionsInfo(
|
||||||
|
[
|
||||||
|
.processor(processor),
|
||||||
|
.loadDiskFileSynchronously,
|
||||||
|
.cacheOriginalImage
|
||||||
|
]
|
||||||
|
)) {
|
||||||
|
self.image = Image(uiImage: kfImage)
|
||||||
|
} else {
|
||||||
|
self.errorMessage = "Invalid image data from redirect (could not decode with KingfisherWebP)."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if httpResponse.statusCode == 200 {
|
||||||
|
if let uiImage = UIImage(data: data) {
|
||||||
|
self.image = Image(uiImage: uiImage)
|
||||||
|
} else {
|
||||||
|
// Try KingfisherWebP for WebP
|
||||||
|
let processor = WebPProcessor.default // Correct usage
|
||||||
|
if let kfImage = processor.process(item: .data(data), options: KingfisherParsedOptionsInfo(
|
||||||
|
[
|
||||||
|
.processor(processor),
|
||||||
|
.loadDiskFileSynchronously,
|
||||||
|
.cacheOriginalImage
|
||||||
|
]
|
||||||
|
)) {
|
||||||
|
self.image = Image(uiImage: kfImage)
|
||||||
|
} else {
|
||||||
|
self.errorMessage = "Invalid image data (could not decode with KingfisherWebP)."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.errorMessage = "HTTP Status Code: \(httpResponse.statusCode)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
self.errorMessage = error.localizedDescription
|
||||||
|
print("[watchOS] Image loading failed: \(error.localizedDescription)")
|
||||||
|
}
|
||||||
|
isLoading = false
|
||||||
|
}
|
||||||
|
|
||||||
|
func cancel() {
|
||||||
|
dataTask?.cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Views
|
// MARK: - Views
|
||||||
|
|
||||||
struct ActivityListView: View {
|
struct ActivityListView: View {
|
||||||
@StateObject private var viewModel: ActivityViewModel
|
@StateObject private var viewModel: ActivityViewModel
|
||||||
@EnvironmentObject var appState: AppState
|
@EnvironmentObject var appState: AppState
|
||||||
|
|
||||||
init(filter: String) {
|
init(filter: String, mockActivities: [SnActivity]? = nil) {
|
||||||
_viewModel = StateObject(wrappedValue: ActivityViewModel(filter: filter))
|
_viewModel = StateObject(wrappedValue: ActivityViewModel(filter: filter, mockActivities: mockActivities))
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
@@ -302,8 +503,10 @@ struct ActivityListView: View {
|
|||||||
switch activity.type {
|
switch activity.type {
|
||||||
case "posts.new", "posts.new.replies":
|
case "posts.new", "posts.new.replies":
|
||||||
if case .post(let post) = activity.data {
|
if case .post(let post) = activity.data {
|
||||||
|
NavigationLink(destination: PostDetailView(post: post)) {
|
||||||
PostRowView(post: post)
|
PostRowView(post: post)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
case "discovery":
|
case "discovery":
|
||||||
if case .discovery(let discoveryData) = activity.data {
|
if case .discovery(let discoveryData) = activity.data {
|
||||||
DiscoveryView(discoveryData: discoveryData)
|
DiscoveryView(discoveryData: discoveryData)
|
||||||
@@ -315,7 +518,10 @@ struct ActivityListView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.task {
|
.task {
|
||||||
await viewModel.fetchActivities(appState: appState)
|
// Only fetch if appState is ready and token/serverUrl are available
|
||||||
|
if appState.isReady, let token = appState.token, let serverUrl = appState.serverUrl {
|
||||||
|
await viewModel.fetchActivities(token: token, serverUrl: serverUrl)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.navigationTitle(viewModel.filter)
|
.navigationTitle(viewModel.filter)
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
@@ -324,12 +530,51 @@ struct ActivityListView: View {
|
|||||||
|
|
||||||
struct PostRowView: View {
|
struct PostRowView: View {
|
||||||
let post: SnPost
|
let post: SnPost
|
||||||
|
@EnvironmentObject var appState: AppState
|
||||||
|
@StateObject private var imageLoader = ImageLoader() // Instantiate ImageLoader
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(alignment: .leading, spacing: 4) {
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
Text(post.title ?? "Post")
|
HStack {
|
||||||
|
if let serverUrl = appState.serverUrl, let pictureId = post.publisher.picture?.id, let imageUrl = getAttachmentUrl(for: pictureId, serverUrl: serverUrl), let token = appState.token {
|
||||||
|
if imageLoader.isLoading {
|
||||||
|
ProgressView()
|
||||||
|
.frame(width: 24, height: 24)
|
||||||
|
} else if let image = imageLoader.image {
|
||||||
|
image
|
||||||
|
.resizable()
|
||||||
|
.frame(width: 24, height: 24)
|
||||||
|
.clipShape(Circle())
|
||||||
|
} else if let errorMessage = imageLoader.errorMessage {
|
||||||
|
Text("Failed: \(errorMessage)")
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundColor(.red)
|
||||||
|
.frame(width: 24, height: 24)
|
||||||
|
} else {
|
||||||
|
// Placeholder if no image and not loading
|
||||||
|
Image(systemName: "person.circle.fill")
|
||||||
|
.resizable()
|
||||||
|
.frame(width: 24, height: 24)
|
||||||
|
.clipShape(Circle())
|
||||||
|
.foregroundColor(.gray)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Text(post.publisher.nick ?? post.publisher.name)
|
||||||
|
.font(.subheadline)
|
||||||
|
.bold()
|
||||||
|
}
|
||||||
|
.task(id: post.publisher.picture?.id) { // Use task(id:) to reload image when pictureId changes
|
||||||
|
if let serverUrl = appState.serverUrl, let pictureId = post.publisher.picture?.id, let imageUrl = getAttachmentUrl(for: pictureId, serverUrl: serverUrl), let token = appState.token {
|
||||||
|
await imageLoader.loadImage(from: imageUrl, token: token)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let title = post.title, !title.isEmpty {
|
||||||
|
Text(title)
|
||||||
.font(.headline)
|
.font(.headline)
|
||||||
if let content = post.content {
|
}
|
||||||
|
|
||||||
|
if let content = post.content, !content.isEmpty {
|
||||||
Text(content)
|
Text(content)
|
||||||
.font(.body)
|
.font(.body)
|
||||||
}
|
}
|
||||||
@@ -337,27 +582,215 @@ struct PostRowView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct PostDetailView: View {
|
||||||
|
let post: SnPost
|
||||||
|
@EnvironmentObject var appState: AppState
|
||||||
|
@StateObject private var publisherImageLoader = ImageLoader() // Instantiate ImageLoader for publisher avatar
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
ScrollView {
|
||||||
|
VStack(alignment: .leading, spacing: 8) {
|
||||||
|
HStack {
|
||||||
|
if let serverUrl = appState.serverUrl, let pictureId = post.publisher.picture?.id, let imageUrl = getAttachmentUrl(for: pictureId, serverUrl: serverUrl), let token = appState.token {
|
||||||
|
if publisherImageLoader.isLoading {
|
||||||
|
ProgressView()
|
||||||
|
.frame(width: 32, height: 32)
|
||||||
|
} else if let image = publisherImageLoader.image {
|
||||||
|
image
|
||||||
|
.resizable()
|
||||||
|
.frame(width: 32, height: 32)
|
||||||
|
.clipShape(Circle())
|
||||||
|
} else if let errorMessage = publisherImageLoader.errorMessage {
|
||||||
|
Text("Failed: \(errorMessage)")
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundColor(.red)
|
||||||
|
.frame(width: 32, height: 32)
|
||||||
|
} else {
|
||||||
|
Image(systemName: "person.circle.fill")
|
||||||
|
.resizable()
|
||||||
|
.frame(width: 32, height: 32)
|
||||||
|
.clipShape(Circle())
|
||||||
|
.foregroundColor(.gray)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Text("@\(post.publisher.name)")
|
||||||
|
.font(.headline)
|
||||||
|
}
|
||||||
|
.task(id: post.publisher.picture?.id) { // Use task(id:) to reload image when pictureId changes
|
||||||
|
if let serverUrl = appState.serverUrl, let pictureId = post.publisher.picture?.id, let imageUrl = getAttachmentUrl(for: pictureId, serverUrl: serverUrl), let token = appState.token {
|
||||||
|
await publisherImageLoader.loadImage(from: imageUrl, token: token)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let title = post.title, !title.isEmpty {
|
||||||
|
Text(title)
|
||||||
|
.font(.title2)
|
||||||
|
.bold()
|
||||||
|
}
|
||||||
|
|
||||||
|
if let content = post.content, !content.isEmpty {
|
||||||
|
Text(content)
|
||||||
|
.font(.body)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !post.attachments.isEmpty {
|
||||||
|
Divider()
|
||||||
|
Text("Attachments").font(.headline)
|
||||||
|
ForEach(post.attachments) { attachment in
|
||||||
|
AttachmentImageView(attachment: attachment)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !post.tags.isEmpty {
|
||||||
|
Divider()
|
||||||
|
Text("Tags").font(.headline)
|
||||||
|
FlowLayout(alignment: .leading, spacing: 4) {
|
||||||
|
ForEach(post.tags) { tag in
|
||||||
|
Text("#\(tag.name ?? tag.slug)")
|
||||||
|
.font(.caption)
|
||||||
|
.padding(.horizontal, 6)
|
||||||
|
.padding(.vertical, 3)
|
||||||
|
.background(Capsule().fill(Color.accentColor.opacity(0.2)))
|
||||||
|
.cornerRadius(5)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
.navigationTitle("Post")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AttachmentImageView: View {
|
||||||
|
let attachment: SnCloudFile
|
||||||
|
@EnvironmentObject var appState: AppState
|
||||||
|
@StateObject private var imageLoader = ImageLoader()
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Group {
|
||||||
|
if imageLoader.isLoading {
|
||||||
|
ProgressView()
|
||||||
|
} else if let image = imageLoader.image {
|
||||||
|
image
|
||||||
|
.resizable()
|
||||||
|
.aspectRatio(contentMode: .fit)
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
} else if let errorMessage = imageLoader.errorMessage {
|
||||||
|
Text("Failed to load attachment: \(errorMessage)")
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundColor(.red)
|
||||||
|
} else {
|
||||||
|
Text("File: \(attachment.id)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.task(id: attachment.id) {
|
||||||
|
if let serverUrl = appState.serverUrl, let imageUrl = getAttachmentUrl(for: attachment.id, serverUrl: serverUrl), let token = appState.token, attachment.mimeType?.starts(with: "image") == true {
|
||||||
|
await imageLoader.loadImage(from: imageUrl, token: token)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct DiscoveryView: View {
|
struct DiscoveryView: View {
|
||||||
let discoveryData: DiscoveryData
|
let discoveryData: DiscoveryData
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
NavigationLink(destination: DiscoveryDetailView(discoveryData: discoveryData)) {
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
Text("Discovery")
|
Text("Discovery")
|
||||||
.font(.headline)
|
.font(.headline)
|
||||||
.padding(.bottom, 2)
|
Text("\(discoveryData.items.count) new items to discover")
|
||||||
ForEach(discoveryData.items) { item in
|
.font(.subheadline)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DiscoveryDetailView: View {
|
||||||
|
let discoveryData: DiscoveryData
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
List(discoveryData.items) { item in
|
||||||
|
NavigationLink(destination: destinationView(for: item)) {
|
||||||
|
itemView(for: item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.navigationTitle("Discovery")
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private func itemView(for item: DiscoveryItem) -> some View {
|
||||||
|
VStack(alignment: .leading) {
|
||||||
switch item.data {
|
switch item.data {
|
||||||
case .realm(let realm):
|
case .realm(let realm):
|
||||||
Text("Realm: \(realm.name)")
|
Text("Realm").font(.headline)
|
||||||
|
Text(realm.name).foregroundColor(.secondary)
|
||||||
case .publisher(let publisher):
|
case .publisher(let publisher):
|
||||||
Text("Publisher: \(publisher.name)")
|
Text("Publisher").font(.headline)
|
||||||
|
Text(publisher.name).foregroundColor(.secondary)
|
||||||
case .article(let article):
|
case .article(let article):
|
||||||
Text("Article: \(article.title)")
|
Text("Article").font(.headline)
|
||||||
|
Text(article.title).foregroundColor(.secondary)
|
||||||
case .unknown:
|
case .unknown:
|
||||||
Text("Unknown discovery item")
|
Text("Unknown item")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private func destinationView(for item: DiscoveryItem) -> some View {
|
||||||
|
switch item.data {
|
||||||
|
case .realm(let realm):
|
||||||
|
RealmDetailView(realm: realm)
|
||||||
|
case .publisher(let publisher):
|
||||||
|
PublisherDetailView(publisher: publisher)
|
||||||
|
case .article(let article):
|
||||||
|
ArticleDetailView(article: article)
|
||||||
|
case .unknown:
|
||||||
|
Text("Detail view not available")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct RealmDetailView: View {
|
||||||
|
let realm: SnRealm
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(alignment: .leading, spacing: 8) {
|
||||||
|
Text(realm.name).font(.headline)
|
||||||
|
if let description = realm.description {
|
||||||
|
Text(description).font(.body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.navigationTitle("Realm")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PublisherDetailView: View {
|
||||||
|
let publisher: SnPublisher
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(alignment: .leading, spacing: 8) {
|
||||||
|
Text(publisher.name).font(.headline)
|
||||||
|
if let description = publisher.description {
|
||||||
|
Text(description).font(.body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.navigationTitle("Publisher")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ArticleDetailView: View {
|
||||||
|
let article: SnWebArticle
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(alignment: .leading, spacing: 8) {
|
||||||
|
Text(article.title).font(.headline)
|
||||||
|
Text(article.url).font(.caption).foregroundColor(.secondary)
|
||||||
|
}
|
||||||
|
.navigationTitle("Article")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -409,6 +842,36 @@ struct ContentView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#Preview {
|
#if DEBUG
|
||||||
ContentView()
|
extension SnActivity {
|
||||||
|
static var mock: [SnActivity] {
|
||||||
|
let mockPublisher = SnPublisher(id: "pub1", name: "Mock Publisher", nick: "mock_nick", description: "A publisher for testing", picture: SnCloudFile(id: "mock_avatar_id", mimeType: "image/png"))
|
||||||
|
let mockTag1 = SnPostTag(id: "tag1", slug: "swiftui", name: "SwiftUI")
|
||||||
|
let mockTag2 = SnPostTag(id: "tag2", slug: "watchos", name: "watchOS")
|
||||||
|
let mockAttachment1 = SnCloudFile(id: "mock_image_id_1", mimeType: "image/jpeg")
|
||||||
|
let mockAttachment2 = SnCloudFile(id: "mock_image_id_2", mimeType: "image/png")
|
||||||
|
|
||||||
|
let post1 = SnPost(id: "1", title: "Hello from a Mock Post!", content: "This is a mock post content. It can be a bit longer to see how it wraps.", publisher: mockPublisher, attachments: [mockAttachment1, mockAttachment2], tags: [mockTag1, mockTag2])
|
||||||
|
let activity1 = SnActivity(id: "1", type: "posts.new", data: .post(post1), createdAt: Date())
|
||||||
|
|
||||||
|
let realm1 = SnRealm(id: "r1", name: "SwiftUI Previews", description: "A place for designing in previews.")
|
||||||
|
let publisher1 = SnPublisher(id: "p1", name: "The Mock Times", nick: "mock_times", description: "All the news that's fit to mock.", picture: nil)
|
||||||
|
let article1 = SnWebArticle(id: "a1", title: "The Art of Mocking Data", url: "https://example.com")
|
||||||
|
|
||||||
|
let discoveryItem1 = DiscoveryItem(type: "realm", data: .realm(realm1))
|
||||||
|
let discoveryItem2 = DiscoveryItem(type: "publisher", data: .publisher(publisher1))
|
||||||
|
let discoveryItem3 = DiscoveryItem(type: "article", data: .article(article1))
|
||||||
|
let discoveryData = DiscoveryData(items: [discoveryItem1, discoveryItem2, discoveryItem3])
|
||||||
|
let activity2 = SnActivity(id: "2", type: "discovery", data: .discovery(discoveryData), createdAt: Date())
|
||||||
|
|
||||||
|
return [activity1, activity2]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
NavigationStack {
|
||||||
|
ActivityListView(filter: "Preview", mockActivities: SnActivity.mock)
|
||||||
|
.environmentObject(AppState())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user