Compare commits

...

58 Commits

Author SHA1 Message Date
5ebefae961 🚀 Launch 3.3.0+145 2025-11-05 22:48:34 +08:00
d4758674bb 🐛 Trying to fix file chunk issue 2025-11-05 13:13:21 +08:00
f5f1ddc0ea Steam connection 2025-11-04 23:53:17 +08:00
2720b59485 🐛 Fix protocol handling 2025-11-04 23:25:37 +08:00
29b1ac7fce 🐛 Fix tray icon didn't change color on macOS 26 automatically 2025-11-04 23:22:35 +08:00
83ca5551ad ♻️ Refactored the app protocol 2025-11-04 23:08:21 +08:00
611cb024a9 🔨 Update windows version code 2025-11-03 00:20:24 +08:00
74fb56891d 🐛 Fix web build 2025-11-03 00:12:02 +08:00
ac4fa5eb85 🚀 Launch 3.3.0+144 2025-11-02 23:57:31 +08:00
8857718709 🐛 Fix compose toolbar safe area issue 2025-11-02 23:56:48 +08:00
dd17b2b9c1 Scroll gradiant to think as well 2025-11-02 23:55:00 +08:00
848439f664 Chat room scroll gradiant 2025-11-02 23:52:03 +08:00
f83117424d 🐛 Fix tag subscribe used wrong icon 2025-11-02 23:44:11 +08:00
8c19c32c76 Publisher profile collapsible pinned post 2025-11-02 23:36:42 +08:00
d62b2bed80 💄 Optimize publisher page filter select date 2025-11-02 23:34:08 +08:00
5a23eb1768 Stronger filter 2025-11-02 23:30:16 +08:00
5f6e4763d3 🐛 Fix app notification 2025-11-02 23:12:11 +08:00
580c36fb89 🐛 Fix mis placed safe area 2025-11-02 22:45:28 +08:00
6c25af3b30 Show publisher mentioned chip as well 2025-11-02 22:44:09 +08:00
a1da72d447 Show profile picture in mention chip 2025-11-02 22:41:50 +08:00
ab4120cc22 💄 Optimize cloud file list 2025-11-02 22:34:32 +08:00
52eff0fa25 🐛 Fix the NSE again... 2025-11-02 22:14:31 +08:00
beeb28abf2 💄 Optimize in-app notification style 2025-11-02 21:55:42 +08:00
c0ab3837ac 👽 Make poll load itself to match server updates 2025-11-02 21:47:37 +08:00
59d38c0d8d 💄 Refined developer hub 2025-11-02 21:19:58 +08:00
bd2247ce86 ♻️ Refactor the app management to use sheet 2025-11-02 21:12:55 +08:00
da2d3f7f17 ♻️ Make bot management into sheet 2025-11-02 21:04:35 +08:00
7497b77384 💄 Adjusted developer hub 2025-11-02 17:45:03 +08:00
f542d9fa97 🐛 Fix timezone error 2025-11-02 17:24:18 +08:00
e70439870e ♻️ Add event bus to more places 2025-11-02 17:13:10 +08:00
d764b042fe Shows account own activities on account page 2025-11-02 16:59:58 +08:00
a76b97d1d2 💄 Shows listening activities are from spotfiy 2025-11-02 16:55:16 +08:00
cfbe6e580b 👔 Add rpc prefix for activities generated from activity server 2025-11-02 16:50:31 +08:00
f08b9e057f Special display for spotify activity 2025-11-02 16:49:39 +08:00
0509f37c96 ♻️ Use system browser for OIDC 2025-11-02 16:32:29 +08:00
a7dc9ac6fa Add spotify in account connection 2025-11-02 15:49:44 +08:00
caf2f5f1f6 💄 Optimize the link embed 2025-11-02 15:43:40 +08:00
12b79af3a2 🐛 Fix bugs 2025-11-02 02:21:15 +08:00
88f149584e ♻️ Removed the post compose screen completely 2025-11-02 01:43:04 +08:00
877001b802 💄 Optimize publisher profile again 2025-11-02 01:36:14 +08:00
fec28f6223 💄 Optimize publisher page 2025-11-02 01:30:47 +08:00
85005ff9c3 💄 Optimize profile page 2025-11-02 01:20:14 +08:00
e3c92a3c55 💄 Optimize profile page styling 2025-11-02 01:05:40 +08:00
9e9fbc5d6a 💄 Optimize settings buttons 2025-11-02 01:04:10 +08:00
8d1d836b52 💄 Optimize the account page 2025-11-02 00:51:16 +08:00
bc60ce5d42 💄 Optimize the pfc and show the activities 2025-11-02 00:25:08 +08:00
c093123e3a Shows images, url from presense 2025-11-02 00:03:16 +08:00
3de73538c7 🐛 Activity refined 2025-11-01 23:36:05 +08:00
ba8d5cee09 Refined presense activity 2025-11-01 21:47:34 +08:00
5ee2e70442 New activity presence 2025-11-01 20:16:54 +08:00
53a3a32907 🚀 Launch 3.3.0+143 (SNAPSHOT) (HOTFIX) 2025-11-01 15:59:16 +08:00
9a628779d9 🚚 Rename watchOS project to proper one 2025-11-01 12:21:37 +08:00
b60bd63d0c 🐛 Made watchOS URLSession wait for connectivty 2025-11-01 12:19:56 +08:00
01cc71fd47 🐛 Fix watch connectivty didn't work on real devices 2025-11-01 02:38:53 +08:00
a2b0cd0b6a 🐛 Fix some production issue for watchOS Solian 2025-10-31 23:09:08 +08:00
7f971bcee3 🔨 Fix stupid xcode's fault cause iOS failed to build after adding watchOS 2025-10-31 22:32:15 +08:00
7de98a1731 🐛 Fix post refresh 2025-10-31 19:18:34 +08:00
b52eb95b14 🐛 Fix compose sheet 2025-10-31 19:15:22 +08:00
169 changed files with 6785 additions and 4008 deletions

View File

@@ -43,6 +43,16 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<!-- App protocol -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<!-- Accepts URIs that begin with YOUR_SCHEME://YOUR_HOST -->
<data android:scheme="solian" />
</intent-filter>
<!-- Deeplinking -->
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />

View File

@@ -162,6 +162,8 @@
"accountConnectionProviderGithub": "GitHub",
"accountConnectionProviderDiscord": "Discord",
"accountConnectionProviderAfdian": "Afdian",
"accountConnectionProviderSpotify": "Spotify",
"accountConnectionProviderSteam": "Steam",
"checkIn": "Check In",
"checkInNone": "Not checked-in yet",
"checkInNoneHint": "Get your fortune tips and daily rewards by checking in.",
@@ -469,6 +471,7 @@
"pronouns": "Pronouns",
"location": "Location",
"timeZone": "Time Zone",
"timezoneNotFound": "Time zone not found",
"birthday": "Birthday",
"selectADate": "Select a date",
"checkInResultT0": "Worst",
@@ -1302,5 +1305,20 @@
"aiThought": "AI Thought",
"aiThoughtTitle": "Let sn-chan think",
"postReferenceUnavailable": "Referenced post is unavailable",
"fabLocation": "FAB Location"
"fabLocation": "FAB Location",
"activities": "Activities",
"presenceTypeGaming": "Playing",
"presenceTypeMusic": "Listening to Music",
"presenceTypeWorkout": "Working out",
"articleCompose": "Compose Article",
"backToHub": "Back to Hub",
"advancedFilters": "Advanced Filters",
"searchPosts": "Search Posts",
"sortBy": "Sort by",
"fromDate": "From Date",
"toDate": "To Date",
"popularity": "Popularity",
"descendingOrder": "Descending Order",
"selectDate": "Select Date",
"pinnedPosts": "Pinned Posts"
}

View File

@@ -158,11 +158,11 @@
"checkIn": "签到",
"checkInNone": "尚未签到",
"checkInNoneHint": "通过签到获取您的财富提示和每日奖励。",
"checkInResultLevel0": "最差运气",
"checkInResultLevel1": "坏运气",
"checkInResultLevel2": "一个普通的日常",
"checkInResultLevel3": "好运",
"checkInResultLevel4": "最佳运气",
"checkInResultLevel0": "大凶",
"checkInResultLevel1": "",
"checkInResultLevel2": "中平",
"checkInResultLevel3": "",
"checkInResultLevel4": "大吉",
"checkInActivityTitle": "{} 在 {} 签到并获得了 {}",
"eventCalander": "活动日历",
"eventCalanderEmpty": "该日无活动。",
@@ -344,7 +344,7 @@
"accountSettingsHelpContent": "此页面允许您管理您的帐户安全性、隐私和其他设置。如果您需要帮助,请联系管理员。",
"unauthorized": "未授权",
"unauthorizedHint": "您未登录或会话已过期,请重新登录。",
"publisherBelongsTo": "属于",
"publisherBelongsTo": "属于 {}",
"postContent": "内容",
"postSettings": "设置",
"postPublisherUnselected": "未指定发布者",

BIN
assets/icons/icon-tray.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View File

@@ -0,0 +1 @@
<svg width="2471" height="2500" viewBox="0 0 256 259" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid"><path d="M127.779 0C60.42 0 5.24 52.412 0 119.014l68.724 28.674a35.812 35.812 0 0 1 20.426-6.366c.682 0 1.356.019 2.02.056l30.566-44.71v-.626c0-26.903 21.69-48.796 48.353-48.796 26.662 0 48.352 21.893 48.352 48.796 0 26.902-21.69 48.804-48.352 48.804-.37 0-.73-.009-1.098-.018l-43.593 31.377c.028.582.046 1.163.046 1.735 0 20.204-16.283 36.636-36.294 36.636-17.566 0-32.263-12.658-35.584-29.412L4.41 164.654c15.223 54.313 64.673 94.132 123.369 94.132 70.818 0 128.221-57.938 128.221-129.393C256 57.93 198.597 0 127.779 0zM80.352 196.332l-15.749-6.568c2.787 5.867 7.621 10.775 14.033 13.47 13.857 5.83 29.836-.803 35.612-14.799a27.555 27.555 0 0 0 .046-21.035c-2.768-6.79-7.999-12.086-14.706-14.909-6.67-2.795-13.811-2.694-20.085-.304l16.275 6.79c10.222 4.3 15.056 16.145 10.794 26.46-4.253 10.314-15.998 15.195-26.22 10.895zm121.957-100.29c0-17.925-14.457-32.52-32.217-32.52-17.769 0-32.226 14.595-32.226 32.52 0 17.926 14.457 32.512 32.226 32.512 17.76 0 32.217-14.586 32.217-32.512zm-56.37-.055c0-13.488 10.84-24.42 24.2-24.42 13.368 0 24.208 10.932 24.208 24.42 0 13.488-10.84 24.421-24.209 24.421-13.359 0-24.2-10.933-24.2-24.42z" fill="#1A1918"/></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -31,6 +31,8 @@ target 'Runner' do
use_modular_headers!
pod 'Alamofire'
pod 'Kingfisher', '~> 8.0'
pod 'KingfisherWebP'
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
@@ -40,8 +42,6 @@ target 'Runner' do
target 'SolianNotificationService' do
inherit! :search_paths
pod 'Kingfisher', '~> 8.0'
pod 'Alamofire'
end
target 'SolianShareExtension' do
@@ -49,7 +49,7 @@ target 'Runner' do
end
end
target 'WatchRunner Watch App' do
target 'Solian Watch App' do
platform :watchos, '11.0'
use_frameworks!

View File

@@ -571,6 +571,6 @@ SPEC CHECKSUMS:
wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556
WebRTC-SDK: 40d4f5ba05cadff14e4db5614aec402a633f007e
PODFILE CHECKSUM: 34a6810e5629b6394fac3c1a962f601a58f86cb2
PODFILE CHECKSUM: 585198f58dca90ac6492607c83a8d17045ab3852
COCOAPODS: 1.16.2

View File

@@ -10,7 +10,8 @@
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
7310A7DF2EB10963002C0FD3 /* WatchRunner Watch App.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = 7310A7D42EB10962002C0FD3 /* WatchRunner Watch App.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
5D8143680678FCD1D1827271 /* Pods_Solian_Watch_App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C9C046CF867AE03DC170F861 /* Pods_Solian_Watch_App.framework */; };
7310A7DF2EB10963002C0FD3 /* Solian Watch App.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = 7310A7D42EB10962002C0FD3 /* Solian Watch App.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
73ACDFAD2E3D0E6100B63535 /* ReplayKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 73ACDFAC2E3D0E6100B63535 /* ReplayKit.framework */; };
73ACDFC32E3D0E6100B63535 /* SolianBroadcastExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 73ACDFAB2E3D0E6100B63535 /* SolianBroadcastExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
73C305D82E0BE878009035B9 /* SolianShareExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 73C305CE2E0BE878009035B9 /* SolianShareExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
@@ -21,7 +22,6 @@
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
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 */; };
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 */; };
@@ -62,11 +62,11 @@
/* Begin PBXCopyFilesBuildPhase section */
7310A7DE2EB10963002C0FD3 /* Embed Watch Content */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
buildActionMask = 12;
dstPath = "$(CONTENTS_FOLDER_PATH)/Watch";
dstSubfolderSpec = 16;
files = (
7310A7DF2EB10963002C0FD3 /* WatchRunner Watch App.app in Embed Watch Content */,
7310A7DF2EB10963002C0FD3 /* Solian Watch App.app in Embed Watch Content */,
);
name = "Embed Watch Content";
runOnlyForDeploymentPostprocessing = 0;
@@ -97,6 +97,7 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
0ECC3D56D018DD87FC342699 /* Pods-Solian Watch App.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Solian Watch App.profile.xcconfig"; path = "Target Support Files/Pods-Solian Watch App/Pods-Solian Watch App.profile.xcconfig"; sourceTree = "<group>"; };
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>"; };
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
@@ -105,16 +106,18 @@
17FAB080A9C53193ABD9C15B /* Pods-SolianShareExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SolianShareExtension.debug.xcconfig"; path = "Target Support Files/Pods-SolianShareExtension/Pods-SolianShareExtension.debug.xcconfig"; sourceTree = "<group>"; };
192FDACE67D7CB6AED15C634 /* Pods-NotificationService.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NotificationService.debug.xcconfig"; path = "Target Support Files/Pods-NotificationService/Pods-NotificationService.debug.xcconfig"; sourceTree = "<group>"; };
1C14F71D23E4371602065522 /* 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>"; };
2440CEDEAAD6D51FDA95FA62 /* Pods-Solian Watch App.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Solian Watch App.release.xcconfig"; path = "Target Support Files/Pods-Solian Watch App/Pods-Solian Watch App.release.xcconfig"; sourceTree = "<group>"; };
252A83CE6862573BB856ED8E /* Pods-NotificationService.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NotificationService.release.xcconfig"; path = "Target Support Files/Pods-NotificationService/Pods-NotificationService.release.xcconfig"; sourceTree = "<group>"; };
27C66EFB5A705F1A822C3EB0 /* Pods-SolianShareExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SolianShareExtension.release.xcconfig"; path = "Target Support Files/Pods-SolianShareExtension/Pods-SolianShareExtension.release.xcconfig"; sourceTree = "<group>"; };
29812C17FFBE7DBBC7203981 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
2D2457F8B2E6EF9C0F935035 /* Pods-NotificationService.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NotificationService.profile.xcconfig"; path = "Target Support Files/Pods-NotificationService/Pods-NotificationService.profile.xcconfig"; sourceTree = "<group>"; };
31EA49B10397BD4145AD765E /* Pods-Solian Watch App.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Solian Watch App.debug.xcconfig"; path = "Target Support Files/Pods-Solian Watch App/Pods-Solian Watch App.debug.xcconfig"; sourceTree = "<group>"; };
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
39FE4CC6223F0D3C0E1FFD04 /* Pods_SolianNotificationService.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SolianNotificationService.framework; sourceTree = BUILT_PRODUCTS_DIR; };
3A1C47BD29CC6AC2587D4DBE /* 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>"; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
7310A7D42EB10962002C0FD3 /* WatchRunner Watch App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "WatchRunner Watch App.app"; sourceTree = BUILT_PRODUCTS_DIR; };
7310A7D42EB10962002C0FD3 /* Solian Watch App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Solian Watch App.app"; sourceTree = BUILT_PRODUCTS_DIR; };
737E920B2DB6A9FF00BE9CDB /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = "<group>"; };
73ACDFAB2E3D0E6100B63535 /* SolianBroadcastExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = SolianBroadcastExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
73ACDFAC2E3D0E6100B63535 /* ReplayKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ReplayKit.framework; path = System/Library/Frameworks/ReplayKit.framework; sourceTree = SDKROOT; };
@@ -126,7 +129,6 @@
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>"; };
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>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
@@ -142,6 +144,7 @@
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; };
B93771F2A63E4148DC6142F7 /* Pods-SolianNotificationService.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SolianNotificationService.release.xcconfig"; path = "Target Support Files/Pods-SolianNotificationService/Pods-SolianNotificationService.release.xcconfig"; sourceTree = "<group>"; };
C9C046CF867AE03DC170F861 /* Pods_Solian_Watch_App.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Solian_Watch_App.framework; sourceTree = BUILT_PRODUCTS_DIR; };
E6B10A9A85BECA2E576C91FF /* 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>"; };
F6D834CA86410B09796B312B /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
F830F535CB92E3F2E1653A11 /* Pods-SolianNotificationService.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SolianNotificationService.debug.xcconfig"; path = "Target Support Files/Pods-SolianNotificationService/Pods-SolianNotificationService.debug.xcconfig"; sourceTree = "<group>"; };
@@ -180,11 +183,11 @@
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
/* Begin PBXFileSystemSynchronizedRootGroup section */
7310A7D52EB10962002C0FD3 /* WatchRunner Watch App */ = {
7310A7D52EB10962002C0FD3 /* Solian Watch App */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
);
path = "WatchRunner Watch App";
path = "Solian Watch App";
sourceTree = "<group>";
};
73268D272DEB012A0076E970 /* Services */ = {
@@ -234,7 +237,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
A1D34487886D362AC8B99B2E /* Pods_WatchRunner_Watch_App.framework in Frameworks */,
5D8143680678FCD1D1827271 /* Pods_Solian_Watch_App.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -291,7 +294,7 @@
7B40764A2C4CC0E7DC70A0D3 /* Pods_SolianShareExtension.framework */,
73ACDFAC2E3D0E6100B63535 /* ReplayKit.framework */,
73ACDFB82E3D0E6100B63535 /* UIKit.framework */,
802C1CFCA7F1E069AAEFB454 /* Pods_WatchRunner_Watch_App.framework */,
C9C046CF867AE03DC170F861 /* Pods_Solian_Watch_App.framework */,
);
name = Frameworks;
sourceTree = "<group>";
@@ -317,6 +320,9 @@
86D60BA96DA647E1B11AA7F0 /* Pods-WatchRunner Watch App.debug.xcconfig */,
A2EB1DAFDE9B8E6D88BBF7A3 /* Pods-WatchRunner Watch App.release.xcconfig */,
103EA2362B9E9F127016A1F1 /* Pods-WatchRunner Watch App.profile.xcconfig */,
31EA49B10397BD4145AD765E /* Pods-Solian Watch App.debug.xcconfig */,
2440CEDEAAD6D51FDA95FA62 /* Pods-Solian Watch App.release.xcconfig */,
0ECC3D56D018DD87FC342699 /* Pods-Solian Watch App.profile.xcconfig */,
);
path = Pods;
sourceTree = "<group>";
@@ -340,7 +346,7 @@
73CDD67B2DEC00480059D95D /* SolianNotificationService */,
73C305CF2E0BE878009035B9 /* SolianShareExtension */,
73ACDFAE2E3D0E6100B63535 /* SolianBroadcastExtension */,
7310A7D52EB10962002C0FD3 /* WatchRunner Watch App */,
7310A7D52EB10962002C0FD3 /* Solian Watch App */,
97C146EF1CF9000F007C117D /* Products */,
331C8082294A63A400263BE5 /* RunnerTests */,
91E124CE95BCB4DCD890160D /* Pods */,
@@ -357,7 +363,7 @@
73CDD67A2DEC00480059D95D /* SolianNotificationService.appex */,
73C305CE2E0BE878009035B9 /* SolianShareExtension.appex */,
73ACDFAB2E3D0E6100B63535 /* SolianBroadcastExtension.appex */,
7310A7D42EB10962002C0FD3 /* WatchRunner Watch App.app */,
7310A7D42EB10962002C0FD3 /* Solian Watch App.app */,
);
name = Products;
sourceTree = "<group>";
@@ -402,26 +408,26 @@
productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
7310A7D32EB10962002C0FD3 /* WatchRunner Watch App */ = {
7310A7D32EB10962002C0FD3 /* Solian Watch App */ = {
isa = PBXNativeTarget;
buildConfigurationList = 7310A7E32EB10963002C0FD3 /* Build configuration list for PBXNativeTarget "WatchRunner Watch App" */;
buildConfigurationList = 7310A7E32EB10963002C0FD3 /* Build configuration list for PBXNativeTarget "Solian Watch App" */;
buildPhases = (
DDEDA1BA6278B94F0F7B9B61 /* [CP] Check Pods Manifest.lock */,
7310A7D02EB10962002C0FD3 /* Sources */,
7310A7D12EB10962002C0FD3 /* Frameworks */,
7310A7D22EB10962002C0FD3 /* Resources */,
C74B07D6587D29C67A198025 /* [CP] Embed Pods Frameworks */,
E29ECA5954168075BDB000DC /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
dependencies = (
);
fileSystemSynchronizedGroups = (
7310A7D52EB10962002C0FD3 /* WatchRunner Watch App */,
7310A7D52EB10962002C0FD3 /* Solian Watch App */,
);
name = "WatchRunner Watch App";
name = "Solian Watch App";
productName = "WatchRunner Watch App";
productReference = 7310A7D42EB10962002C0FD3 /* WatchRunner Watch App.app */;
productReference = 7310A7D42EB10962002C0FD3 /* Solian Watch App.app */;
productType = "com.apple.product-type.application";
};
73ACDFAA2E3D0E6100B63535 /* SolianBroadcastExtension */ = {
@@ -569,7 +575,7 @@
73CDD6792DEC00480059D95D /* SolianNotificationService */,
73C305CD2E0BE878009035B9 /* SolianShareExtension */,
73ACDFAA2E3D0E6100B63535 /* SolianBroadcastExtension */,
7310A7D32EB10962002C0FD3 /* WatchRunner Watch App */,
7310A7D32EB10962002C0FD3 /* Solian Watch App */,
);
};
/* End PBXProject section */
@@ -756,23 +762,6 @@
shellPath = /bin/sh;
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",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-WatchRunner Watch App/Pods-WatchRunner Watch App-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
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;
@@ -788,13 +777,30 @@
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-WatchRunner Watch App-checkManifestLockResult.txt",
"$(DERIVED_FILE_DIR)/Pods-Solian 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;
};
E29ECA5954168075BDB000DC /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Solian Watch App/Pods-Solian Watch App-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Solian Watch App/Pods-Solian Watch App-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Solian Watch App/Pods-Solian Watch App-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
E86CDE9D6464F4F52B910856 /* FlutterFire: "flutterfire upload-crashlytics-symbols" */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
@@ -992,6 +998,7 @@
CUSTOM_GROUP_ID = group.solsynth.solian;
DEVELOPMENT_TEAM = W7HPZ53V6B;
ENABLE_BITCODE = NO;
EXCLUDED_SOURCE_FILE_NAMES = "";
INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = Solian;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
@@ -1002,6 +1009,7 @@
);
PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos";
SWIFT_ENABLE_EXPLICIT_MODULES = "$(SWIFT_USE_INTEGRATED_DRIVER)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
@@ -1014,6 +1022,7 @@
isa = XCBuildConfiguration;
baseConfigurationReference = 14DFD79BE7C26E51B117583C /* Pods-RunnerTests.debug.xcconfig */;
buildSettings = {
ALLOW_TARGET_PLATFORM_SPECIALIZATION = YES;
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
@@ -1022,6 +1031,8 @@
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos";
SUPPORTS_MACCATALYST = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
@@ -1033,6 +1044,7 @@
isa = XCBuildConfiguration;
baseConfigurationReference = 14118AC858B441AB16B7309E /* Pods-RunnerTests.release.xcconfig */;
buildSettings = {
ALLOW_TARGET_PLATFORM_SPECIALIZATION = YES;
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
@@ -1041,6 +1053,8 @@
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos";
SUPPORTS_MACCATALYST = YES;
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
@@ -1050,6 +1064,7 @@
isa = XCBuildConfiguration;
baseConfigurationReference = E6B10A9A85BECA2E576C91FF /* Pods-RunnerTests.profile.xcconfig */;
buildSettings = {
ALLOW_TARGET_PLATFORM_SPECIALIZATION = YES;
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
@@ -1058,6 +1073,8 @@
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos";
SUPPORTS_MACCATALYST = YES;
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
@@ -1065,7 +1082,7 @@
};
7310A7E02EB10963002C0FD3 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 86D60BA96DA647E1B11AA7F0 /* Pods-WatchRunner Watch App.debug.xcconfig */;
baseConfigurationReference = 31EA49B10397BD4145AD765E /* Pods-Solian Watch App.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
@@ -1084,10 +1101,11 @@
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "WatchRunner-Watch-App-Info.plist";
INFOPLIST_KEY_CFBundleDisplayName = WatchRunner;
INFOPLIST_KEY_CFBundleDisplayName = Solian;
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
INFOPLIST_KEY_WKCompanionAppBundleIdentifier = dev.solsynth.solian;
IPHONEOS_DEPLOYMENT_TARGET = 16.6;
INFOPLIST_KEY_WKRunsIndependentlyOfCompanionApp = NO;
IPHONEOS_DEPLOYMENT_TARGET = 18.6;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@@ -1115,7 +1133,7 @@
};
7310A7E12EB10963002C0FD3 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = A2EB1DAFDE9B8E6D88BBF7A3 /* Pods-WatchRunner Watch App.release.xcconfig */;
baseConfigurationReference = 2440CEDEAAD6D51FDA95FA62 /* Pods-Solian Watch App.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
@@ -1134,10 +1152,11 @@
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "WatchRunner-Watch-App-Info.plist";
INFOPLIST_KEY_CFBundleDisplayName = WatchRunner;
INFOPLIST_KEY_CFBundleDisplayName = Solian;
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
INFOPLIST_KEY_WKCompanionAppBundleIdentifier = dev.solsynth.solian;
IPHONEOS_DEPLOYMENT_TARGET = 16.6;
INFOPLIST_KEY_WKRunsIndependentlyOfCompanionApp = NO;
IPHONEOS_DEPLOYMENT_TARGET = 18.6;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@@ -1150,6 +1169,7 @@
SDKROOT = watchos;
SKIP_INSTALL = YES;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SUPPORTED_PLATFORMS = "watchsimulator watchos";
SWIFT_APPROACHABLE_CONCURRENCY = YES;
SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor;
SWIFT_EMIT_LOC_STRINGS = YES;
@@ -1162,7 +1182,7 @@
};
7310A7E22EB10963002C0FD3 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 103EA2362B9E9F127016A1F1 /* Pods-WatchRunner Watch App.profile.xcconfig */;
baseConfigurationReference = 0ECC3D56D018DD87FC342699 /* Pods-Solian Watch App.profile.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
@@ -1181,10 +1201,11 @@
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "WatchRunner-Watch-App-Info.plist";
INFOPLIST_KEY_CFBundleDisplayName = WatchRunner;
INFOPLIST_KEY_CFBundleDisplayName = Solian;
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
INFOPLIST_KEY_WKCompanionAppBundleIdentifier = dev.solsynth.solian;
IPHONEOS_DEPLOYMENT_TARGET = 16.6;
INFOPLIST_KEY_WKRunsIndependentlyOfCompanionApp = NO;
IPHONEOS_DEPLOYMENT_TARGET = 18.6;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@@ -1197,6 +1218,7 @@
SDKROOT = watchos;
SKIP_INSTALL = YES;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SUPPORTED_PLATFORMS = "watchsimulator watchos";
SWIFT_APPROACHABLE_CONCURRENCY = YES;
SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor;
SWIFT_EMIT_LOC_STRINGS = YES;
@@ -1240,6 +1262,7 @@
PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian.SolianBroadcastExtension;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
@@ -1280,6 +1303,7 @@
PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian.SolianBroadcastExtension;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
@@ -1318,6 +1342,7 @@
PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian.SolianBroadcastExtension;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
@@ -1359,6 +1384,7 @@
PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian.SolianShareExtension;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_ENABLE_EXPLICIT_MODULES = NO;
@@ -1402,6 +1428,7 @@
PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian.SolianShareExtension;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_ENABLE_EXPLICIT_MODULES = NO;
SWIFT_VERSION = 5.0;
@@ -1443,6 +1470,7 @@
PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian.SolianShareExtension;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_ENABLE_EXPLICIT_MODULES = NO;
SWIFT_VERSION = 5.0;
@@ -1692,6 +1720,7 @@
CUSTOM_GROUP_ID = group.solsynth.solian;
DEVELOPMENT_TEAM = W7HPZ53V6B;
ENABLE_BITCODE = NO;
EXCLUDED_SOURCE_FILE_NAMES = "";
INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = Solian;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
@@ -1722,6 +1751,7 @@
CUSTOM_GROUP_ID = group.solsynth.solian;
DEVELOPMENT_TEAM = W7HPZ53V6B;
ENABLE_BITCODE = NO;
EXCLUDED_SOURCE_FILE_NAMES = "";
INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = Solian;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
@@ -1730,8 +1760,10 @@
"$(inherited)",
"@executable_path/Frameworks",
);
ONLY_ACTIVE_ARCH = NO;
PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos";
SWIFT_ENABLE_EXPLICIT_MODULES = "$(SWIFT_USE_INTEGRATED_DRIVER)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
@@ -1753,7 +1785,7 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
7310A7E32EB10963002C0FD3 /* Build configuration list for PBXNativeTarget "WatchRunner Watch App" */ = {
7310A7E32EB10963002C0FD3 /* Build configuration list for PBXNativeTarget "Solian Watch App" */ = {
isa = XCConfigurationList;
buildConfigurations = (
7310A7E02EB10963002C0FD3 /* Debug */,

View File

@@ -20,6 +20,20 @@
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "7310A7D32EB10962002C0FD3"
BuildableName = "Solian Watch App.app"
BlueprintName = "Solian Watch App"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction

View File

@@ -5,14 +5,14 @@ import WatchConnectivity
@main
@objc class AppDelegate: FlutterAppDelegate {
let notifyDelegate = NotifyDelegate()
private var watchConnectivityService: WatchConnectivityService?
private static var sharedWatchConnectivityService: WatchConnectivityService?
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
UNUserNotificationCenter.current().delegate = notifyDelegate
let replyableMessageCategory = UNNotificationCategory(
identifier: "CHAT_MESSAGE",
actions: [
@@ -25,38 +25,45 @@ import WatchConnectivity
intentIdentifiers: [],
options: []
)
UNUserNotificationCenter.current().setNotificationCategories([replyableMessageCategory])
GeneratedPluginRegistrant.register(with: self)
// Always initialize and retain a strong reference
if WCSession.isSupported() {
watchConnectivityService = WatchConnectivityService()
AppDelegate.sharedWatchConnectivityService = WatchConnectivityService.shared
} else {
print("[iOS] WCSession not supported on this device.")
}
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
class WatchConnectivityService: NSObject, WCSessionDelegate {
private let session: WCSession
override init() {
self.session = .default
final class WatchConnectivityService: NSObject, WCSessionDelegate {
static let shared = WatchConnectivityService()
private let session: WCSession = .default
private override init() {
super.init()
print("[iOS] Activating WCSession")
self.session.delegate = self
self.session.activate()
print("[iOS] Activating WCSession...")
session.delegate = self
session.activate()
}
// MARK: - WCSessionDelegate
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
if let error = error {
print("[iOS] WCSession activation failed with error: \(error.localizedDescription)")
return
print("[iOS] WCSession activation failed: \(error.localizedDescription)")
} else {
print("[iOS] WCSession activated with state: \(activationState.rawValue)")
if activationState == .activated {
sendDataToWatch()
}
}
print("[iOS] WCSession activated with state: \(activationState.rawValue)")
}
func sessionDidBecomeInactive(_ session: WCSession) {}
func sessionDidDeactivate(_ session: WCSession) {
@@ -69,10 +76,7 @@ class WatchConnectivityService: NSObject, WCSessionDelegate {
let token = UserDefaults.standard.getFlutterToken()
let serverUrl = UserDefaults.standard.getServerUrl()
print("[iOS] Retrieved token: \(token ?? "nil")")
print("[iOS] Retrieved serverUrl: \(serverUrl)")
var data: [String: Any] = ["serverUrl": serverUrl]
var data: [String: Any] = ["serverUrl": serverUrl ?? ""]
if let token = token {
data["token"] = token
}
@@ -81,4 +85,25 @@ class WatchConnectivityService: NSObject, WCSessionDelegate {
replyHandler(data)
}
}
func sendDataToWatch() {
guard session.activationState == .activated else {
return
}
let token = UserDefaults.standard.getFlutterToken()
let serverUrl = UserDefaults.standard.getServerUrl()
var data: [String: Any] = ["serverUrl": serverUrl ?? ""]
if let token = token {
data["token"] = token
}
do {
try session.updateApplicationContext(data)
print("[iOS] Sent application context: \(data)")
} catch {
print("[iOS] Failed to send application context: \(error.localizedDescription)")
}
}
}

View File

@@ -1,108 +1,111 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>AppGroupId</key>
<string>$(CUSTOM_GROUP_ID)</string>
<key>BUNDLE_ID</key>
<string>dev.solsynth.solian</string>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Solian</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>solian</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLSchemes</key>
<array>
<string>ShareMedia-$(PRODUCT_BUNDLE_IDENTIFIER)</string>
</array>
</dict>
<dict>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>CFBundleURLSchemes</key>
<array>
<string>solian</string>
</array>
</dict>
</array>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>CLIENT_ID</key>
<string>961776991058-stt7et4qvn3cpscl4r61gl1hnlatqkig.apps.googleusercontent.com</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSCalendarsUsageDescription</key>
<string>Grant access to Calander help us to shows Solar Calander with your own events.</string>
<key>NSCameraUsageDescription</key>
<string>Grant access to Camera will allow Solian take photo or video for your post.</string>
<key>NSFaceIDUsageDescription</key>
<string>Allow the Solar Network verify your ownership of the logged in account and continue your action quickly.</string>
<key>NSMicrophoneUsageDescription</key>
<string>Grant access to Microphone will allow Solian record audio for your post.</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>Grant access to Photo Library will allow Solian download photo to album for you.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Grant access to Photo Library will allow Solian upload photo or video for your post.</string>
<key>NSUserActivityTypes</key>
<array>
<string>INStartCallIntent</string>
<string>INSendMessageIntent</string>
</array>
<key>PLIST_VERSION</key>
<string>1</string>
<key>REVERSED_CLIENT_ID</key>
<string>com.googleusercontent.apps.961776991058-stt7et4qvn3cpscl4r61gl1hnlatqkig</string>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>audio</string>
<string>remote-notification</string>
<string>voip</string>
</array>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UIStatusBarHidden</key>
<false/>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
<string>UIInterfaceOrientationPortrait</string>
</array>
<key>WKCompanionAppBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
</array>
</dict>
</plist>
<dict>
<key>AppGroupId</key>
<string>$(CUSTOM_GROUP_ID)</string>
<key>BUNDLE_ID</key>
<string>dev.solsynth.solian</string>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true />
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Solian</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>solian</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLSchemes</key>
<array>
<string>ShareMedia-$(PRODUCT_BUNDLE_IDENTIFIER)</string>
</array>
</dict>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string></string>
<key>CFBundleURLSchemes</key>
<array>
<string>solian</string>
</array>
</dict>
</array>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>CLIENT_ID</key>
<string>961776991058-stt7et4qvn3cpscl4r61gl1hnlatqkig.apps.googleusercontent.com</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false />
<key>LSRequiresIPhoneOS</key>
<true />
<key>NSCalendarsUsageDescription</key>
<string>Grant access to Calander help us to shows Solar Calander with your own events.</string>
<key>NSCameraUsageDescription</key>
<string>Grant access to Camera will allow Solian take photo or video for your post.</string>
<key>NSFaceIDUsageDescription</key>
<string>Allow the Solar Network verify your ownership of the logged in account and continue
your action quickly.</string>
<key>NSMicrophoneUsageDescription</key>
<string>Grant access to Microphone will allow Solian record audio for your post.</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>Grant access to Photo Library will allow Solian download photo to album for you.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Grant access to Photo Library will allow Solian upload photo or video for your post.</string>
<key>NSUserActivityTypes</key>
<array>
<string>INStartCallIntent</string>
<string>INSendMessageIntent</string>
</array>
<key>PLIST_VERSION</key>
<string>1</string>
<key>REVERSED_CLIENT_ID</key>
<string>com.googleusercontent.apps.961776991058-stt7et4qvn3cpscl4r61gl1hnlatqkig</string>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true />
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>audio</string>
<string>remote-notification</string>
<string>voip</string>
</array>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UIStatusBarHidden</key>
<false />
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
<string>UIInterfaceOrientationPortrait</string>
</array>
<key>WKCompanionAppBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
</array>
</dict>
</plist>

View File

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 70 KiB

View File

@@ -44,7 +44,13 @@ struct WebSocketPacket {
// MARK: - Network Service
class NetworkService {
private let session = URLSession.shared
private let session: URLSession
init() {
let config = URLSessionConfiguration.ephemeral
config.waitsForConnectivity = true
session = URLSession(configuration: config)
}
// Add a serial queue for WebSocket operations
private let webSocketQueue = DispatchQueue(label: "com.solian.websocketQueue")

View File

@@ -15,6 +15,7 @@ class AppState: ObservableObject {
@Published var token: String? = nil
@Published var serverUrl: String? = nil
@Published var isReady = false
@Published var errorMessage: String? = nil
let networkService = NetworkService()
private var wcService = WatchConnectivityService()
@@ -22,13 +23,14 @@ class AppState: ObservableObject {
private var hasAttemptedConnection = false
init() {
wcService.$token.combineLatest(wcService.$serverUrl, wcService.$isFetched)
wcService.$token.combineLatest(wcService.$serverUrl, wcService.$isFetched, wcService.$errorMessage)
.receive(on: DispatchQueue.main)
.sink { [weak self] (token: String?, serverUrl: String?, isFetched: Bool?) in
.sink { [weak self] (token: String?, serverUrl: String?, isFetched: Bool?, errorMessage: String?) in
guard let self = self else { return }
self.token = token
self.serverUrl = serverUrl
self.errorMessage = errorMessage
if let token = token, let serverUrl = serverUrl, !token.isEmpty, !serverUrl.isEmpty {
self.isReady = true

View File

@@ -0,0 +1,113 @@
import WatchConnectivity
import Combine
import Foundation
class WatchConnectivityService: NSObject, WCSessionDelegate, ObservableObject {
@Published var token: String?
@Published var serverUrl: String?
@Published var isFetched: Bool?
@Published var errorMessage: String?
private let session: WCSession
private let userDefaults = UserDefaults.standard
private let tokenKey = "token"
private let serverUrlKey = "serverUrl"
override init() {
self.session = .default
super.init()
print("[watchOS] Activating WCSession")
self.session.delegate = self
self.session.activate()
// Load cached data
self.token = userDefaults.string(forKey: tokenKey)
self.serverUrl = userDefaults.string(forKey: serverUrlKey)
self.isFetched = false
}
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
if let error = error {
print("[watchOS] WCSession activation failed with error: \(error.localizedDescription)")
DispatchQueue.main.async {
self.errorMessage = "WCSession activation failed: \(error.localizedDescription)"
}
return
}
print("[watchOS] WCSession activated with state: \(activationState.rawValue)")
if activationState == .activated {
requestDataFromPhone()
}
}
func session(_ session: WCSession, didReceiveApplicationContext applicationContext: [String : Any]) {
print("[watchOS] Received application context: \(applicationContext)")
DispatchQueue.main.async {
if let token = applicationContext["token"] as? String {
self.token = token
self.userDefaults.set(token, forKey: self.tokenKey)
}
if let serverUrl = applicationContext["serverUrl"] as? String {
self.serverUrl = serverUrl
self.userDefaults.set(serverUrl, forKey: self.serverUrlKey)
}
self.isFetched = true
self.errorMessage = nil
}
}
func session(_ session: WCSession, didReceiveMessage message: [String : Any]) {
print("[watchOS] Received message: \(message)")
DispatchQueue.main.async {
if let token = message["token"] as? String {
self.token = token
self.userDefaults.set(token, forKey: self.tokenKey)
}
if let serverUrl = message["serverUrl"] as? String {
self.serverUrl = serverUrl
self.userDefaults.set(serverUrl, forKey: self.serverUrlKey)
}
}
}
func requestDataFromPhone() {
// Check if we already have valid data to avoid unnecessary requests
if let token = self.token, let serverUrl = self.serverUrl, !token.isEmpty, !serverUrl.isEmpty {
print("[watchOS] Skipped fetch - already have valid data")
self.isFetched = true
return
}
guard session.activationState == .activated else {
print("[watchOS] Session not activated yet, state: \(session.activationState.rawValue)")
DispatchQueue.main.async {
self.errorMessage = "Session not ready yet"
}
return
}
print("[watchOS] Requesting data from phone")
session.sendMessage(["request": "data"]) { [weak self] response in
guard let self = self else { return }
print("[watchOS] Received reply: \(response)")
DispatchQueue.main.async {
self.isFetched = true
if let token = response["token"] as? String {
self.token = token
self.userDefaults.set(token, forKey: self.tokenKey)
}
if let serverUrl = response["serverUrl"] as? String {
self.serverUrl = serverUrl
self.userDefaults.set(serverUrl, forKey: self.serverUrlKey)
}
self.errorMessage = nil // Clear any previous errors
}
} errorHandler: { error in
print("[watchOS] sendMessage failed with error: \(error.localizedDescription)")
DispatchQueue.main.async {
self.errorMessage = "Failed to get data from phone: \(error.localizedDescription)"
// Don't set isFetched = true on error - allow retry
}
}
}
}

View File

@@ -9,7 +9,7 @@ import SwiftUI
// The main view with the TabView for filtering.
struct ExploreView: View {
@StateObject private var appState = AppState()
@EnvironmentObject private var appState: AppState
@State private var isComposing = false
@State private var selectedTab: String = "Explore"
@@ -46,14 +46,22 @@ struct ExploreView: View {
}
}
}
.environmentObject(appState)
} else {
ProgressView { Text("Connecting to phone...") }
VStack {
ProgressView { Text("Syncing...") }
Button("Retry") {
appState.requestData()
}
}
}
}
.sheet(isPresented: $isComposing) {
ComposePostView()
.environmentObject(appState)
}
.alert("Error", isPresented: .constant(appState.errorMessage != nil), actions: {
Button("OK") { appState.errorMessage = nil }
}, message: {
Text(appState.errorMessage ?? "")
})
}
}

View File

@@ -9,6 +9,7 @@ import UserNotifications
import Intents
import Kingfisher
import UniformTypeIdentifiers
import KingfisherWebP
enum ParseNotificationPayloadError: Error {
case missingMetadata(String)
@@ -24,6 +25,11 @@ class NotificationService: UNNotificationServiceExtension {
_ request: UNNotificationRequest,
withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void
) {
KingfisherManager.shared.defaultOptions += [
.processor(WebPProcessor.default),
.cacheSerializer(WebPSerializer.default)
]
self.contentHandler = contentHandler
guard let bestAttemptContent = request.content.mutableCopy() as? UNMutableNotificationContent else {
contentHandler(request.content)
@@ -64,40 +70,12 @@ class NotificationService: UNNotificationServiceExtension {
let handle = INPersonHandle(value: "\(metaCopy["user_id"] ?? "")", type: .unknown)
if let pfpUrl = pfpUrl, let url = URL(string: pfpUrl) {
let targetSize = 512
let scaleProcessor = ResizingImageProcessor(referenceSize: CGSize(width: targetSize, height: targetSize), mode: .aspectFit)
KingfisherManager.shared.retrieveImage(with: url, options: [.processor(scaleProcessor)], completionHandler: { result in
var image: Data?
switch result {
case .success(let value):
image = value.image.pngData()
case .failure(let error):
print("Unable to get pfp url: \(error)")
}
let sender = INPerson(
personHandle: handle,
nameComponents: PersonNameComponents(nickname: "\(metaCopy["sender_name"] ?? "")"),
displayName: content.title,
image: image == nil ? nil : INImage(imageData: image!),
contactIdentifier: nil,
customIdentifier: nil
)
let intent = self.createMessageIntent(with: sender, meta: metaCopy, body: content.body)
self.donateInteraction(for: intent)
content.categoryIdentifier = "CHAT_MESSAGE"
self.contentHandler?(content)
})
} else {
let completeNotificationProcessing: (Data?) -> Void = { imageData in
let sender = INPerson(
personHandle: handle,
nameComponents: PersonNameComponents(nickname: "\(metaCopy["sender_name"] ?? "")"),
displayName: content.title,
image: nil,
image: imageData == nil ? nil : INImage(imageData: imageData!),
contactIdentifier: nil,
customIdentifier: nil
)
@@ -105,8 +83,37 @@ class NotificationService: UNNotificationServiceExtension {
let intent = self.createMessageIntent(with: sender, meta: metaCopy, body: content.body)
self.donateInteraction(for: intent)
content.categoryIdentifier = "CHAT_MESSAGE"
self.contentHandler?(content)
if let updatedContent = try? request.content.updating(from: intent) {
if let mutableContent = updatedContent.mutableCopy() as? UNMutableNotificationContent {
mutableContent.categoryIdentifier = "CHAT_MESSAGE"
self.contentHandler?(mutableContent)
} else {
self.contentHandler?(updatedContent)
}
} else {
content.categoryIdentifier = "CHAT_MESSAGE"
self.contentHandler?(content)
}
}
if let pfpUrl = pfpUrl, let url = URL(string: pfpUrl) {
let targetSize = 512
let scaleProcessor = ResizingImageProcessor(referenceSize: CGSize(width: targetSize, height: targetSize), mode: .aspectFit)
KingfisherManager.shared.retrieveImage(with: url, options: [
.processor(scaleProcessor)
], completionHandler: { result in
var image: Data?
switch result {
case .success(let value):
image = value.image.pngData()
case .failure(let error):
print("Unable to get pfp url: \(error)")
}
completeNotificationProcessing(image)
})
} else {
completeNotificationProcessing(nil)
}
}

View File

@@ -1,15 +0,0 @@
//
// CustomPreviews.swift
// WatchRunner Watch App
//
// Created by LittleSheep on 2025/10/29.
//
import SwiftUI
#Preview {
NavigationStack {
ActivityListView(filter: "Preview", mockActivities: SnActivity.mock)
.environmentObject(AppState())
}
}

View File

@@ -1,35 +0,0 @@
//
// MockData.swift
// WatchRunner Watch App
//
// Created by LittleSheep on 2025/10/29.
//
import Foundation
#if DEBUG
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

View File

@@ -1,93 +0,0 @@
//
// WatchConnectivityService.swift
// WatchRunner Watch App
//
// Created by LittleSheep on 2025/10/29.
//
import Foundation
import WatchConnectivity
import Combine
// MARK: - Watch Connectivity
class WatchConnectivityService: NSObject, WCSessionDelegate, ObservableObject {
@Published var token: String?
@Published var serverUrl: String?
@Published var isFetched: Bool?
private let session: WCSession
private let userDefaults = UserDefaults.standard
private let tokenKey = "token"
private let serverUrlKey = "serverUrl"
override init() {
self.session = .default
super.init()
print("[watchOS] Activating WCSession")
self.session.delegate = self
self.session.activate()
// Load cached data
self.token = userDefaults.string(forKey: tokenKey)
self.serverUrl = userDefaults.string(forKey: serverUrlKey)
self.isFetched = false
}
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
if let error = error {
print("[watchOS] WCSession activation failed with error: \(error.localizedDescription)")
return
}
print("[watchOS] WCSession activated with state: \(activationState.rawValue)")
if activationState == .activated {
requestDataFromPhone()
}
}
func session(_ session: WCSession, didReceiveMessage message: [String : Any]) {
print("[watchOS] Received message: \(message)")
DispatchQueue.main.async {
if let token = message["token"] as? String {
self.token = token
self.userDefaults.set(token, forKey: self.tokenKey)
}
if let serverUrl = message["serverUrl"] as? String {
self.serverUrl = serverUrl
self.userDefaults.set(serverUrl, forKey: self.serverUrlKey)
}
}
}
func requestDataFromPhone() {
if self.isFetched == true {
print("[watchOS] Skipped fetch from phone due to tried.")
return
}
guard session.isReachable else {
self.isFetched = true
print("[watchOS] Phone is not reachable")
return
}
print("[watchOS] Requesting data from phone")
session.sendMessage(["request": "data"]) { [weak self] response in
guard let self = self else { return }
print("[watchOS] Received reply: \(response)")
DispatchQueue.main.async {
self.isFetched = true
if let token = response["token"] as? String {
self.token = token
self.userDefaults.set(token, forKey: self.tokenKey)
}
if let serverUrl = response["serverUrl"] as? String {
self.serverUrl = serverUrl
self.userDefaults.set(serverUrl, forKey: self.serverUrlKey)
}
}
} errorHandler: { error in
print("[watchOS] sendMessage failed with error: \(error.localizedDescription)")
}
}
}

View File

@@ -30,6 +30,7 @@ import 'package:talker_flutter/talker_flutter.dart';
import 'package:talker_riverpod_logger/talker_riverpod_logger.dart';
import 'package:url_launcher/url_launcher_string.dart';
import 'package:window_manager/window_manager.dart';
import 'package:protocol_handler/protocol_handler.dart';
@pragma('vm:entry-point')
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
@@ -50,6 +51,12 @@ void main() async {
GoRouter.optionURLReflectsImperativeAPIs = true;
}
if (!kIsWeb && (Platform.isLinux || Platform.isMacOS || Platform.isWindows)) {
talker.info("[SplashScreen] Initializing desktop window manager...");
await protocolHandler.register('myprotocol');
talker.info("[SplashScreen] Desktop window manager is ready!");
}
try {
await EasyLocalization.ensureInitialized();

View File

@@ -19,8 +19,8 @@ sealed class SnNotableDay with _$SnNotableDay {
}
@freezed
sealed class SnActivity with _$SnActivity {
const factory SnActivity({
sealed class SnTimelineEvent with _$SnTimelineEvent {
const factory SnTimelineEvent({
required String id,
required String type,
required String resourceIdentifier,
@@ -28,10 +28,10 @@ sealed class SnActivity with _$SnActivity {
required DateTime createdAt,
required DateTime updatedAt,
required DateTime? deletedAt,
}) = _SnActivity;
}) = _SnTimelineEvent;
factory SnActivity.fromJson(Map<String, dynamic> json) =>
_$SnActivityFromJson(json);
factory SnTimelineEvent.fromJson(Map<String, dynamic> json) =>
_$SnTimelineEventFromJson(json);
}
@freezed
@@ -74,3 +74,29 @@ sealed class SnEventCalendarEntry with _$SnEventCalendarEntry {
factory SnEventCalendarEntry.fromJson(Map<String, dynamic> json) =>
_$SnEventCalendarEntryFromJson(json);
}
@freezed
sealed class SnPresenceActivity with _$SnPresenceActivity {
const factory SnPresenceActivity({
required String id,
required int type,
required String? manualId,
required String? title,
required String? subtitle,
required String? caption,
required String? titleUrl,
required String? subtitleUrl,
required String? smallImage,
required String? largeImage,
required Map<String, dynamic>? meta,
required int leaseMinutes,
required DateTime leaseExpiresAt,
required String accountId,
required DateTime createdAt,
required DateTime updatedAt,
required DateTime? deletedAt,
}) = _SnPresenceActivity;
factory SnPresenceActivity.fromJson(Map<String, dynamic> json) =>
_$SnPresenceActivityFromJson(json);
}

View File

@@ -288,22 +288,22 @@ as List<int>,
/// @nodoc
mixin _$SnActivity {
mixin _$SnTimelineEvent {
String get id; String get type; String get resourceIdentifier; dynamic get data; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
/// Create a copy of SnActivity
/// Create a copy of SnTimelineEvent
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$SnActivityCopyWith<SnActivity> get copyWith => _$SnActivityCopyWithImpl<SnActivity>(this as SnActivity, _$identity);
$SnTimelineEventCopyWith<SnTimelineEvent> get copyWith => _$SnTimelineEventCopyWithImpl<SnTimelineEvent>(this as SnTimelineEvent, _$identity);
/// Serializes this SnActivity to a JSON map.
/// Serializes this SnTimelineEvent to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnActivity&&(identical(other.id, id) || other.id == id)&&(identical(other.type, type) || other.type == type)&&(identical(other.resourceIdentifier, resourceIdentifier) || other.resourceIdentifier == resourceIdentifier)&&const DeepCollectionEquality().equals(other.data, data)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnTimelineEvent&&(identical(other.id, id) || other.id == id)&&(identical(other.type, type) || other.type == type)&&(identical(other.resourceIdentifier, resourceIdentifier) || other.resourceIdentifier == resourceIdentifier)&&const DeepCollectionEquality().equals(other.data, data)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -312,15 +312,15 @@ int get hashCode => Object.hash(runtimeType,id,type,resourceIdentifier,const Dee
@override
String toString() {
return 'SnActivity(id: $id, type: $type, resourceIdentifier: $resourceIdentifier, data: $data, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
return 'SnTimelineEvent(id: $id, type: $type, resourceIdentifier: $resourceIdentifier, data: $data, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
}
}
/// @nodoc
abstract mixin class $SnActivityCopyWith<$Res> {
factory $SnActivityCopyWith(SnActivity value, $Res Function(SnActivity) _then) = _$SnActivityCopyWithImpl;
abstract mixin class $SnTimelineEventCopyWith<$Res> {
factory $SnTimelineEventCopyWith(SnTimelineEvent value, $Res Function(SnTimelineEvent) _then) = _$SnTimelineEventCopyWithImpl;
@useResult
$Res call({
String id, String type, String resourceIdentifier, dynamic data, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
@@ -331,14 +331,14 @@ $Res call({
}
/// @nodoc
class _$SnActivityCopyWithImpl<$Res>
implements $SnActivityCopyWith<$Res> {
_$SnActivityCopyWithImpl(this._self, this._then);
class _$SnTimelineEventCopyWithImpl<$Res>
implements $SnTimelineEventCopyWith<$Res> {
_$SnTimelineEventCopyWithImpl(this._self, this._then);
final SnActivity _self;
final $Res Function(SnActivity) _then;
final SnTimelineEvent _self;
final $Res Function(SnTimelineEvent) _then;
/// Create a copy of SnActivity
/// Create a copy of SnTimelineEvent
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? type = null,Object? resourceIdentifier = null,Object? data = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
return _then(_self.copyWith(
@@ -356,8 +356,8 @@ as DateTime?,
}
/// Adds pattern-matching-related methods to [SnActivity].
extension SnActivityPatterns on SnActivity {
/// Adds pattern-matching-related methods to [SnTimelineEvent].
extension SnTimelineEventPatterns on SnTimelineEvent {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
@@ -370,10 +370,10 @@ extension SnActivityPatterns on SnActivity {
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _SnActivity value)? $default,{required TResult orElse(),}){
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _SnTimelineEvent value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _SnActivity() when $default != null:
case _SnTimelineEvent() when $default != null:
return $default(_that);case _:
return orElse();
@@ -392,10 +392,10 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _SnActivity value) $default,){
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _SnTimelineEvent value) $default,){
final _that = this;
switch (_that) {
case _SnActivity():
case _SnTimelineEvent():
return $default(_that);}
}
/// A variant of `map` that fallback to returning `null`.
@@ -410,10 +410,10 @@ return $default(_that);}
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _SnActivity value)? $default,){
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _SnTimelineEvent value)? $default,){
final _that = this;
switch (_that) {
case _SnActivity() when $default != null:
case _SnTimelineEvent() when $default != null:
return $default(_that);case _:
return null;
@@ -433,7 +433,7 @@ return $default(_that);case _:
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String type, String resourceIdentifier, dynamic data, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _SnActivity() when $default != null:
case _SnTimelineEvent() when $default != null:
return $default(_that.id,_that.type,_that.resourceIdentifier,_that.data,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
return orElse();
@@ -454,7 +454,7 @@ return $default(_that.id,_that.type,_that.resourceIdentifier,_that.data,_that.cr
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String type, String resourceIdentifier, dynamic data, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt) $default,) {final _that = this;
switch (_that) {
case _SnActivity():
case _SnTimelineEvent():
return $default(_that.id,_that.type,_that.resourceIdentifier,_that.data,_that.createdAt,_that.updatedAt,_that.deletedAt);}
}
/// A variant of `when` that fallback to returning `null`
@@ -471,7 +471,7 @@ return $default(_that.id,_that.type,_that.resourceIdentifier,_that.data,_that.cr
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String type, String resourceIdentifier, dynamic data, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,) {final _that = this;
switch (_that) {
case _SnActivity() when $default != null:
case _SnTimelineEvent() when $default != null:
return $default(_that.id,_that.type,_that.resourceIdentifier,_that.data,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
return null;
@@ -483,9 +483,9 @@ return $default(_that.id,_that.type,_that.resourceIdentifier,_that.data,_that.cr
/// @nodoc
@JsonSerializable()
class _SnActivity implements SnActivity {
const _SnActivity({required this.id, required this.type, required this.resourceIdentifier, required this.data, required this.createdAt, required this.updatedAt, required this.deletedAt});
factory _SnActivity.fromJson(Map<String, dynamic> json) => _$SnActivityFromJson(json);
class _SnTimelineEvent implements SnTimelineEvent {
const _SnTimelineEvent({required this.id, required this.type, required this.resourceIdentifier, required this.data, required this.createdAt, required this.updatedAt, required this.deletedAt});
factory _SnTimelineEvent.fromJson(Map<String, dynamic> json) => _$SnTimelineEventFromJson(json);
@override final String id;
@override final String type;
@@ -495,20 +495,20 @@ class _SnActivity implements SnActivity {
@override final DateTime updatedAt;
@override final DateTime? deletedAt;
/// Create a copy of SnActivity
/// Create a copy of SnTimelineEvent
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$SnActivityCopyWith<_SnActivity> get copyWith => __$SnActivityCopyWithImpl<_SnActivity>(this, _$identity);
_$SnTimelineEventCopyWith<_SnTimelineEvent> get copyWith => __$SnTimelineEventCopyWithImpl<_SnTimelineEvent>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$SnActivityToJson(this, );
return _$SnTimelineEventToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnActivity&&(identical(other.id, id) || other.id == id)&&(identical(other.type, type) || other.type == type)&&(identical(other.resourceIdentifier, resourceIdentifier) || other.resourceIdentifier == resourceIdentifier)&&const DeepCollectionEquality().equals(other.data, data)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnTimelineEvent&&(identical(other.id, id) || other.id == id)&&(identical(other.type, type) || other.type == type)&&(identical(other.resourceIdentifier, resourceIdentifier) || other.resourceIdentifier == resourceIdentifier)&&const DeepCollectionEquality().equals(other.data, data)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -517,15 +517,15 @@ int get hashCode => Object.hash(runtimeType,id,type,resourceIdentifier,const Dee
@override
String toString() {
return 'SnActivity(id: $id, type: $type, resourceIdentifier: $resourceIdentifier, data: $data, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
return 'SnTimelineEvent(id: $id, type: $type, resourceIdentifier: $resourceIdentifier, data: $data, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
}
}
/// @nodoc
abstract mixin class _$SnActivityCopyWith<$Res> implements $SnActivityCopyWith<$Res> {
factory _$SnActivityCopyWith(_SnActivity value, $Res Function(_SnActivity) _then) = __$SnActivityCopyWithImpl;
abstract mixin class _$SnTimelineEventCopyWith<$Res> implements $SnTimelineEventCopyWith<$Res> {
factory _$SnTimelineEventCopyWith(_SnTimelineEvent value, $Res Function(_SnTimelineEvent) _then) = __$SnTimelineEventCopyWithImpl;
@override @useResult
$Res call({
String id, String type, String resourceIdentifier, dynamic data, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
@@ -536,17 +536,17 @@ $Res call({
}
/// @nodoc
class __$SnActivityCopyWithImpl<$Res>
implements _$SnActivityCopyWith<$Res> {
__$SnActivityCopyWithImpl(this._self, this._then);
class __$SnTimelineEventCopyWithImpl<$Res>
implements _$SnTimelineEventCopyWith<$Res> {
__$SnTimelineEventCopyWithImpl(this._self, this._then);
final _SnActivity _self;
final $Res Function(_SnActivity) _then;
final _SnTimelineEvent _self;
final $Res Function(_SnTimelineEvent) _then;
/// Create a copy of SnActivity
/// Create a copy of SnTimelineEvent
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? type = null,Object? resourceIdentifier = null,Object? data = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
return _then(_SnActivity(
return _then(_SnTimelineEvent(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
as String,resourceIdentifier: null == resourceIdentifier ? _self.resourceIdentifier : resourceIdentifier // ignore: cast_nullable_to_non_nullable
@@ -1425,4 +1425,317 @@ $SnCheckInResultCopyWith<$Res>? get checkInResult {
}
}
/// @nodoc
mixin _$SnPresenceActivity {
String get id; int get type; String? get manualId; String? get title; String? get subtitle; String? get caption; String? get titleUrl; String? get subtitleUrl; String? get smallImage; String? get largeImage; Map<String, dynamic>? get meta; int get leaseMinutes; DateTime get leaseExpiresAt; String get accountId; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
/// Create a copy of SnPresenceActivity
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$SnPresenceActivityCopyWith<SnPresenceActivity> get copyWith => _$SnPresenceActivityCopyWithImpl<SnPresenceActivity>(this as SnPresenceActivity, _$identity);
/// Serializes this SnPresenceActivity to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnPresenceActivity&&(identical(other.id, id) || other.id == id)&&(identical(other.type, type) || other.type == type)&&(identical(other.manualId, manualId) || other.manualId == manualId)&&(identical(other.title, title) || other.title == title)&&(identical(other.subtitle, subtitle) || other.subtitle == subtitle)&&(identical(other.caption, caption) || other.caption == caption)&&(identical(other.titleUrl, titleUrl) || other.titleUrl == titleUrl)&&(identical(other.subtitleUrl, subtitleUrl) || other.subtitleUrl == subtitleUrl)&&(identical(other.smallImage, smallImage) || other.smallImage == smallImage)&&(identical(other.largeImage, largeImage) || other.largeImage == largeImage)&&const DeepCollectionEquality().equals(other.meta, meta)&&(identical(other.leaseMinutes, leaseMinutes) || other.leaseMinutes == leaseMinutes)&&(identical(other.leaseExpiresAt, leaseExpiresAt) || other.leaseExpiresAt == leaseExpiresAt)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,type,manualId,title,subtitle,caption,titleUrl,subtitleUrl,smallImage,largeImage,const DeepCollectionEquality().hash(meta),leaseMinutes,leaseExpiresAt,accountId,createdAt,updatedAt,deletedAt);
@override
String toString() {
return 'SnPresenceActivity(id: $id, type: $type, manualId: $manualId, title: $title, subtitle: $subtitle, caption: $caption, titleUrl: $titleUrl, subtitleUrl: $subtitleUrl, smallImage: $smallImage, largeImage: $largeImage, meta: $meta, leaseMinutes: $leaseMinutes, leaseExpiresAt: $leaseExpiresAt, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
}
}
/// @nodoc
abstract mixin class $SnPresenceActivityCopyWith<$Res> {
factory $SnPresenceActivityCopyWith(SnPresenceActivity value, $Res Function(SnPresenceActivity) _then) = _$SnPresenceActivityCopyWithImpl;
@useResult
$Res call({
String id, int type, String? manualId, String? title, String? subtitle, String? caption, String? titleUrl, String? subtitleUrl, String? smallImage, String? largeImage, Map<String, dynamic>? meta, int leaseMinutes, DateTime leaseExpiresAt, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
});
}
/// @nodoc
class _$SnPresenceActivityCopyWithImpl<$Res>
implements $SnPresenceActivityCopyWith<$Res> {
_$SnPresenceActivityCopyWithImpl(this._self, this._then);
final SnPresenceActivity _self;
final $Res Function(SnPresenceActivity) _then;
/// Create a copy of SnPresenceActivity
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? type = null,Object? manualId = freezed,Object? title = freezed,Object? subtitle = freezed,Object? caption = freezed,Object? titleUrl = freezed,Object? subtitleUrl = freezed,Object? smallImage = freezed,Object? largeImage = freezed,Object? meta = freezed,Object? leaseMinutes = null,Object? leaseExpiresAt = null,Object? accountId = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
return _then(_self.copyWith(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
as int,manualId: freezed == manualId ? _self.manualId : manualId // ignore: cast_nullable_to_non_nullable
as String?,title: freezed == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
as String?,subtitle: freezed == subtitle ? _self.subtitle : subtitle // ignore: cast_nullable_to_non_nullable
as String?,caption: freezed == caption ? _self.caption : caption // ignore: cast_nullable_to_non_nullable
as String?,titleUrl: freezed == titleUrl ? _self.titleUrl : titleUrl // ignore: cast_nullable_to_non_nullable
as String?,subtitleUrl: freezed == subtitleUrl ? _self.subtitleUrl : subtitleUrl // ignore: cast_nullable_to_non_nullable
as String?,smallImage: freezed == smallImage ? _self.smallImage : smallImage // ignore: cast_nullable_to_non_nullable
as String?,largeImage: freezed == largeImage ? _self.largeImage : largeImage // ignore: cast_nullable_to_non_nullable
as String?,meta: freezed == meta ? _self.meta : meta // ignore: cast_nullable_to_non_nullable
as Map<String, dynamic>?,leaseMinutes: null == leaseMinutes ? _self.leaseMinutes : leaseMinutes // ignore: cast_nullable_to_non_nullable
as int,leaseExpiresAt: null == leaseExpiresAt ? _self.leaseExpiresAt : leaseExpiresAt // ignore: cast_nullable_to_non_nullable
as DateTime,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
as String,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
as DateTime?,
));
}
}
/// Adds pattern-matching-related methods to [SnPresenceActivity].
extension SnPresenceActivityPatterns on SnPresenceActivity {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _SnPresenceActivity value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _SnPresenceActivity() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _SnPresenceActivity value) $default,){
final _that = this;
switch (_that) {
case _SnPresenceActivity():
return $default(_that);}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _SnPresenceActivity value)? $default,){
final _that = this;
switch (_that) {
case _SnPresenceActivity() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, int type, String? manualId, String? title, String? subtitle, String? caption, String? titleUrl, String? subtitleUrl, String? smallImage, String? largeImage, Map<String, dynamic>? meta, int leaseMinutes, DateTime leaseExpiresAt, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _SnPresenceActivity() when $default != null:
return $default(_that.id,_that.type,_that.manualId,_that.title,_that.subtitle,_that.caption,_that.titleUrl,_that.subtitleUrl,_that.smallImage,_that.largeImage,_that.meta,_that.leaseMinutes,_that.leaseExpiresAt,_that.accountId,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, int type, String? manualId, String? title, String? subtitle, String? caption, String? titleUrl, String? subtitleUrl, String? smallImage, String? largeImage, Map<String, dynamic>? meta, int leaseMinutes, DateTime leaseExpiresAt, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt) $default,) {final _that = this;
switch (_that) {
case _SnPresenceActivity():
return $default(_that.id,_that.type,_that.manualId,_that.title,_that.subtitle,_that.caption,_that.titleUrl,_that.subtitleUrl,_that.smallImage,_that.largeImage,_that.meta,_that.leaseMinutes,_that.leaseExpiresAt,_that.accountId,_that.createdAt,_that.updatedAt,_that.deletedAt);}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, int type, String? manualId, String? title, String? subtitle, String? caption, String? titleUrl, String? subtitleUrl, String? smallImage, String? largeImage, Map<String, dynamic>? meta, int leaseMinutes, DateTime leaseExpiresAt, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,) {final _that = this;
switch (_that) {
case _SnPresenceActivity() when $default != null:
return $default(_that.id,_that.type,_that.manualId,_that.title,_that.subtitle,_that.caption,_that.titleUrl,_that.subtitleUrl,_that.smallImage,_that.largeImage,_that.meta,_that.leaseMinutes,_that.leaseExpiresAt,_that.accountId,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
return null;
}
}
}
/// @nodoc
@JsonSerializable()
class _SnPresenceActivity implements SnPresenceActivity {
const _SnPresenceActivity({required this.id, required this.type, required this.manualId, required this.title, required this.subtitle, required this.caption, required this.titleUrl, required this.subtitleUrl, required this.smallImage, required this.largeImage, required final Map<String, dynamic>? meta, required this.leaseMinutes, required this.leaseExpiresAt, required this.accountId, required this.createdAt, required this.updatedAt, required this.deletedAt}): _meta = meta;
factory _SnPresenceActivity.fromJson(Map<String, dynamic> json) => _$SnPresenceActivityFromJson(json);
@override final String id;
@override final int type;
@override final String? manualId;
@override final String? title;
@override final String? subtitle;
@override final String? caption;
@override final String? titleUrl;
@override final String? subtitleUrl;
@override final String? smallImage;
@override final String? largeImage;
final Map<String, dynamic>? _meta;
@override Map<String, dynamic>? get meta {
final value = _meta;
if (value == null) return null;
if (_meta is EqualUnmodifiableMapView) return _meta;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(value);
}
@override final int leaseMinutes;
@override final DateTime leaseExpiresAt;
@override final String accountId;
@override final DateTime createdAt;
@override final DateTime updatedAt;
@override final DateTime? deletedAt;
/// Create a copy of SnPresenceActivity
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$SnPresenceActivityCopyWith<_SnPresenceActivity> get copyWith => __$SnPresenceActivityCopyWithImpl<_SnPresenceActivity>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$SnPresenceActivityToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnPresenceActivity&&(identical(other.id, id) || other.id == id)&&(identical(other.type, type) || other.type == type)&&(identical(other.manualId, manualId) || other.manualId == manualId)&&(identical(other.title, title) || other.title == title)&&(identical(other.subtitle, subtitle) || other.subtitle == subtitle)&&(identical(other.caption, caption) || other.caption == caption)&&(identical(other.titleUrl, titleUrl) || other.titleUrl == titleUrl)&&(identical(other.subtitleUrl, subtitleUrl) || other.subtitleUrl == subtitleUrl)&&(identical(other.smallImage, smallImage) || other.smallImage == smallImage)&&(identical(other.largeImage, largeImage) || other.largeImage == largeImage)&&const DeepCollectionEquality().equals(other._meta, _meta)&&(identical(other.leaseMinutes, leaseMinutes) || other.leaseMinutes == leaseMinutes)&&(identical(other.leaseExpiresAt, leaseExpiresAt) || other.leaseExpiresAt == leaseExpiresAt)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,type,manualId,title,subtitle,caption,titleUrl,subtitleUrl,smallImage,largeImage,const DeepCollectionEquality().hash(_meta),leaseMinutes,leaseExpiresAt,accountId,createdAt,updatedAt,deletedAt);
@override
String toString() {
return 'SnPresenceActivity(id: $id, type: $type, manualId: $manualId, title: $title, subtitle: $subtitle, caption: $caption, titleUrl: $titleUrl, subtitleUrl: $subtitleUrl, smallImage: $smallImage, largeImage: $largeImage, meta: $meta, leaseMinutes: $leaseMinutes, leaseExpiresAt: $leaseExpiresAt, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
}
}
/// @nodoc
abstract mixin class _$SnPresenceActivityCopyWith<$Res> implements $SnPresenceActivityCopyWith<$Res> {
factory _$SnPresenceActivityCopyWith(_SnPresenceActivity value, $Res Function(_SnPresenceActivity) _then) = __$SnPresenceActivityCopyWithImpl;
@override @useResult
$Res call({
String id, int type, String? manualId, String? title, String? subtitle, String? caption, String? titleUrl, String? subtitleUrl, String? smallImage, String? largeImage, Map<String, dynamic>? meta, int leaseMinutes, DateTime leaseExpiresAt, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
});
}
/// @nodoc
class __$SnPresenceActivityCopyWithImpl<$Res>
implements _$SnPresenceActivityCopyWith<$Res> {
__$SnPresenceActivityCopyWithImpl(this._self, this._then);
final _SnPresenceActivity _self;
final $Res Function(_SnPresenceActivity) _then;
/// Create a copy of SnPresenceActivity
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? type = null,Object? manualId = freezed,Object? title = freezed,Object? subtitle = freezed,Object? caption = freezed,Object? titleUrl = freezed,Object? subtitleUrl = freezed,Object? smallImage = freezed,Object? largeImage = freezed,Object? meta = freezed,Object? leaseMinutes = null,Object? leaseExpiresAt = null,Object? accountId = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
return _then(_SnPresenceActivity(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
as int,manualId: freezed == manualId ? _self.manualId : manualId // ignore: cast_nullable_to_non_nullable
as String?,title: freezed == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
as String?,subtitle: freezed == subtitle ? _self.subtitle : subtitle // ignore: cast_nullable_to_non_nullable
as String?,caption: freezed == caption ? _self.caption : caption // ignore: cast_nullable_to_non_nullable
as String?,titleUrl: freezed == titleUrl ? _self.titleUrl : titleUrl // ignore: cast_nullable_to_non_nullable
as String?,subtitleUrl: freezed == subtitleUrl ? _self.subtitleUrl : subtitleUrl // ignore: cast_nullable_to_non_nullable
as String?,smallImage: freezed == smallImage ? _self.smallImage : smallImage // ignore: cast_nullable_to_non_nullable
as String?,largeImage: freezed == largeImage ? _self.largeImage : largeImage // ignore: cast_nullable_to_non_nullable
as String?,meta: freezed == meta ? _self._meta : meta // ignore: cast_nullable_to_non_nullable
as Map<String, dynamic>?,leaseMinutes: null == leaseMinutes ? _self.leaseMinutes : leaseMinutes // ignore: cast_nullable_to_non_nullable
as int,leaseExpiresAt: null == leaseExpiresAt ? _self.leaseExpiresAt : leaseExpiresAt // ignore: cast_nullable_to_non_nullable
as DateTime,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
as String,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
as DateTime?,
));
}
}
// dart format on

View File

@@ -27,20 +27,21 @@ Map<String, dynamic> _$SnNotableDayToJson(_SnNotableDay instance) =>
'holidays': instance.holidays,
};
_SnActivity _$SnActivityFromJson(Map<String, dynamic> json) => _SnActivity(
id: json['id'] as String,
type: json['type'] as String,
resourceIdentifier: json['resource_identifier'] as String,
data: json['data'],
createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String),
deletedAt:
json['deleted_at'] == null
? null
: DateTime.parse(json['deleted_at'] as String),
);
_SnTimelineEvent _$SnTimelineEventFromJson(Map<String, dynamic> json) =>
_SnTimelineEvent(
id: json['id'] as String,
type: json['type'] as String,
resourceIdentifier: json['resource_identifier'] as String,
data: json['data'],
createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String),
deletedAt:
json['deleted_at'] == null
? null
: DateTime.parse(json['deleted_at'] as String),
);
Map<String, dynamic> _$SnActivityToJson(_SnActivity instance) =>
Map<String, dynamic> _$SnTimelineEventToJson(_SnTimelineEvent instance) =>
<String, dynamic>{
'id': instance.id,
'type': instance.type,
@@ -121,3 +122,48 @@ Map<String, dynamic> _$SnEventCalendarEntryToJson(
'check_in_result': instance.checkInResult?.toJson(),
'statuses': instance.statuses.map((e) => e.toJson()).toList(),
};
_SnPresenceActivity _$SnPresenceActivityFromJson(Map<String, dynamic> json) =>
_SnPresenceActivity(
id: json['id'] as String,
type: (json['type'] as num).toInt(),
manualId: json['manual_id'] as String?,
title: json['title'] as String?,
subtitle: json['subtitle'] as String?,
caption: json['caption'] as String?,
titleUrl: json['title_url'] as String?,
subtitleUrl: json['subtitle_url'] as String?,
smallImage: json['small_image'] as String?,
largeImage: json['large_image'] as String?,
meta: json['meta'] as Map<String, dynamic>?,
leaseMinutes: (json['lease_minutes'] as num).toInt(),
leaseExpiresAt: DateTime.parse(json['lease_expires_at'] as String),
accountId: json['account_id'] as String,
createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String),
deletedAt:
json['deleted_at'] == null
? null
: DateTime.parse(json['deleted_at'] as String),
);
Map<String, dynamic> _$SnPresenceActivityToJson(_SnPresenceActivity instance) =>
<String, dynamic>{
'id': instance.id,
'type': instance.type,
'manual_id': instance.manualId,
'title': instance.title,
'subtitle': instance.subtitle,
'caption': instance.caption,
'title_url': instance.titleUrl,
'subtitle_url': instance.subtitleUrl,
'small_image': instance.smallImage,
'large_image': instance.largeImage,
'meta': instance.meta,
'lease_minutes': instance.leaseMinutes,
'lease_expires_at': instance.leaseExpiresAt.toIso8601String(),
'account_id': instance.accountId,
'created_at': instance.createdAt.toIso8601String(),
'updated_at': instance.updatedAt.toIso8601String(),
'deleted_at': instance.deletedAt?.toIso8601String(),
};

View File

@@ -8,7 +8,7 @@ part 'poll.g.dart';
@freezed
sealed class SnPollWithStats with _$SnPollWithStats {
const factory SnPollWithStats({
required Map<String, dynamic>? userAnswer,
required SnPollAnswer? userAnswer,
@Default({}) Map<String, dynamic> stats,
required String id,
required List<SnPollQuestion> questions,

View File

@@ -15,7 +15,7 @@ T _$identity<T>(T value) => value;
/// @nodoc
mixin _$SnPollWithStats {
Map<String, dynamic>? get userAnswer; Map<String, dynamic> get stats; String get id; List<SnPollQuestion> get questions; String? get title; String? get description; DateTime? get endedAt; String get publisherId; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
SnPollAnswer? get userAnswer; Map<String, dynamic> get stats; String get id; List<SnPollQuestion> get questions; String? get title; String? get description; DateTime? get endedAt; String get publisherId; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
/// Create a copy of SnPollWithStats
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -28,12 +28,12 @@ $SnPollWithStatsCopyWith<SnPollWithStats> get copyWith => _$SnPollWithStatsCopyW
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnPollWithStats&&const DeepCollectionEquality().equals(other.userAnswer, userAnswer)&&const DeepCollectionEquality().equals(other.stats, stats)&&(identical(other.id, id) || other.id == id)&&const DeepCollectionEquality().equals(other.questions, questions)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.endedAt, endedAt) || other.endedAt == endedAt)&&(identical(other.publisherId, publisherId) || other.publisherId == publisherId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnPollWithStats&&(identical(other.userAnswer, userAnswer) || other.userAnswer == userAnswer)&&const DeepCollectionEquality().equals(other.stats, stats)&&(identical(other.id, id) || other.id == id)&&const DeepCollectionEquality().equals(other.questions, questions)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.endedAt, endedAt) || other.endedAt == endedAt)&&(identical(other.publisherId, publisherId) || other.publisherId == publisherId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(userAnswer),const DeepCollectionEquality().hash(stats),id,const DeepCollectionEquality().hash(questions),title,description,endedAt,publisherId,createdAt,updatedAt,deletedAt);
int get hashCode => Object.hash(runtimeType,userAnswer,const DeepCollectionEquality().hash(stats),id,const DeepCollectionEquality().hash(questions),title,description,endedAt,publisherId,createdAt,updatedAt,deletedAt);
@override
String toString() {
@@ -48,11 +48,11 @@ abstract mixin class $SnPollWithStatsCopyWith<$Res> {
factory $SnPollWithStatsCopyWith(SnPollWithStats value, $Res Function(SnPollWithStats) _then) = _$SnPollWithStatsCopyWithImpl;
@useResult
$Res call({
Map<String, dynamic>? userAnswer, Map<String, dynamic> stats, String id, List<SnPollQuestion> questions, String? title, String? description, DateTime? endedAt, String publisherId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
SnPollAnswer? userAnswer, Map<String, dynamic> stats, String id, List<SnPollQuestion> questions, String? title, String? description, DateTime? endedAt, String publisherId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
});
$SnPollAnswerCopyWith<$Res>? get userAnswer;
}
/// @nodoc
@@ -68,7 +68,7 @@ class _$SnPollWithStatsCopyWithImpl<$Res>
@pragma('vm:prefer-inline') @override $Res call({Object? userAnswer = freezed,Object? stats = null,Object? id = null,Object? questions = null,Object? title = freezed,Object? description = freezed,Object? endedAt = freezed,Object? publisherId = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
return _then(_self.copyWith(
userAnswer: freezed == userAnswer ? _self.userAnswer : userAnswer // ignore: cast_nullable_to_non_nullable
as Map<String, dynamic>?,stats: null == stats ? _self.stats : stats // ignore: cast_nullable_to_non_nullable
as SnPollAnswer?,stats: null == stats ? _self.stats : stats // ignore: cast_nullable_to_non_nullable
as Map<String, dynamic>,id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,questions: null == questions ? _self.questions : questions // ignore: cast_nullable_to_non_nullable
as List<SnPollQuestion>,title: freezed == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
@@ -81,7 +81,19 @@ as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ign
as DateTime?,
));
}
/// Create a copy of SnPollWithStats
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$SnPollAnswerCopyWith<$Res>? get userAnswer {
if (_self.userAnswer == null) {
return null;
}
return $SnPollAnswerCopyWith<$Res>(_self.userAnswer!, (value) {
return _then(_self.copyWith(userAnswer: value));
});
}
}
@@ -160,7 +172,7 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( Map<String, dynamic>? userAnswer, Map<String, dynamic> stats, String id, List<SnPollQuestion> questions, String? title, String? description, DateTime? endedAt, String publisherId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,{required TResult orElse(),}) {final _that = this;
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( SnPollAnswer? userAnswer, Map<String, dynamic> stats, String id, List<SnPollQuestion> questions, String? title, String? description, DateTime? endedAt, String publisherId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _SnPollWithStats() when $default != null:
return $default(_that.userAnswer,_that.stats,_that.id,_that.questions,_that.title,_that.description,_that.endedAt,_that.publisherId,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
@@ -181,7 +193,7 @@ return $default(_that.userAnswer,_that.stats,_that.id,_that.questions,_that.titl
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( Map<String, dynamic>? userAnswer, Map<String, dynamic> stats, String id, List<SnPollQuestion> questions, String? title, String? description, DateTime? endedAt, String publisherId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt) $default,) {final _that = this;
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( SnPollAnswer? userAnswer, Map<String, dynamic> stats, String id, List<SnPollQuestion> questions, String? title, String? description, DateTime? endedAt, String publisherId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt) $default,) {final _that = this;
switch (_that) {
case _SnPollWithStats():
return $default(_that.userAnswer,_that.stats,_that.id,_that.questions,_that.title,_that.description,_that.endedAt,_that.publisherId,_that.createdAt,_that.updatedAt,_that.deletedAt);}
@@ -198,7 +210,7 @@ return $default(_that.userAnswer,_that.stats,_that.id,_that.questions,_that.titl
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( Map<String, dynamic>? userAnswer, Map<String, dynamic> stats, String id, List<SnPollQuestion> questions, String? title, String? description, DateTime? endedAt, String publisherId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,) {final _that = this;
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( SnPollAnswer? userAnswer, Map<String, dynamic> stats, String id, List<SnPollQuestion> questions, String? title, String? description, DateTime? endedAt, String publisherId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,) {final _that = this;
switch (_that) {
case _SnPollWithStats() when $default != null:
return $default(_that.userAnswer,_that.stats,_that.id,_that.questions,_that.title,_that.description,_that.endedAt,_that.publisherId,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
@@ -213,18 +225,10 @@ return $default(_that.userAnswer,_that.stats,_that.id,_that.questions,_that.titl
@JsonSerializable()
class _SnPollWithStats implements SnPollWithStats {
const _SnPollWithStats({required final Map<String, dynamic>? userAnswer, final Map<String, dynamic> stats = const {}, required this.id, required final List<SnPollQuestion> questions, this.title, this.description, this.endedAt, required this.publisherId, required this.createdAt, required this.updatedAt, this.deletedAt}): _userAnswer = userAnswer,_stats = stats,_questions = questions;
const _SnPollWithStats({required this.userAnswer, final Map<String, dynamic> stats = const {}, required this.id, required final List<SnPollQuestion> questions, this.title, this.description, this.endedAt, required this.publisherId, required this.createdAt, required this.updatedAt, this.deletedAt}): _stats = stats,_questions = questions;
factory _SnPollWithStats.fromJson(Map<String, dynamic> json) => _$SnPollWithStatsFromJson(json);
final Map<String, dynamic>? _userAnswer;
@override Map<String, dynamic>? get userAnswer {
final value = _userAnswer;
if (value == null) return null;
if (_userAnswer is EqualUnmodifiableMapView) return _userAnswer;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(value);
}
@override final SnPollAnswer? userAnswer;
final Map<String, dynamic> _stats;
@override@JsonKey() Map<String, dynamic> get stats {
if (_stats is EqualUnmodifiableMapView) return _stats;
@@ -261,12 +265,12 @@ Map<String, dynamic> toJson() {
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnPollWithStats&&const DeepCollectionEquality().equals(other._userAnswer, _userAnswer)&&const DeepCollectionEquality().equals(other._stats, _stats)&&(identical(other.id, id) || other.id == id)&&const DeepCollectionEquality().equals(other._questions, _questions)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.endedAt, endedAt) || other.endedAt == endedAt)&&(identical(other.publisherId, publisherId) || other.publisherId == publisherId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnPollWithStats&&(identical(other.userAnswer, userAnswer) || other.userAnswer == userAnswer)&&const DeepCollectionEquality().equals(other._stats, _stats)&&(identical(other.id, id) || other.id == id)&&const DeepCollectionEquality().equals(other._questions, _questions)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.endedAt, endedAt) || other.endedAt == endedAt)&&(identical(other.publisherId, publisherId) || other.publisherId == publisherId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_userAnswer),const DeepCollectionEquality().hash(_stats),id,const DeepCollectionEquality().hash(_questions),title,description,endedAt,publisherId,createdAt,updatedAt,deletedAt);
int get hashCode => Object.hash(runtimeType,userAnswer,const DeepCollectionEquality().hash(_stats),id,const DeepCollectionEquality().hash(_questions),title,description,endedAt,publisherId,createdAt,updatedAt,deletedAt);
@override
String toString() {
@@ -281,11 +285,11 @@ abstract mixin class _$SnPollWithStatsCopyWith<$Res> implements $SnPollWithStats
factory _$SnPollWithStatsCopyWith(_SnPollWithStats value, $Res Function(_SnPollWithStats) _then) = __$SnPollWithStatsCopyWithImpl;
@override @useResult
$Res call({
Map<String, dynamic>? userAnswer, Map<String, dynamic> stats, String id, List<SnPollQuestion> questions, String? title, String? description, DateTime? endedAt, String publisherId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
SnPollAnswer? userAnswer, Map<String, dynamic> stats, String id, List<SnPollQuestion> questions, String? title, String? description, DateTime? endedAt, String publisherId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
});
@override $SnPollAnswerCopyWith<$Res>? get userAnswer;
}
/// @nodoc
@@ -300,8 +304,8 @@ class __$SnPollWithStatsCopyWithImpl<$Res>
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? userAnswer = freezed,Object? stats = null,Object? id = null,Object? questions = null,Object? title = freezed,Object? description = freezed,Object? endedAt = freezed,Object? publisherId = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
return _then(_SnPollWithStats(
userAnswer: freezed == userAnswer ? _self._userAnswer : userAnswer // ignore: cast_nullable_to_non_nullable
as Map<String, dynamic>?,stats: null == stats ? _self._stats : stats // ignore: cast_nullable_to_non_nullable
userAnswer: freezed == userAnswer ? _self.userAnswer : userAnswer // ignore: cast_nullable_to_non_nullable
as SnPollAnswer?,stats: null == stats ? _self._stats : stats // ignore: cast_nullable_to_non_nullable
as Map<String, dynamic>,id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,questions: null == questions ? _self._questions : questions // ignore: cast_nullable_to_non_nullable
as List<SnPollQuestion>,title: freezed == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
@@ -315,7 +319,19 @@ as DateTime?,
));
}
/// Create a copy of SnPollWithStats
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$SnPollAnswerCopyWith<$Res>? get userAnswer {
if (_self.userAnswer == null) {
return null;
}
return $SnPollAnswerCopyWith<$Res>(_self.userAnswer!, (value) {
return _then(_self.copyWith(userAnswer: value));
});
}
}

View File

@@ -8,7 +8,12 @@ part of 'poll.dart';
_SnPollWithStats _$SnPollWithStatsFromJson(Map<String, dynamic> json) =>
_SnPollWithStats(
userAnswer: json['user_answer'] as Map<String, dynamic>?,
userAnswer:
json['user_answer'] == null
? null
: SnPollAnswer.fromJson(
json['user_answer'] as Map<String, dynamic>,
),
stats: json['stats'] as Map<String, dynamic>? ?? const {},
id: json['id'] as String,
questions:
@@ -32,7 +37,7 @@ _SnPollWithStats _$SnPollWithStatsFromJson(Map<String, dynamic> json) =>
Map<String, dynamic> _$SnPollWithStatsToJson(_SnPollWithStats instance) =>
<String, dynamic>{
'user_answer': instance.userAnswer,
'user_answer': instance.userAnswer?.toJson(),
'stats': instance.stats,
'id': instance.id,
'questions': instance.questions.map((e) => e.toJson()).toList(),

View File

@@ -1,12 +1,13 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:dio/dio.dart' hide Response;
import 'package:flutter/foundation.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/account.dart';
import 'package:island/models/activity.dart';
import 'package:island/pods/network.dart';
import 'package:island/talker.dart';
import 'package:island/widgets/account/status.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:shelf/shelf.dart';
import 'package:shelf/shelf_io.dart' as shelf_io;
import 'package:shelf_web_socket/shelf_web_socket.dart';
@@ -15,6 +16,8 @@ import 'package:web_socket_channel/web_socket_channel.dart';
// Conditional imports for IPC server - use web stubs on web platform
import 'ipc_server.dart' if (dart.library.html) 'ipc_server.web.dart';
part 'activity_rpc.g.dart';
const String kRpcLogPrefix = 'arRPC.websocket';
const String kRpcIpcLogPrefix = 'arRPC.ipc';
@@ -116,12 +119,19 @@ class ActivityRpcServer {
_handleIpcPacket(socket, packet);
};
// Set up IPC close handler
if (!kIsWeb) {
(_ipcServer as dynamic).onSocketClose = (socket) {
handlers['close']?.call(socket);
};
}
await _ipcServer!.start();
} catch (e) {
talker.log('[$kRpcLogPrefix] IPC server error: $e');
}
} else {
talker.log('IPC server disabled on macOS or web in production mode');
talker.log('IPC server disabled on macOS or web');
}
}
@@ -294,23 +304,42 @@ class _WsSocketWrapper {
class ServerState {
final String status;
final List<String> activities;
final String? currentActivityManualId;
final Map<String, dynamic>? currentActivityData;
ServerState({required this.status, this.activities = const []});
ServerState({
required this.status,
this.activities = const [],
this.currentActivityManualId,
this.currentActivityData,
});
ServerState copyWith({String? status, List<String>? activities}) {
ServerState copyWith({
String? status,
List<String>? activities,
String? currentActivityManualId,
Map<String, dynamic>? currentActivityData,
}) {
return ServerState(
status: status ?? this.status,
activities: activities ?? this.activities,
currentActivityManualId:
currentActivityManualId ?? this.currentActivityManualId,
currentActivityData: currentActivityData ?? this.currentActivityData,
);
}
}
class ServerStateNotifier extends StateNotifier<ServerState> {
final ActivityRpcServer server;
final Dio apiClient;
Timer? _renewalTimer;
ServerStateNotifier(this.server)
ServerStateNotifier(this.apiClient, this.server)
: super(ServerState(status: 'Server not started'));
String? get currentActivityManualId => state.currentActivityManualId;
Future<void> start() async {
if (!kIsWeb && !Platform.isAndroid && !Platform.isIOS) {
try {
@@ -333,122 +362,185 @@ class ServerStateNotifier extends StateNotifier<ServerState> {
void addActivity(String activity) {
state = state.copyWith(activities: [...state.activities, activity]);
}
void setCurrentActivity(String? id, Map<String, dynamic>? data) {
state = state.copyWith(
currentActivityManualId: id,
currentActivityData: data,
);
if (id != null && data != null) {
_startRenewal();
} else {
_stopRenewal();
}
}
void _startRenewal() {
_renewalTimer?.cancel();
const int renewalIntervalSeconds = kPresenceActivityLease * 60 - 30;
_renewalTimer = Timer.periodic(Duration(seconds: renewalIntervalSeconds), (
timer,
) {
_renewActivity();
});
}
void _stopRenewal() {
_renewalTimer?.cancel();
_renewalTimer = null;
}
Future<void> _renewActivity() async {
if (state.currentActivityData != null) {
try {
await apiClient.post(
'/pass/activities',
data: state.currentActivityData,
);
talker.log('Activity lease renewed');
} catch (e) {
talker.log('Failed to renew activity lease: $e');
}
}
}
}
const kPresenceActivityLease = 5;
// Providers
final rpcServerStateProvider =
StateNotifierProvider<ServerStateNotifier, ServerState>((ref) {
final server = ActivityRpcServer({});
final notifier = ServerStateNotifier(server);
server.updateHandlers({
'connection': (socket) {
final clientId =
socket is _WsSocketWrapper
? socket.clientId
: (socket as IpcSocketWrapper).clientId;
notifier.updateStatus('Client connected (ID: $clientId)');
socket.send({
'cmd': 'DISPATCH',
'data': {
'v': 1,
'config': {
'cdn_host': 'fake.cdn',
'api_endpoint': '//fake.api',
'environment': 'dev',
},
'user': {
'id': 'fake_user_id',
'username': 'FakeUser',
'discriminator': '0001',
'avatar': null,
'bot': false,
},
},
'evt': 'READY',
'nonce': '12345',
});
},
'message': (socket, dynamic data) async {
if (data['cmd'] == 'SET_ACTIVITY') {
notifier.addActivity(
'Activity: ${data['args']['activity']['details'] ?? ''}',
);
final label = data['args']['activity']['details'] ?? '';
final appId = socket.clientId;
final meta = data['args']['activity'];
try {
await setRemoteActivityStatus(ref, label, appId, meta);
final now = DateTime.now();
final status = SnAccountStatus(
id: 'local_$appId',
attitude: 0,
isOnline: true,
isInvisible: false,
isNotDisturb: false,
isCustomized: true,
label: label,
meta: meta,
clearedAt: null,
accountId: 'me',
createdAt: now,
updatedAt: now,
deletedAt: null,
);
ref.read(currentAccountStatusProvider.notifier).setStatus(status);
} catch (e) {
talker.log('Failed to set remote activity status: $e');
}
socket.send({
'cmd': 'SET_ACTIVITY',
'data': data['args']['activity'],
'evt': null,
'nonce': data['nonce'],
});
}
},
'close': (socket) async {
notifier.updateStatus('Client disconnected');
final appId = socket.clientId;
try {
await unsetRemoteActivityStatus(ref, appId);
ref.read(currentAccountStatusProvider.notifier).clearStatus();
} catch (e) {
talker.log('Failed to unset remote activity status: $e');
}
final rpcServerStateProvider = StateNotifierProvider<
ServerStateNotifier,
ServerState
>((ref) {
final apiClient = ref.watch(apiClientProvider);
final server = ActivityRpcServer({});
final notifier = ServerStateNotifier(apiClient, server);
server.updateHandlers({
'connection': (socket) {
final clientId =
socket is _WsSocketWrapper
? socket.clientId
: (socket as IpcSocketWrapper).clientId;
notifier.updateStatus('Client connected (ID: $clientId)');
socket.send({
'cmd': 'DISPATCH',
'data': {
'v': 1,
'config': {
'cdn_host': 'fake.cdn',
'api_endpoint': '//fake.api',
'environment': 'dev',
},
'user': {
'id': 'fake_user_id',
'username': 'FakeUser',
'discriminator': '0001',
'avatar': null,
'bot': false,
},
},
'evt': 'READY',
'nonce': '12345',
});
return notifier;
});
},
'message': (socket, dynamic data) async {
if (data['cmd'] == 'SET_ACTIVITY') {
final activity = data['args']['activity'];
final appId = 'rpc:${socket.clientId}';
final currentId = notifier.currentActivityManualId;
if (currentId != null && currentId != appId) {
talker.info(
'Skipped the new SET_ACTIVITY command due to there is one existing...',
);
return;
}
notifier.addActivity('Activity: ${activity['details'] ?? 'Untitled'}');
// https://discord.com/developers/docs/topics/rpc#setactivity-set-activity-argument-structure
final type = switch (activity['type']) {
0 => 1, // Discord Playing -> Playing
2 => 2, // Discord Music -> Listening
3 => 2, // Discord Watching -> Listening
_ => 1, // Discord Competing (or null) -> Playing
};
final title = activity['name'] ?? activity['assets']?['small_text'];
final subtitle =
activity['details'] ?? activity['assets']?['large_text'];
var imageSmall = activity['assets']?['small_image'];
var imageLarge = activity['assets']?['large_image'];
if (imageSmall != null && !imageSmall!.contains(':')) {
imageSmall = 'discord:$imageSmall';
}
if (imageLarge != null && !imageLarge!.contains(':')) {
imageLarge = 'discord:$imageLarge';
}
try {
final apiClient = ref.watch(apiClientProvider);
final activityData = {
'type': type,
'manual_id': appId,
'title': title,
'subtitle': subtitle,
'caption': activity['state'],
'title_url': activity['assets']?['small_text_url'],
'subtitle_url': activity['assets']?['large_text_url'],
'small_image': imageSmall,
'large_image': imageLarge,
'meta': activity,
'lease_minutes': kPresenceActivityLease,
};
await apiClient.post('/pass/activities', data: activityData);
notifier.setCurrentActivity(appId, activityData);
} catch (e) {
talker.log('Failed to set remote activity status: $e');
}
socket.send({
'cmd': 'SET_ACTIVITY',
'data': data['args']['activity'],
'evt': null,
'nonce': data['nonce'],
});
}
},
'close': (socket) async {
notifier.updateStatus('Client disconnected');
final currentId = notifier.currentActivityManualId;
try {
final apiClient = ref.watch(apiClientProvider);
await apiClient.delete(
'/pass/activities',
queryParameters: {'manualId': currentId},
);
notifier.setCurrentActivity(null, null);
} catch (e) {
talker.log('Failed to unset remote activity status: $e');
}
},
});
return notifier;
});
final rpcServerProvider = Provider<ActivityRpcServer>((ref) {
final notifier = ref.watch(rpcServerStateProvider.notifier);
return notifier.server;
});
Future<void> setRemoteActivityStatus(
@riverpod
Future<List<SnPresenceActivity>> presenceActivities(
Ref ref,
String label,
String appId,
Map<String, dynamic> meta,
String uname,
) async {
final apiClient = ref.read(apiClientProvider);
await apiClient.post(
'/pass/accounts/me/statuses',
data: {
'is_invisible': false,
'is_not_disturb': false,
'is_automated': true,
'label': label,
'app_identifier': appId,
'meta': meta,
},
ref.keepAlive();
final timer = Timer.periodic(
const Duration(minutes: 1),
(_) => ref.invalidateSelf(),
);
}
ref.onDispose(() => timer.cancel());
Future<void> unsetRemoteActivityStatus(Ref ref, String appId) async {
final apiClient = ref.read(apiClientProvider);
await apiClient.delete(
'/pass/accounts/me/statuses',
queryParameters: {'app': appId},
);
final apiClient = ref.watch(apiClientProvider);
final response = await apiClient.get('/pass/activities/$uname');
final data = response.data as List<dynamic>;
return data.map((json) => SnPresenceActivity.fromJson(json)).toList();
}

Some files were not shown because too many files have changed in this diff Show More