Compare commits

..

285 Commits

Author SHA1 Message Date
33ec0b1d9a 🚀 Launch 3.5.0+150 2025-12-03 01:41:30 +08:00
f698385494 🍱 Sync zh-CN translation 2025-12-03 01:36:01 +08:00
6ecdf89d20 Unactivated account hint, close #199 2025-12-03 01:34:02 +08:00
683f686540 🐛 Fix opening large files, close #201 2025-12-03 01:01:32 +08:00
6a115ab1cc Allow user to enter affiliation spell when creating account 2025-12-03 00:53:21 +08:00
d05283d3b1 💄 Optimize some styling 2025-12-03 00:42:34 +08:00
b9653e7264 👽 Update to match server side updates 2025-12-03 00:39:12 +08:00
87d1c8b320 🐛 Fix site manager will treat all files like text 2025-12-01 23:29:57 +08:00
22eb54b61f 🐛 Fix file management do not support windows format 2025-12-01 22:36:26 +08:00
a90ad2debc 💄 Optimize style of online counter again 2025-12-01 00:04:51 +08:00
2f00bf660d 💄 Optimize style of online count 2025-11-30 22:47:53 +08:00
322a93324c 🐛 Fix room content didn't continely load 2025-11-30 22:00:23 +08:00
c3a3be0807 👽 Update message db to support server changes 2025-11-30 21:57:35 +08:00
27c7c8f039 👽 Update third party login 2025-11-30 21:38:36 +08:00
a7960da362 💫 Adjust snackbar animation 2025-11-30 17:29:20 +08:00
64ab30b0a9 🐛 Fix websocket didn't connect after logged out 2025-11-30 17:25:16 +08:00
935e6d5833 💄 Logout now will clear database to avoid wrong state 2025-11-30 17:24:27 +08:00
938b128b1e 💄 Optimize create account flow and add some hints 2025-11-30 17:05:15 +08:00
c9764daa20 🌐 Localized able friends online 2025-11-30 16:06:06 +08:00
7bc44e8f06 🐛 Fix some styling issues 2025-11-30 15:42:51 +08:00
4a7ff96a8b Support new local connect auth 2025-11-30 14:51:12 +08:00
e759d5f46c ⬆️ Upgrade dependecies 2025-11-30 14:50:58 +08:00
f5ca6a37bf 🍱 Sync the translation with crowdin 2025-11-26 22:29:10 +08:00
5fc8859f3b 🚀 Launch 3.4.0+149 2025-11-25 00:06:45 +08:00
e30e7adbe2 🐛 Trying to fix NSE attachment 2025-11-25 00:05:07 +08:00
68be4db160 Able to upload from share 2025-11-25 00:00:04 +08:00
aa91e376ca 🐛 Fix bugs in the share sheet 2025-11-24 23:49:19 +08:00
caffb85588 Explore screen has a drop to share 2025-11-24 23:36:53 +08:00
521b192205 🐛 Fix edit post got truncated post lead to wrong state of editing, close #195 2025-11-24 23:28:43 +08:00
77ac0428ea 💄 The universal escape now can close the fade made dialog 2025-11-24 23:21:52 +08:00
88c8227c66 ♻️ Dangerous confirm dialog variant 2025-11-24 23:13:35 +08:00
b20d8350a8 💄 Alert max width 2025-11-24 23:01:29 +08:00
98b27bed0e 🐛 Fix list overlap with some UI element on the screen 2025-11-24 22:56:08 +08:00
3a7d8b1a0d 🐛 Fix file dashboard icon sometimes overflow 2025-11-24 22:51:22 +08:00
b4801d6af6 🐛 Fix site unable to delete, close #196 2025-11-24 22:50:24 +08:00
aab5b957af 🐛 Fix mobile site didn't show domain 2025-11-24 22:43:25 +08:00
43d706a184 💄 Adjust the comment row styling 2025-11-24 22:34:38 +08:00
98df275f88 🐛 Fix compose article unable to scroll close #194 2025-11-24 22:30:56 +08:00
5663df6ef1 🚀 Launch 3.3.0+148 2025-11-23 13:07:15 +08:00
e996a0c95f 👽 Update the verification mark 2025-11-23 13:04:07 +08:00
a090e93f57 🗑️ Remove the chat role display in message 2025-11-23 12:56:02 +08:00
c69034c071 Better notification list 2025-11-23 12:55:49 +08:00
369ea6cf5b 🐛 Fix unmounted setState 2025-11-23 12:46:49 +08:00
2e371b5296 💄 More accurate notification unread count 2025-11-23 12:45:19 +08:00
2e9d61bcfa Chat unread indicator across all chat 2025-11-23 12:40:52 +08:00
9c2b5b0dfa 🐛 Fix further remote messages will not be loaded 2025-11-23 12:25:46 +08:00
3b40f515b3 🐛 Fix file list go back to wrong page 2025-11-23 12:18:57 +08:00
5ee61dbef2 Pagination in chat message sync 2025-11-23 12:18:46 +08:00
b151ef6686 🐛 Try to fix message loading 2025-11-23 11:54:51 +08:00
ff934d0f08 💄 Update the captcha style 2025-11-23 11:39:52 +08:00
abe5ded896 🚀 Launch 3.3.0+147 2025-11-23 02:01:00 +08:00
f1d72a5215 🐛 Fix android build no check 2025-11-23 02:00:13 +08:00
864cbe73b7 🐛 Try to fix share intent fails 2025-11-23 01:54:01 +08:00
108a6da074 🌐 Localized files 2025-11-23 01:43:54 +08:00
f9a09599c9 ⬆️ Upgrade dependecies 2025-11-23 01:23:43 +08:00
9067dadd3e 🐛 Fix reaction sheet popover goes out of the screen 2025-11-23 01:21:54 +08:00
09f8df1e78 💄 Optimize design of the call content 2025-11-23 01:12:04 +08:00
2c5f246c55 💄 Redesign the video of the call 2025-11-23 00:53:00 +08:00
a66c6ea654 💫 Animated call overlay 2025-11-23 00:35:42 +08:00
3ad4bb4518 ♻️ Rebuild the call 2025-11-23 00:26:40 +08:00
53f0dcb825 Optimize performance for message item 2025-11-22 20:46:41 +08:00
557f5a2389 👔 Hide the friends overview on mobile 2025-11-22 20:26:41 +08:00
78f14f890f 💄 Optimize embedded section of chat input 2025-11-22 20:11:01 +08:00
77b2effb34 💫 Update the animation of alert's dialog 2025-11-22 19:18:42 +08:00
f02b4abf65 💄 Optimize audio player height 2025-11-22 18:58:25 +08:00
3f37c4f761 ♻️ Remove platform alert and use flutter dialog instead 2025-11-22 18:56:18 +08:00
5deb910fa4 ♻️ Refactored all ScaffoldMessager to use unifined snackbar API 2025-11-22 18:42:12 +08:00
f50a19f573 🐛 Dozens of bug fixes 2025-11-22 18:36:10 +08:00
98c8a356e8 ♻️ Rebuild the activity heatmap to close #189 2025-11-22 16:19:23 +08:00
d0c16ea08f Site quick open page 2025-11-22 16:02:30 +08:00
f2c1b2a531 File management actions 2025-11-22 16:01:27 +08:00
3061f0c5a9 Site file edit 2025-11-22 15:43:35 +08:00
98f7f33c65 Site file management able to navigate folders 2025-11-22 15:24:16 +08:00
d9af5d32fd Site file management able to upload site 2025-11-22 14:59:44 +08:00
f2031697ec 🐛 Fix the site refresh didn't wrok 2025-11-22 14:44:41 +08:00
9b85b7573c 💄 Optimize publication site screen 2025-11-22 14:39:03 +08:00
4fb739b33b 💄 Desktop optimization for the site dashboard 2025-11-21 00:40:45 +08:00
c03ba3bc3a ♻️ Breakdown of the site detail page 2025-11-21 00:34:39 +08:00
fc65440420 🐛 Fix file upload in site 2025-11-21 00:24:35 +08:00
7b85533184 Pages management in site detail 2025-11-21 00:05:36 +08:00
77d9eb60c6 Page details 2025-11-20 22:40:20 +08:00
4d8953cd22 Site mode 2025-11-20 21:58:59 +08:00
fafa460fe8 Site basis 2025-11-20 21:29:08 +08:00
faf3a677d4 Rewind AI slop 2025-11-20 00:21:21 +08:00
0f644a0234 🐛 Fix chat list tiles renders wrong account 2025-11-20 00:11:13 +08:00
18d16fdd57 🐛 Fix bugs in message db 2025-11-20 00:01:36 +08:00
18e890d63c 💄 Optimize cloud file sizing 2025-11-19 22:56:38 +08:00
9c5e50c16a 🐛 Fix share post via screenshot entirely broke 2025-11-19 22:49:47 +08:00
96a2c8182e ⬆️ Upgrade dependecies 2025-11-19 21:41:57 +08:00
56b27c3e82 Use cached chat rooms for first time render chat 2025-11-19 00:50:22 +08:00
ad4bf94195 ♻️ Refactored chat db 2025-11-19 00:29:22 +08:00
b77a832d8a 🐛 Fix autohide of upload 2025-11-18 22:52:45 +08:00
5e61805db7 💄 Upload overlay auto hide 2025-11-18 22:38:27 +08:00
35b96b0bd2 💄 Optimize downloading and files 2025-11-18 22:21:23 +08:00
c8ad791ff3 💄 Optimize cloud files 2025-11-18 22:06:38 +08:00
1e908502dc Able to open file detail view from lightbox 2025-11-18 21:59:35 +08:00
715ce1a368 File reference list 2025-11-18 21:45:13 +08:00
548c9963ee File list selection select all 2025-11-18 21:32:25 +08:00
db5199438a Selection and batch operations in file list 2025-11-18 21:17:09 +08:00
4409a6fb1e More global filters om file list 2025-11-18 20:33:49 +08:00
26a24b0e41 Pdf viewer zoom 2025-11-18 13:06:08 +08:00
9b948d259b Cached pdf viewer 2025-11-18 13:00:57 +08:00
1f713b5b2b 💄 Make captcha undismissable 2025-11-18 12:57:21 +08:00
f92cfafda4 Downloading file tasks 2025-11-18 01:45:15 +08:00
fa208b44d7 🐛 Fix publisher account name shows wrong 2025-11-18 01:31:15 +08:00
94adecafbb 💄 Optimize file detail view styling 2025-11-18 00:32:26 +08:00
0303ef4a93 💄 Optimize file list again 2025-11-18 00:20:10 +08:00
c2b18ce10b 🐛 Fix file list 2025-11-18 00:04:07 +08:00
0767bb53ce Put clean up recycled files back 2025-11-17 23:53:11 +08:00
b233f9a410 💄 File list loading indicator 2025-11-17 23:10:13 +08:00
256024fb46 💄 Adjust upload overlay auto show and hide logic 2025-11-17 22:57:53 +08:00
4a80aaf24d Unindexed files filter 2025-11-17 22:57:42 +08:00
aafd160c44 🐛 Fix waterfall styling issue 2025-11-17 22:00:51 +08:00
4a800725e3 Zoom image via mosue scroll 2025-11-17 22:00:35 +08:00
24791b3293 🎉 Launch 3.3.0+146 2025-11-17 01:33:34 +08:00
3ac263d483 🐛 Fix build on web 2025-11-17 01:26:34 +08:00
2445d8adf8 💄 Optimzations 2025-11-17 01:23:27 +08:00
d4f95bbbf4 Claim fund 2025-11-17 01:20:49 +08:00
943e4b7b5c 🐛 Fix edit post didn't inherent poll and fund 2025-11-16 23:58:36 +08:00
7edc02a1d3 ♻️ No longer two submit post function 2025-11-16 23:54:50 +08:00
3f9881e943 Fund creation and attach found to message 2025-11-16 23:43:28 +08:00
50c25e919c 🐛 Bug fixes in cloud file collection 2025-11-16 23:00:14 +08:00
99fb08dd55 Send message with poll 2025-11-16 22:43:18 +08:00
e43bc6b8a8 💄 Optimize poll 2025-11-16 22:29:24 +08:00
c247cdf81c ♻️ Refactored poll editor 2025-11-16 22:15:10 +08:00
3ffa730505 💄 Optimize chat input expand style 2025-11-16 21:49:41 +08:00
1cc34d3073 Chat input expansiable section basis 2025-11-16 21:42:10 +08:00
96a919cc4e 💫 Animated height padding in inputs 2025-11-16 20:20:24 +08:00
e7e3bfcadf 🐛 Fix oidc callback 2025-11-16 18:38:36 +08:00
a8617a5040 💄 Collspible link embeds 2025-11-16 18:23:24 +08:00
d94f8d004f 💄 Shows friends overview on mobile as well 2025-11-16 18:10:55 +08:00
d93b066979 📝 Rename the currencies 2025-11-16 17:59:10 +08:00
320664a547 💄 Friends overview optimization 2025-11-16 17:50:36 +08:00
98f4698d5b 🐛 Fixes of serval bugs 2025-11-16 17:40:37 +08:00
82397dd087 Friends overview basis 2025-11-16 17:26:31 +08:00
4ec10ceb47 ♻️ Rework of the oidc login flow (wip) 2025-11-16 17:07:34 +08:00
4b03b45a0d 💄 Create account, login modal varaint and more auth check to prevent user from entering certain widget in unauthorized state 2025-11-16 14:53:40 +08:00
7a72d32649 🐛 Fix publisher page account avatar no gesture detector, close #188 2025-11-16 12:06:48 +08:00
5152dd13ea 💄 Continue optimize the post reaction sheet 2025-11-16 12:04:13 +08:00
fd377aa7af 💄 Change the post reaction sheet sticker picker align and close 2025-11-16 11:56:49 +08:00
67044148f1 💄 Fix chat input overlaps with message sometimes 2025-11-16 11:48:51 +08:00
92bc43e4df 🐛 Fix chat creation cause stack has no page 2025-11-16 11:48:35 +08:00
a1a7b34c86 ♻️ Use bottom modal sheet for chat creation form 2025-11-16 11:46:31 +08:00
40c0e052cf 💄 Optimize thought input space to avoid input cover message 2025-11-16 11:22:47 +08:00
9a75228e38 Multi model support in thought 2025-11-16 02:45:02 +08:00
a9fd75cc45 Thinking billing check 2025-11-16 01:18:20 +08:00
a713b30d93 🐛 Fix bugs 2025-11-16 00:52:17 +08:00
e516f0a862 🐛 Bug fixes 2025-11-16 00:34:10 +08:00
429b966c4b 🐛 Fix wrong tool call progress status 2025-11-15 23:22:33 +08:00
f14da0d3a2 💫 Add tool call calling hint animation 2025-11-15 23:22:07 +08:00
d201182bd2 ♻️ Turn thought into a Tab 2025-11-15 23:08:31 +08:00
6f6422c15e 💄 Optimize thought function call style 2025-11-15 23:02:25 +08:00
9f6ae639ee 🐛 Fix publisher member management missing service id to use sphere API 2025-11-15 22:40:27 +08:00
35f4d7d885 ♻️ Updated the thought rendering 2025-11-15 22:16:29 +08:00
a9c8f49797 💄 Optimize thoughts 2025-11-15 21:15:41 +08:00
5e9341a19c ♻️ Refactored the thinking 2025-11-15 17:10:36 +08:00
645a6dca93 ♻️ Refactor the thought insight to support new API 2025-11-15 16:59:22 +08:00
ea8e7ead2d 💄 Add go to previous path action in path nav in file list 2025-11-15 16:25:24 +08:00
5f2f083d72 ♻️ Fixes and optimizations in file list 2025-11-15 16:20:05 +08:00
5cf40e27de 💄 Optimized the waterfall file list style 2025-11-15 16:05:42 +08:00
1ab7295918 💄 Optimize waterfall file list design 2025-11-15 15:54:13 +08:00
07f191171c Waterfall layout in files (w.i.p) 2025-11-15 15:42:09 +08:00
4a5dac248e ♻️ Dedicated file viewer widget 2025-11-15 15:08:49 +08:00
3b983a6444 ♻️ Refactored the file detail 2025-11-15 15:04:01 +08:00
4607b77355 ♻️ Better file icons 2025-11-15 13:36:00 +08:00
7957e4894a File list drag and drop 2025-11-15 13:22:05 +08:00
f94f80c375 👽 Update the indexed file api calls 2025-11-15 03:06:41 +08:00
74fa2215a6 Unindexed files 2025-11-15 02:59:20 +08:00
0d11435feb ⬆️ Upgrade dependecies 2025-11-15 01:44:10 +08:00
e22598b0a6 🔨 Preview of the watchOS app main screen 2025-11-14 21:41:04 +08:00
84cfe643f5 👽 Adopt the new folder system (w.i.p) 2025-11-14 01:04:15 +08:00
05ac04e9a2 Enchaned file detail screen 2025-11-13 01:44:55 +08:00
66f283d6e8 Renders file folders in drive 2025-11-13 01:31:58 +08:00
c779c7523c FIle index 2025-11-12 22:09:22 +08:00
ac7cb29afe ♻️ Improved the files screen 2025-11-11 00:49:07 +08:00
935aa77223 ♻️ Turn the file screen into a tab 2025-11-11 00:39:49 +08:00
24e5b3b824 Account tab icon shows pfp 2025-11-11 00:39:35 +08:00
0391893b32 🐛 Bug fixes in upload task tracking 2025-11-11 00:00:09 +08:00
b8d24876c8 🚚 Rename upload task to drive task 2025-11-10 01:56:22 +08:00
0493661f9a 🐛 Fixes and optimizations 2025-11-10 01:49:38 +08:00
b40afde00f 💫 Animated the upload overlay 2025-11-10 01:40:28 +08:00
78a4022531 💄 Optimize upload overlay styling 2025-11-10 01:27:06 +08:00
8a291c80b7 Upload tasks overlay 2025-11-10 01:11:43 +08:00
1395d65b76 ♻️ Refactored publisher creation into sheet 2025-11-09 21:18:34 +08:00
eb4942e0ed 💄 Optimize account card style when no background 2025-11-09 14:28:53 +08:00
f254cfa81e ♻️ Refactored the captcha 2025-11-09 14:21:33 +08:00
4927795260 ♻️ Refactored the upload files according to new backend tasks 2025-11-09 14:09:26 +08:00
e4019dadc8 💄 Optimize file upload prograss indicates 2025-11-09 01:59:24 +08:00
5e7d77e1a1 🐛 Fix share sheet error 2025-11-08 20:05:18 +08:00
bfcbed035c ♻️ Refactored file uploading 2025-11-08 20:04:54 +08:00
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
b3ef7d6ad0 🐛 Fix fab menu wrong type 2025-10-31 19:09:24 +08:00
d28c11940d 🐛 Bug fixes 2025-10-31 19:02:53 +08:00
504322c2dd 🍱 Update app icons for watchOS 2025-10-31 01:31:34 +08:00
a07ec3ca36 ⬆️ Upgrade deps 2025-10-31 01:02:16 +08:00
d96691e920 🔀 Merge pull request '添加 Solian for Apple Watch' (#8) from features/watchos-app into v3
Reviewed-on: #8
2025-10-30 16:58:52 +00:00
6273b2d917 💄 Auto hide input on watchOS 2025-10-31 00:56:51 +08:00
ab90d244b5 Able to send message on watchOS 2025-10-31 00:39:06 +08:00
dc6af6d9e5 Render attachments of message on watchOS 2025-10-31 00:20:38 +08:00
0ca801d963 Live updates of chat messages with websocket on watchOS 2025-10-31 00:11:24 +08:00
3edcdd72af 🐛 Fixed stupid app state updated twice 2025-10-30 23:58:05 +08:00
402bb3fe04 Make a broke websocket on watchOS (w.i.p) 2025-10-30 22:37:41 +08:00
8ba55eb1be App info header on watchOS 2025-10-30 21:20:41 +08:00
983ae2a1fc Render messages on watchOS 2025-10-30 02:15:51 +08:00
6fc94001b3 Message loading on watchOS 2025-10-30 02:04:10 +08:00
44dbcfdc94 Chat room listing 2025-10-30 01:28:36 +08:00
b57caf56db Able to clear status on watchOS
🐛 Fix some bugs in status on watchOS
2025-10-30 01:15:42 +08:00
dbcd1b6d36 watchOS able to set status 2025-10-30 01:03:19 +08:00
a8055de910 💄 Optimize account view on watchOS 2025-10-30 00:28:56 +08:00
49b15e7674 🐛 Fix compile issue on watchOS 2025-10-30 00:26:46 +08:00
e2369c40db watchOS Account profile page 2025-10-30 00:26:32 +08:00
44c5d91620 💄 Better watchOS video attachment display 2025-10-30 00:03:24 +08:00
7a5a2407b7 💄 Optimized post item row 2025-10-30 00:03:03 +08:00
234434f102 watchOS cache image 2025-10-29 23:41:43 +08:00
9c3b228d02 Pagination real impl on watchOS 2025-10-29 22:21:11 +08:00
82682cae9a watchOS notification screen 2025-10-29 22:13:29 +08:00
fcbd5fe680 watchOS showing video 2025-10-29 21:44:33 +08:00
ad91b17af7 Cache the data fetched from phone in watch 2025-10-29 13:13:13 +08:00
24fa637329 🍱 Add accent color to watchOS 2025-10-29 13:09:19 +08:00
926ae5402f 🐛 Fix bugs 2025-10-29 01:50:27 +08:00
1a37d384e6 ♻️ Refactor watchOS content view 2025-10-29 01:26:27 +08:00
d4cf598f69 Image rendering on watchOS 2025-10-29 00:47:23 +08:00
0106c08891 🐛 Fix API requesting on watchOS app 2025-10-28 23:20:52 +08:00
9697def808 Watch connectivity on iOS 2025-10-28 23:16:44 +08:00
6572875229 🎉 Created a watchOS app that compiles 2025-10-28 22:29:05 +08:00
66590b9079 🐛 Fix ios native code 2025-10-28 22:19:51 +08:00
08b9604b55 🚀 Launch 3.3.0+138 (SNAPSHOT) (HOTFIX) 2025-10-28 18:51:20 +08:00
0602bbd277 🐛 Fix AppBar in SafeArea in explore 2025-10-28 13:08:52 +08:00
350 changed files with 48009 additions and 12523 deletions

View File

@@ -62,3 +62,9 @@ If you want to build the release version, use the flutter build command. Learn m
```bash ```bash
flutter build <platform> flutter build <platform>
``` ```
### Known Issues
Due to the issues with the flutter build tools, [see](https://github.com/flutter/flutter/issues/160622).
Since there is a watchOS app for iOS, you're unable to use the flutter cli to run iOS app. Use xcode instead.

View File

@@ -43,6 +43,16 @@
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </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 --> <!-- Deeplinking -->
<intent-filter android:autoVerify="true"> <intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />

File diff suppressed because it is too large Load Diff

View File

@@ -164,8 +164,6 @@
"checkInResultLevel3": "Good Luck", "checkInResultLevel3": "Good Luck",
"checkInResultLevel4": "Best Luck", "checkInResultLevel4": "Best Luck",
"checkInActivityTitle": "{} checked in on {} and got a {}", "checkInActivityTitle": "{} checked in on {} and got a {}",
"eventCalander": "Event Calander",
"eventCalanderEmpty": "No events on that day.",
"fortuneGraph": "Fortune Trend", "fortuneGraph": "Fortune Trend",
"noFortuneData": "No fortune data available for this month.", "noFortuneData": "No fortune data available for this month.",
"creatorHub": "Creator Hub", "creatorHub": "Creator Hub",
@@ -752,21 +750,6 @@
"rename": "Rename", "rename": "Rename",
"markAsSensitive": "Mark as Sensitive", "markAsSensitive": "Mark as Sensitive",
"fileName": "File name", "fileName": "File name",
"sensitiveCategories": {
"language": "Language",
"sexualContent": "Sexual Content",
"violence": "Violence",
"profanity": "Profanity",
"hateSpeech": "Hate Speech",
"racism": "Racism",
"adultContent": "Adult Content",
"drugAbuse": "Drug Abuse",
"alcoholAbuse": "Alcohol Abuse",
"gambling": "Gambling",
"selfHarm": "Self-harm",
"childAbuse": "Child Abuse",
"other": "Other"
},
"poll": "Poll", "poll": "Poll",
"pollsRecent": "Recent Polls", "pollsRecent": "Recent Polls",
"pollCreateNew": "Create New", "pollCreateNew": "Create New",
@@ -1075,5 +1058,421 @@
"deleteRecycledFiles": "Delete Recycled Files", "deleteRecycledFiles": "Delete Recycled Files",
"recycledFilesDeleted": "Recycled files deleted successfully", "recycledFilesDeleted": "Recycled files deleted successfully",
"failedToDeleteRecycledFiles": "Failed to delete recycled files", "failedToDeleteRecycledFiles": "Failed to delete recycled files",
"upload": "Upload" "upload": "Upload",
"deleteMessage": "Delete Message",
"deleteMessageConfirmation": "Are you sure you want to delete this message?",
"customReaction": "Custom Reaction",
"customReactions": "Custom Reactions",
"stickerPlaceholder": "Sticker Placeholder",
"reactionAttitude": "Reaction Attitude",
"addReaction": "Add Reaction",
"eventCalendar": "Event Calendar",
"eventCalendarEmpty": "No events on that day.",
"walletStats": "Wallet Statistics",
"totalTransactions": "Total Transactions",
"totalOrders": "Total Orders",
"totalIncome": "Total Income",
"totalOutgoing": "Total Outgoing",
"netBalance": "Net Balance",
"messageUpdateLinks": "Server generated links previews",
"messageUpdateEdited": "Edited a message",
"settingsCardBackgroundOpacity": "Card Background Opacity",
"settingsThemeMode": "Theme Mode",
"settingsThemeModeSystem": "System",
"settingsThemeModeLight": "Light",
"settingsThemeModeDark": "Dark",
"enterPin": "Enter your PIN code",
"chatReplyingTo": "Replying to {}",
"chatForwarding": "Forwarding message",
"chatEditing": "Editing message",
"chatNoContent": "No content",
"sensitiveCategories": {
"language": "Language",
"sexualContent": "Sexual Content",
"violence": "Violence",
"profanity": "Profanity",
"hateSpeech": "Hate Speech",
"racism": "Racism",
"adultContent": "Adult Content",
"drugAbuse": "Drug Abuse",
"alcoholAbuse": "Alcohol Abuse",
"gambling": "Gambling",
"selfHarm": "Self-harm",
"childAbuse": "Child Abuse",
"other": "Other"
},
"Searching...": "Searching...",
"searchError": "Search failed. Please try again.",
"tryDifferentKeywords": "Try different keywords or remove search filters",
"settingsWindowOpacity": "Window Opacity",
"messageContent": "Message Content",
"updateAvailable": "Update available",
"noChangelogProvided": "No changelog provided.",
"useSecondarySourceForDownload": "Use secondary source for download",
"installUpdate": "Install update",
"openReleasePage": "Open release page",
"postCompose": "Compose Post",
"postPublish": "Publish Post",
"restoreDraftTitle": "Restore Draft",
"restoreDraftMessage": "A draft was found. Do you want to restore it?",
"draft": "Draft",
"purchaseGift": "Purchase Gift",
"selectRecipient": "Select Recipient",
"changeRecipient": "Change Recipient",
"addMessage": "Add Message",
"skipRecipient": "Skip Recipient",
"giftSubscriptions": "Gift Subscriptions",
"purchaseAGift": "Purchase a Gift",
"redeemAGift": "Redeem a Gift",
"giftHistory": "Gift History",
"sentGifts": "Sent Gifts",
"receivedGifts": "Received Gifts",
"noSentGifts": "No sent gifts",
"noReceivedGifts": "No received gifts",
"stellarGift": "Stellar Gift",
"novaGift": "Nova Gift",
"supernovaGift": "Supernova Gift",
"sameAsMembership": "Same as membership",
"enterGiftCodeToRedeem": "Enter gift code to redeem",
"enterGiftCode": "Enter gift code",
"giftPurchased": "Gift Purchased!",
"shareCodeWithRecipient": "Share this code with the recipient to redeem the gift.",
"openGiftAnyoneCanRedeem": "This is an open gift that anyone can redeem.",
"ok": "OK",
"selectedRecipient": "Selected recipient",
"noRecipientSelected": "No recipient selected",
"thisWillBeAnOpenGift": "This will be an open gift",
"personalMessage": "Personal Message",
"addPersonalMessageForRecipient": "Add a personal message for the recipient",
"giftStatusCreated": "Created",
"giftStatusSent": "Sent",
"giftStatusRedeemed": "Redeemed",
"giftStatusCancelled": "Cancelled",
"giftStatusExpired": "Expired",
"giftStatusUnknown": "Unknown",
"giftCodeCopiedToClipboard": "Gift code copied to clipboard",
"codeLabel": "Code: ",
"subscriptionLabel": "Subscription: ",
"toLabel": "To: ",
"fromLabel": "From: ",
"messageLabel": "Message: ",
"giftRedeemed": "Gift Redeemed!",
"giftRedeemedSuccessfully": "You have successfully redeemed the gift. Your new subscription is now active.",
"cancelGift": "Cancel Gift",
"cancelGiftConfirm": "Are you sure you want to cancel this gift? This action cannot be undone.",
"giftCancelledSuccessfully": "Gift cancelled successfully",
"createFund": "Create Fund",
"fundAmount": "Fund Amount",
"enterAmount": "Enter Amount",
"selectCurrency": "Select Currency",
"splitType": "Split Type",
"evenSplit": "Even Split",
"equalAmountEach": "Equal amount for each recipient",
"randomSplit": "Random Split",
"randomAmountEach": "Random amount for each recipient",
"recipientCount": "Recipient Count",
"numberOfRecipients": "Number of Recipients",
"addPersonalMessageForRecipients": "Add a personal message for recipients",
"invalidAmount": "Invalid amount",
"invalidRecipientCount": "Invalid recipient count",
"fundOverview": "Fund Overview",
"totalFundsSent": "Total Funds Sent",
"totalFundsReceived": "Total Funds Received",
"transactions": "Transactions",
"myFunds": "My Funds",
"availableFunds": "Available Funds",
"fundStatusCreated": "Created",
"fundStatusPartial": "Partially Claimed",
"fundStatusCompleted": "Fully Claimed",
"fundStatusExpired": "Expired",
"fundStatusUnknown": "Unknown",
"recipients": "Recipients",
"fundClaimedSuccessfully": "Fund claimed successfully!",
"claim": "Claim",
"noFundsCreated": "No funds created yet",
"createYourFirstFund": "Create your first fund to get started",
"noAvailableFunds": "No available funds",
"fundsWillAppearHere": "Funds you can claim will appear here",
"fundCreatedSuccessfully": "Fund created successfully!",
"selectRecipients": "Select Recipients",
"noRecipientsSelected": "No recipients selected",
"selectRecipientsToSendFund": "Select recipients to send the fund to",
"addRecipient": "Add Recipient",
"addMoreRecipients": "Add More Recipients",
"transactionDetails": "Transaction Details",
"remarks": "Remarks",
"payer": "Payer",
"payee": "Payee",
"transactionType": "Transaction Type",
"transfer": "Transfer",
"payment": "Payment",
"systemWallet": "System Wallet",
"date": "Date",
"createTransfer": "Create Transfer",
"transferAmount": "Transfer Amount",
"selectPayee": "Select Payee",
"selectedPayee": "Selected Payee",
"noPayeeSelected": "No payee selected",
"selectPayeeToTransfer": "Select payee to transfer to",
"addRemark": "Add Remark",
"transferRemark": "Transfer Remark",
"addRemarkForTransfer": "Add remark for transfer",
"enterPinToConfirmTransfer": "Enter your 6-digit PIN to confirm transfer",
"transferCreatedSuccessfully": "Transfer created successfully!",
"postUpdate": "Update",
"fileMetadata": "File Metadata",
"resend": "Resend",
"fileInfoTitle": "File Information",
"download": "Download",
"info": "Info",
"noStickers": "No Stickers",
"noStickersInPack": "This pack does not contains stickers",
"noStickerPacks": "No Sticker Packs",
"refresh": "Refresh",
"spoiler": "Spoiler",
"activityHeatmap": "Activity Heatmap",
"custom": "Custom",
"usernameColor": "Username Color",
"colorType": "Color Type",
"plain": "Plain",
"gradient": "Gradient",
"colorValue": "Color Value",
"gradientDirection": "Gradient Direction",
"gradientDirectionToRight": "To Right",
"gradientDirectionToLeft": "To Left",
"gradientDirectionToBottom": "To Bottom",
"gradientDirectionToTop": "To Top",
"gradientDirectionToBottomRight": "To Bottom Right",
"gradientDirectionToBottomLeft": "To Bottom Left",
"gradientDirectionToTopRight": "To Top Right",
"gradientDirectionToTopLeft": "To Top Left",
"gradientColors": "Gradient Colors",
"color": "Color",
"addColor": "Add Color",
"availableWithYourPlan": "Available with your plan",
"upgradeRequired": "Upgrade required",
"settingsDisableAnimation": "Disable Animation",
"addTag": "Add Tag",
"accountConnectionProviderSpotify": "Spotify",
"accountConnectionProviderSteam": "Steam",
"timezoneNotFound": "Time zone not found",
"awardPoints": "Awarded {} points",
"postFeaturedOn": "Post featured on {}",
"messageSentAt": "Sent at {}",
"myTickets": "My Tickets",
"drawHistory": "Draw History",
"lottery": "Lottery",
"noLotteryTickets": "No lottery tickets yet",
"buyYourFirstTicket": "Buy your first lottery ticket to get started!",
"buyTicket": "Buy Ticket",
"ticketNumbers": "Numbers: {}, Special: {}",
"cost": "Cost",
"multiplier": "Multiplier",
"prizeWon": "Prize Won",
"pending": "Pending",
"drawn": "Drawn",
"won": "Won",
"lost": "Lost",
"noDrawHistory": "No draw history yet",
"buyLotteryTicket": "Buy Lottery Ticket",
"selectNumbers": "Select Numbers",
"select5UniqueNumbers": "Select 5 unique numbers",
"selectSpecialNumber": "Select Special Number",
"selectMultiplier": "Select Multiplier",
"baseCost": "Base Cost",
"totalCost": "Total Cost",
"prizeStructure": "Prize Structure",
"enterPinToConfirmPurchase": "Enter your PIN to confirm purchase",
"ticketPurchasedSuccessfully": "Ticket purchased successfully!",
"winningNumbers": "Winning Numbers",
"specialNumber": "Special Number",
"totalTickets": "Total Tickets",
"totalWinners": "Total Winners",
"prizePool": "Prize Pool",
"enterPinToConfirmPayment": "Enter your PIN code to confirm payment",
"purchase": "Purchase",
"multiplierLabel": "Multiplier",
"specialOnly": "Special Only",
"matches": "Matches",
"thoughtDefaultTopic": "Reflection",
"thoughtAiName": "SN-chan",
"thoughtUserName": "You",
"thoughtStreamingHint": "Sn-chan is thinking...",
"thoughtInputHint": "Ask sn-chan anything...",
"thoughtNewConversation": "Start New Conversation",
"thoughtParseError": "Failed to parse AI response",
"thoughtFunctionCall": "Use {}",
"aiThought": "AI Thought",
"aiThoughtTitle": "Let sn-chan think",
"postReferenceUnavailable": "Referenced post is unavailable",
"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",
"customReactionHint": "Custom Reaction allow you to use user uploaded stickers as the symbol of the reaction for the post. Exclusive for Stellar Program members.",
"publicationSites": "Publication Sites",
"uploadTasks": "Upload Tasks",
"thoughtFunctionCallBegin": "Calling tool {}",
"thoughtFunctionCallFinish": "{} responded",
"thoughtUnpaidHint": "Thinking unavaiable due to unpaid orders",
"more": "More",
"collapse": "Collapse",
"pollConfirmDiscard": "Are you sure you want to leave? All the poll data you're editing will not be saved.",
"discard": "Discard",
"fund": "Fund",
"fundsRecent": "Recent Funds",
"fundCreateNew": "Create New",
"fundCreateNewHint": "Create a new fund for your message. Select recipients and amount.",
"amountOfSplits": "Amount of Splits",
"enterNumberOfSplits": "Enter Splits Amount",
"orCreateWith": "Or\ncreate with",
"unindexedFiles": "Unindexed files",
"folder": "Folder",
"clearCompleted": "Clear Completed",
"uploadSuccess": "Upload successful!",
"wouldYouLikeToViewFile": "Would you like to view the file?",
"contentCantEmpty": "Content cannot be empty",
"features": "Features",
"unnamed": "Unnamed",
"fundEnvelopeLoadFailed": "Failed to load fund envelope",
"fundEnvelope": "Fund Envelope",
"fundEnvelopeRemaining": "Remaining: {} {}",
"fundEnvelopeSplit": "Split: {}",
"fundEnvelopeSplitEvenly": "Evenly",
"fundEnvelopeSplitRandomly": "Randomly",
"fundEnvelopeClaimSuccess": "Fund claimed successfully!",
"fundEnvelopeStatusCreated": "Created",
"fundEnvelopeStatusPartial": "Partially Claimed",
"fundEnvelopeStatusCompleted": "Fully Claimed",
"fundEnvelopeStatusExpired": "Expired",
"fundEnvelopeStatusUnknown": "Unknown",
"fundEnvelopeRecipients": "Recipients ({}/{} claimed)",
"fundEnvelopeExpiredDaysAgo": {
"one": "Expired {} day ago",
"other": "Expired {} days ago"
},
"fundEnvelopeExpiresSoon": "Expires soon",
"fundEnvelopeExpiresInHours": {
"one": "Expires in {} hour",
"other": "Expires in {} hours"
},
"fundEnvelopeExpiresInDays": {
"one": "Expires in {} day",
"other": "Expires in {} days"
},
"fundEnvelopeRemainingWithSplits": "{} {} / {} splits",
"fundEnvelopeUnknownUser": "Unknown User",
"deleteSite": "Delete Site",
"deleteSiteConfirm": "Are you sure you want to delete this site?",
"siteDeletedSuccess": "Site deleted successfully",
"siteSlug": "Slug",
"siteSlugHint": "my-site",
"siteSlugRequired": "Please enter a slug",
"siteSlugInvalid": "Slug can only contain lowercase letters, numbers, and dashes",
"siteName": "Site Name",
"siteNameHint": "My Publication Site",
"siteNameRequired": "Please enter a site name",
"siteMode": "Mode",
"siteModeFullyManaged": "Fully Managed",
"siteModeSelfManaged": "Self-Managed",
"editPublicationSite": "Edit Publication Site",
"deletePublicationSite": "Delete Publication Site",
"publicationSiteSavedSuccess": "Publication site saved successfully",
"publicationSiteDeleteConfirm": "Are you sure you want to delete this publication site? This action cannot be undone.",
"publicationSiteDeletedSuccess": "Publication site deleted successfully",
"newPublicationSite": "New Publication Site",
"siteDetails": "Site Details",
"siteInformation": "Site Information",
"siteDomain": "Domain",
"siteCreated": "Created",
"siteUpdated": "Updated",
"failedToLoadSite": "Failed to load site",
"sitePages": "Pages",
"noPagesYet": "No pages yet",
"createFirstPage": "Create your first page to get started",
"failedToLoadPages": "Failed to load pages",
"fileManagement": "File Management",
"siteFiles": "Files",
"siteFolder": "Folder",
"siteRoot": "Root",
"noFilesUploadedYet": "No files uploaded yet",
"uploadFirstFile": "Upload your first file to get started",
"failedToLoadFiles": "Failed to load files",
"noFilesFoundInFolder": "No files found in the selected folder",
"fileActions": "File Actions",
"purgeFiles": "Purge Files",
"purgeFilesDescription": "Remove all uploaded files from the site",
"deploySite": "Deploy Site",
"deploySiteDescription": "Upload and deploy a new version from ZIP archive",
"confirmPurge": "Confirm Purge",
"purgeFilesConfirm": "This will permanently delete all files uploaded to this site. This action cannot be undone. Are you sure you want to continue?",
"purgeAllFiles": "Purge All Files",
"allFilesPurgedSuccess": "All files purged successfully",
"failedToPurgeFiles": "Failed to purge files: {}",
"siteDeployedSuccess": "Site deployed successfully",
"failedToDeploySite": "Failed to deploy site: {}",
"createPage": "Create Page",
"editPage": "Edit Page",
"pageType": "Page Type",
"htmlPage": "HTML Page",
"redirectPage": "Redirect Page",
"pageTypeRequired": "Please select a page type",
"pagePath": "Page Path",
"pagePathHint": "/about, /contact, etc.",
"pagePathRequired": "Please enter a page path",
"pagePathInvalid": "Page path can only contain letters, numbers, hyphens, underscores, and slashes",
"pagePathMustStartWithSlash": "Page path must start with /",
"pagePathNoConsecutiveSlashes": "Page path cannot have consecutive slashes",
"pageTitle": "Page Title",
"pageTitleHint": "About Us, Contact, etc.",
"pageTitleRequired": "Please enter a page title",
"pageContentHtml": "Page Content (HTML)",
"pageContentHint": "<h1>Hello World</h1><p>This is my page content...</p>",
"pageContentRequired": "Please enter HTML content for the page",
"redirectTarget": "Redirect Target",
"redirectTargetHint": "/new-page, https://example.com, etc.",
"redirectTargetRequired": "Please enter a redirect target",
"redirectTargetInvalid": "Target must be a relative path (/) or absolute URL (http/https)",
"deletePage": "Delete Page",
"deletePageConfirm": "Are you sure you want to delete this page?",
"savePage": "Save Page",
"pageCreatedSuccess": "Page created successfully",
"pageUpdatedSuccess": "Page updated successfully",
"pageDeletedSuccess": "Page deleted successfully",
"uploadFiles": "Upload Files",
"uploadPath": "Upload Path",
"uploadPathHint": "/ (root) or /assets/images/",
"uploadPathRequired": "Please enter an upload path",
"uploadPathMustStartWithSlash": "Path must start with /",
"uploadPathNoSpaces": "Path cannot contain spaces",
"uploadPathNoConsecutiveSlashes": "Path cannot have consecutive slashes",
"percentCompleted": "{}% completed",
"filesToUpload": "{} files to upload",
"fileSizeKb": "Size: {} KB",
"uploadingEllipsis": "Uploading...",
"uploadFilesCount": {
"one": "Upload {} File",
"other": "Upload {} Files"
},
"allUploadsCompleted": "All uploads completed",
"someUploadsFailed": "Some uploads failed",
"uploadingInProgress": "Uploading in progress",
"readyToUpload": "Ready to upload",
"allFilesUploadedSuccess": "All files uploaded successfully",
"lotteryLastNumberSpecial": "The last selected number will be your special number.",
"lotteryMultiplierRequired": "Please enter a multiplier",
"lotteryMultiplierRange": "Multiplier must be between 1 and 10",
"dropToShare": "Drop to share"
} }

View File

@@ -164,8 +164,6 @@
"checkInResultLevel3": "Good Luck", "checkInResultLevel3": "Good Luck",
"checkInResultLevel4": "Best Luck", "checkInResultLevel4": "Best Luck",
"checkInActivityTitle": "{} checked in on {} and got a {}", "checkInActivityTitle": "{} checked in on {} and got a {}",
"eventCalander": "Event Calander",
"eventCalanderEmpty": "No events on that day.",
"fortuneGraph": "Fortune Trend", "fortuneGraph": "Fortune Trend",
"noFortuneData": "No fortune data available for this month.", "noFortuneData": "No fortune data available for this month.",
"creatorHub": "Creator Hub", "creatorHub": "Creator Hub",
@@ -752,21 +750,6 @@
"rename": "Rename", "rename": "Rename",
"markAsSensitive": "Mark as Sensitive", "markAsSensitive": "Mark as Sensitive",
"fileName": "File name", "fileName": "File name",
"sensitiveCategories": {
"language": "Language",
"sexualContent": "Sexual Content",
"violence": "Violence",
"profanity": "Profanity",
"hateSpeech": "Hate Speech",
"racism": "Racism",
"adultContent": "Adult Content",
"drugAbuse": "Drug Abuse",
"alcoholAbuse": "Alcohol Abuse",
"gambling": "Gambling",
"selfHarm": "Self-harm",
"childAbuse": "Child Abuse",
"other": "Other"
},
"poll": "Poll", "poll": "Poll",
"pollsRecent": "Recent Polls", "pollsRecent": "Recent Polls",
"pollCreateNew": "Create New", "pollCreateNew": "Create New",
@@ -1075,5 +1058,421 @@
"deleteRecycledFiles": "Delete Recycled Files", "deleteRecycledFiles": "Delete Recycled Files",
"recycledFilesDeleted": "Recycled files deleted successfully", "recycledFilesDeleted": "Recycled files deleted successfully",
"failedToDeleteRecycledFiles": "Failed to delete recycled files", "failedToDeleteRecycledFiles": "Failed to delete recycled files",
"upload": "Upload" "upload": "Upload",
"deleteMessage": "Delete Message",
"deleteMessageConfirmation": "Are you sure you want to delete this message?",
"customReaction": "Custom Reaction",
"customReactions": "Custom Reactions",
"stickerPlaceholder": "Sticker Placeholder",
"reactionAttitude": "Reaction Attitude",
"addReaction": "Add Reaction",
"eventCalendar": "Event Calendar",
"eventCalendarEmpty": "No events on that day.",
"walletStats": "Wallet Statistics",
"totalTransactions": "Total Transactions",
"totalOrders": "Total Orders",
"totalIncome": "Total Income",
"totalOutgoing": "Total Outgoing",
"netBalance": "Net Balance",
"messageUpdateLinks": "Server generated links previews",
"messageUpdateEdited": "Edited a message",
"settingsCardBackgroundOpacity": "Card Background Opacity",
"settingsThemeMode": "Theme Mode",
"settingsThemeModeSystem": "System",
"settingsThemeModeLight": "Light",
"settingsThemeModeDark": "Dark",
"enterPin": "Enter your PIN code",
"chatReplyingTo": "Replying to {}",
"chatForwarding": "Forwarding message",
"chatEditing": "Editing message",
"chatNoContent": "No content",
"sensitiveCategories": {
"language": "Language",
"sexualContent": "Sexual Content",
"violence": "Violence",
"profanity": "Profanity",
"hateSpeech": "Hate Speech",
"racism": "Racism",
"adultContent": "Adult Content",
"drugAbuse": "Drug Abuse",
"alcoholAbuse": "Alcohol Abuse",
"gambling": "Gambling",
"selfHarm": "Self-harm",
"childAbuse": "Child Abuse",
"other": "Other"
},
"Searching...": "Searching...",
"searchError": "Search failed. Please try again.",
"tryDifferentKeywords": "Try different keywords or remove search filters",
"settingsWindowOpacity": "Window Opacity",
"messageContent": "Message Content",
"updateAvailable": "Update available",
"noChangelogProvided": "No changelog provided.",
"useSecondarySourceForDownload": "Use secondary source for download",
"installUpdate": "Install update",
"openReleasePage": "Open release page",
"postCompose": "Compose Post",
"postPublish": "Publish Post",
"restoreDraftTitle": "Restore Draft",
"restoreDraftMessage": "A draft was found. Do you want to restore it?",
"draft": "Draft",
"purchaseGift": "Purchase Gift",
"selectRecipient": "Select Recipient",
"changeRecipient": "Change Recipient",
"addMessage": "Add Message",
"skipRecipient": "Skip Recipient",
"giftSubscriptions": "Gift Subscriptions",
"purchaseAGift": "Purchase a Gift",
"redeemAGift": "Redeem a Gift",
"giftHistory": "Gift History",
"sentGifts": "Sent Gifts",
"receivedGifts": "Received Gifts",
"noSentGifts": "No sent gifts",
"noReceivedGifts": "No received gifts",
"stellarGift": "Stellar Gift",
"novaGift": "Nova Gift",
"supernovaGift": "Supernova Gift",
"sameAsMembership": "Same as membership",
"enterGiftCodeToRedeem": "Enter gift code to redeem",
"enterGiftCode": "Enter gift code",
"giftPurchased": "Gift Purchased!",
"shareCodeWithRecipient": "Share this code with the recipient to redeem the gift.",
"openGiftAnyoneCanRedeem": "This is an open gift that anyone can redeem.",
"ok": "OK",
"selectedRecipient": "Selected recipient",
"noRecipientSelected": "No recipient selected",
"thisWillBeAnOpenGift": "This will be an open gift",
"personalMessage": "Personal Message",
"addPersonalMessageForRecipient": "Add a personal message for the recipient",
"giftStatusCreated": "Created",
"giftStatusSent": "Sent",
"giftStatusRedeemed": "Redeemed",
"giftStatusCancelled": "Cancelled",
"giftStatusExpired": "Expired",
"giftStatusUnknown": "Unknown",
"giftCodeCopiedToClipboard": "Gift code copied to clipboard",
"codeLabel": "Code: ",
"subscriptionLabel": "Subscription: ",
"toLabel": "To: ",
"fromLabel": "From: ",
"messageLabel": "Message: ",
"giftRedeemed": "Gift Redeemed!",
"giftRedeemedSuccessfully": "You have successfully redeemed the gift. Your new subscription is now active.",
"cancelGift": "Cancel Gift",
"cancelGiftConfirm": "Are you sure you want to cancel this gift? This action cannot be undone.",
"giftCancelledSuccessfully": "Gift cancelled successfully",
"createFund": "Create Fund",
"fundAmount": "Fund Amount",
"enterAmount": "Enter Amount",
"selectCurrency": "Select Currency",
"splitType": "Split Type",
"evenSplit": "Even Split",
"equalAmountEach": "Equal amount for each recipient",
"randomSplit": "Random Split",
"randomAmountEach": "Random amount for each recipient",
"recipientCount": "Recipient Count",
"numberOfRecipients": "Number of Recipients",
"addPersonalMessageForRecipients": "Add a personal message for recipients",
"invalidAmount": "Invalid amount",
"invalidRecipientCount": "Invalid recipient count",
"fundOverview": "Fund Overview",
"totalFundsSent": "Total Funds Sent",
"totalFundsReceived": "Total Funds Received",
"transactions": "Transactions",
"myFunds": "My Funds",
"availableFunds": "Available Funds",
"fundStatusCreated": "Created",
"fundStatusPartial": "Partially Claimed",
"fundStatusCompleted": "Fully Claimed",
"fundStatusExpired": "Expired",
"fundStatusUnknown": "Unknown",
"recipients": "Recipients",
"fundClaimedSuccessfully": "Fund claimed successfully!",
"claim": "Claim",
"noFundsCreated": "No funds created yet",
"createYourFirstFund": "Create your first fund to get started",
"noAvailableFunds": "No available funds",
"fundsWillAppearHere": "Funds you can claim will appear here",
"fundCreatedSuccessfully": "Fund created successfully!",
"selectRecipients": "Select Recipients",
"noRecipientsSelected": "No recipients selected",
"selectRecipientsToSendFund": "Select recipients to send the fund to",
"addRecipient": "Add Recipient",
"addMoreRecipients": "Add More Recipients",
"transactionDetails": "Transaction Details",
"remarks": "Remarks",
"payer": "Payer",
"payee": "Payee",
"transactionType": "Transaction Type",
"transfer": "Transfer",
"payment": "Payment",
"systemWallet": "System Wallet",
"date": "Date",
"createTransfer": "Create Transfer",
"transferAmount": "Transfer Amount",
"selectPayee": "Select Payee",
"selectedPayee": "Selected Payee",
"noPayeeSelected": "No payee selected",
"selectPayeeToTransfer": "Select payee to transfer to",
"addRemark": "Add Remark",
"transferRemark": "Transfer Remark",
"addRemarkForTransfer": "Add remark for transfer",
"enterPinToConfirmTransfer": "Enter your 6-digit PIN to confirm transfer",
"transferCreatedSuccessfully": "Transfer created successfully!",
"postUpdate": "Update",
"fileMetadata": "File Metadata",
"resend": "Resend",
"fileInfoTitle": "File Information",
"download": "Download",
"info": "Info",
"noStickers": "No Stickers",
"noStickersInPack": "This pack does not contains stickers",
"noStickerPacks": "No Sticker Packs",
"refresh": "Refresh",
"spoiler": "Spoiler",
"activityHeatmap": "Activity Heatmap",
"custom": "Custom",
"usernameColor": "Username Color",
"colorType": "Color Type",
"plain": "Plain",
"gradient": "Gradient",
"colorValue": "Color Value",
"gradientDirection": "Gradient Direction",
"gradientDirectionToRight": "To Right",
"gradientDirectionToLeft": "To Left",
"gradientDirectionToBottom": "To Bottom",
"gradientDirectionToTop": "To Top",
"gradientDirectionToBottomRight": "To Bottom Right",
"gradientDirectionToBottomLeft": "To Bottom Left",
"gradientDirectionToTopRight": "To Top Right",
"gradientDirectionToTopLeft": "To Top Left",
"gradientColors": "Gradient Colors",
"color": "Color",
"addColor": "Add Color",
"availableWithYourPlan": "Available with your plan",
"upgradeRequired": "Upgrade required",
"settingsDisableAnimation": "Disable Animation",
"addTag": "Add Tag",
"accountConnectionProviderSpotify": "Spotify",
"accountConnectionProviderSteam": "Steam",
"timezoneNotFound": "Time zone not found",
"awardPoints": "Awarded {} points",
"postFeaturedOn": "Post featured on {}",
"messageSentAt": "Sent at {}",
"myTickets": "My Tickets",
"drawHistory": "Draw History",
"lottery": "Lottery",
"noLotteryTickets": "No lottery tickets yet",
"buyYourFirstTicket": "Buy your first lottery ticket to get started!",
"buyTicket": "Buy Ticket",
"ticketNumbers": "Numbers: {}, Special: {}",
"cost": "Cost",
"multiplier": "Multiplier",
"prizeWon": "Prize Won",
"pending": "Pending",
"drawn": "Drawn",
"won": "Won",
"lost": "Lost",
"noDrawHistory": "No draw history yet",
"buyLotteryTicket": "Buy Lottery Ticket",
"selectNumbers": "Select Numbers",
"select5UniqueNumbers": "Select 5 unique numbers",
"selectSpecialNumber": "Select Special Number",
"selectMultiplier": "Select Multiplier",
"baseCost": "Base Cost",
"totalCost": "Total Cost",
"prizeStructure": "Prize Structure",
"enterPinToConfirmPurchase": "Enter your PIN to confirm purchase",
"ticketPurchasedSuccessfully": "Ticket purchased successfully!",
"winningNumbers": "Winning Numbers",
"specialNumber": "Special Number",
"totalTickets": "Total Tickets",
"totalWinners": "Total Winners",
"prizePool": "Prize Pool",
"enterPinToConfirmPayment": "Enter your PIN code to confirm payment",
"purchase": "Purchase",
"multiplierLabel": "Multiplier",
"specialOnly": "Special Only",
"matches": "Matches",
"thoughtDefaultTopic": "Reflection",
"thoughtAiName": "SN-chan",
"thoughtUserName": "You",
"thoughtStreamingHint": "Sn-chan is thinking...",
"thoughtInputHint": "Ask sn-chan anything...",
"thoughtNewConversation": "Start New Conversation",
"thoughtParseError": "Failed to parse AI response",
"thoughtFunctionCall": "Use {}",
"aiThought": "AI Thought",
"aiThoughtTitle": "Let sn-chan think",
"postReferenceUnavailable": "Referenced post is unavailable",
"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",
"customReactionHint": "Custom Reaction allow you to use user uploaded stickers as the symbol of the reaction for the post. Exclusive for Stellar Program members.",
"publicationSites": "Publication Sites",
"uploadTasks": "Upload Tasks",
"thoughtFunctionCallBegin": "Calling tool {}",
"thoughtFunctionCallFinish": "{} responded",
"thoughtUnpaidHint": "Thinking unavaiable due to unpaid orders",
"more": "More",
"collapse": "Collapse",
"pollConfirmDiscard": "Are you sure you want to leave? All the poll data you're editing will not be saved.",
"discard": "Discard",
"fund": "Fund",
"fundsRecent": "Recent Funds",
"fundCreateNew": "Create New",
"fundCreateNewHint": "Create a new fund for your message. Select recipients and amount.",
"amountOfSplits": "Amount of Splits",
"enterNumberOfSplits": "Enter Splits Amount",
"orCreateWith": "Or\ncreate with",
"unindexedFiles": "Unindexed files",
"folder": "Folder",
"clearCompleted": "Clear Completed",
"uploadSuccess": "Upload successful!",
"wouldYouLikeToViewFile": "Would you like to view the file?",
"contentCantEmpty": "Content cannot be empty",
"features": "Features",
"unnamed": "Unnamed",
"fundEnvelopeLoadFailed": "Failed to load fund envelope",
"fundEnvelope": "Fund Envelope",
"fundEnvelopeRemaining": "Remaining: {} {}",
"fundEnvelopeSplit": "Split: {}",
"fundEnvelopeSplitEvenly": "Evenly",
"fundEnvelopeSplitRandomly": "Randomly",
"fundEnvelopeClaimSuccess": "Fund claimed successfully!",
"fundEnvelopeStatusCreated": "Created",
"fundEnvelopeStatusPartial": "Partially Claimed",
"fundEnvelopeStatusCompleted": "Fully Claimed",
"fundEnvelopeStatusExpired": "Expired",
"fundEnvelopeStatusUnknown": "Unknown",
"fundEnvelopeRecipients": "Recipients ({}/{} claimed)",
"fundEnvelopeExpiredDaysAgo": {
"one": "Expired {} day ago",
"other": "Expired {} days ago"
},
"fundEnvelopeExpiresSoon": "Expires soon",
"fundEnvelopeExpiresInHours": {
"one": "Expires in {} hour",
"other": "Expires in {} hours"
},
"fundEnvelopeExpiresInDays": {
"one": "Expires in {} day",
"other": "Expires in {} days"
},
"fundEnvelopeRemainingWithSplits": "{} {} / {} splits",
"fundEnvelopeUnknownUser": "Unknown User",
"deleteSite": "Delete Site",
"deleteSiteConfirm": "Are you sure you want to delete this site?",
"siteDeletedSuccess": "Site deleted successfully",
"siteSlug": "Slug",
"siteSlugHint": "my-site",
"siteSlugRequired": "Please enter a slug",
"siteSlugInvalid": "Slug can only contain lowercase letters, numbers, and dashes",
"siteName": "Site Name",
"siteNameHint": "My Publication Site",
"siteNameRequired": "Please enter a site name",
"siteMode": "Mode",
"siteModeFullyManaged": "Fully Managed",
"siteModeSelfManaged": "Self-Managed",
"editPublicationSite": "Edit Publication Site",
"deletePublicationSite": "Delete Publication Site",
"publicationSiteSavedSuccess": "Publication site saved successfully",
"publicationSiteDeleteConfirm": "Are you sure you want to delete this publication site? This action cannot be undone.",
"publicationSiteDeletedSuccess": "Publication site deleted successfully",
"newPublicationSite": "New Publication Site",
"siteDetails": "Site Details",
"siteInformation": "Site Information",
"siteDomain": "Domain",
"siteCreated": "Created",
"siteUpdated": "Updated",
"failedToLoadSite": "Failed to load site",
"sitePages": "Pages",
"noPagesYet": "No pages yet",
"createFirstPage": "Create your first page to get started",
"failedToLoadPages": "Failed to load pages",
"fileManagement": "File Management",
"siteFiles": "Files",
"siteFolder": "Folder",
"siteRoot": "Root",
"noFilesUploadedYet": "No files uploaded yet",
"uploadFirstFile": "Upload your first file to get started",
"failedToLoadFiles": "Failed to load files",
"noFilesFoundInFolder": "No files found in the selected folder",
"fileActions": "File Actions",
"purgeFiles": "Purge Files",
"purgeFilesDescription": "Remove all uploaded files from the site",
"deploySite": "Deploy Site",
"deploySiteDescription": "Upload and deploy a new version from ZIP archive",
"confirmPurge": "Confirm Purge",
"purgeFilesConfirm": "This will permanently delete all files uploaded to this site. This action cannot be undone. Are you sure you want to continue?",
"purgeAllFiles": "Purge All Files",
"allFilesPurgedSuccess": "All files purged successfully",
"failedToPurgeFiles": "Failed to purge files: {}",
"siteDeployedSuccess": "Site deployed successfully",
"failedToDeploySite": "Failed to deploy site: {}",
"createPage": "Create Page",
"editPage": "Edit Page",
"pageType": "Page Type",
"htmlPage": "HTML Page",
"redirectPage": "Redirect Page",
"pageTypeRequired": "Please select a page type",
"pagePath": "Page Path",
"pagePathHint": "/about, /contact, etc.",
"pagePathRequired": "Please enter a page path",
"pagePathInvalid": "Page path can only contain letters, numbers, hyphens, underscores, and slashes",
"pagePathMustStartWithSlash": "Page path must start with /",
"pagePathNoConsecutiveSlashes": "Page path cannot have consecutive slashes",
"pageTitle": "Page Title",
"pageTitleHint": "About Us, Contact, etc.",
"pageTitleRequired": "Please enter a page title",
"pageContentHtml": "Page Content (HTML)",
"pageContentHint": "<h1>Hello World</h1><p>This is my page content...</p>",
"pageContentRequired": "Please enter HTML content for the page",
"redirectTarget": "Redirect Target",
"redirectTargetHint": "/new-page, https://example.com, etc.",
"redirectTargetRequired": "Please enter a redirect target",
"redirectTargetInvalid": "Target must be a relative path (/) or absolute URL (http/https)",
"deletePage": "Delete Page",
"deletePageConfirm": "Are you sure you want to delete this page?",
"savePage": "Save Page",
"pageCreatedSuccess": "Page created successfully",
"pageUpdatedSuccess": "Page updated successfully",
"pageDeletedSuccess": "Page deleted successfully",
"uploadFiles": "Upload Files",
"uploadPath": "Upload Path",
"uploadPathHint": "/ (root) or /assets/images/",
"uploadPathRequired": "Please enter an upload path",
"uploadPathMustStartWithSlash": "Path must start with /",
"uploadPathNoSpaces": "Path cannot contain spaces",
"uploadPathNoConsecutiveSlashes": "Path cannot have consecutive slashes",
"percentCompleted": "{}% completed",
"filesToUpload": "{} files to upload",
"fileSizeKb": "Size: {} KB",
"uploadingEllipsis": "Uploading...",
"uploadFilesCount": {
"one": "Upload {} File",
"other": "Upload {} Files"
},
"allUploadsCompleted": "All uploads completed",
"someUploadsFailed": "Some uploads failed",
"uploadingInProgress": "Uploading in progress",
"readyToUpload": "Ready to upload",
"allFilesUploadedSuccess": "All files uploaded successfully",
"lotteryLastNumberSpecial": "The last selected number will be your special number.",
"lotteryMultiplierRequired": "Please enter a multiplier",
"lotteryMultiplierRange": "Multiplier must be between 1 and 10",
"dropToShare": "Drop to share"
} }

View File

@@ -164,8 +164,6 @@
"checkInResultLevel3": "Good Luck", "checkInResultLevel3": "Good Luck",
"checkInResultLevel4": "Best Luck", "checkInResultLevel4": "Best Luck",
"checkInActivityTitle": "{} checked in on {} and got a {}", "checkInActivityTitle": "{} checked in on {} and got a {}",
"eventCalander": "Event Calander",
"eventCalanderEmpty": "No events on that day.",
"fortuneGraph": "Fortune Trend", "fortuneGraph": "Fortune Trend",
"noFortuneData": "No fortune data available for this month.", "noFortuneData": "No fortune data available for this month.",
"creatorHub": "Creator Hub", "creatorHub": "Creator Hub",
@@ -752,21 +750,6 @@
"rename": "Rename", "rename": "Rename",
"markAsSensitive": "Mark as Sensitive", "markAsSensitive": "Mark as Sensitive",
"fileName": "File name", "fileName": "File name",
"sensitiveCategories": {
"language": "Language",
"sexualContent": "Sexual Content",
"violence": "Violence",
"profanity": "Profanity",
"hateSpeech": "Hate Speech",
"racism": "Racism",
"adultContent": "Adult Content",
"drugAbuse": "Drug Abuse",
"alcoholAbuse": "Alcohol Abuse",
"gambling": "Gambling",
"selfHarm": "Self-harm",
"childAbuse": "Child Abuse",
"other": "Other"
},
"poll": "Poll", "poll": "Poll",
"pollsRecent": "Recent Polls", "pollsRecent": "Recent Polls",
"pollCreateNew": "Create New", "pollCreateNew": "Create New",
@@ -1075,5 +1058,421 @@
"deleteRecycledFiles": "Delete Recycled Files", "deleteRecycledFiles": "Delete Recycled Files",
"recycledFilesDeleted": "Recycled files deleted successfully", "recycledFilesDeleted": "Recycled files deleted successfully",
"failedToDeleteRecycledFiles": "Failed to delete recycled files", "failedToDeleteRecycledFiles": "Failed to delete recycled files",
"upload": "Upload" "upload": "Upload",
"deleteMessage": "Delete Message",
"deleteMessageConfirmation": "Are you sure you want to delete this message?",
"customReaction": "Custom Reaction",
"customReactions": "Custom Reactions",
"stickerPlaceholder": "Sticker Placeholder",
"reactionAttitude": "Reaction Attitude",
"addReaction": "Add Reaction",
"eventCalendar": "Event Calendar",
"eventCalendarEmpty": "No events on that day.",
"walletStats": "Wallet Statistics",
"totalTransactions": "Total Transactions",
"totalOrders": "Total Orders",
"totalIncome": "Total Income",
"totalOutgoing": "Total Outgoing",
"netBalance": "Net Balance",
"messageUpdateLinks": "Server generated links previews",
"messageUpdateEdited": "Edited a message",
"settingsCardBackgroundOpacity": "Card Background Opacity",
"settingsThemeMode": "Theme Mode",
"settingsThemeModeSystem": "System",
"settingsThemeModeLight": "Light",
"settingsThemeModeDark": "Dark",
"enterPin": "Enter your PIN code",
"chatReplyingTo": "Replying to {}",
"chatForwarding": "Forwarding message",
"chatEditing": "Editing message",
"chatNoContent": "No content",
"sensitiveCategories": {
"language": "Language",
"sexualContent": "Sexual Content",
"violence": "Violence",
"profanity": "Profanity",
"hateSpeech": "Hate Speech",
"racism": "Racism",
"adultContent": "Adult Content",
"drugAbuse": "Drug Abuse",
"alcoholAbuse": "Alcohol Abuse",
"gambling": "Gambling",
"selfHarm": "Self-harm",
"childAbuse": "Child Abuse",
"other": "Other"
},
"Searching...": "Searching...",
"searchError": "Search failed. Please try again.",
"tryDifferentKeywords": "Try different keywords or remove search filters",
"settingsWindowOpacity": "Window Opacity",
"messageContent": "Message Content",
"updateAvailable": "Update available",
"noChangelogProvided": "No changelog provided.",
"useSecondarySourceForDownload": "Use secondary source for download",
"installUpdate": "Install update",
"openReleasePage": "Open release page",
"postCompose": "Compose Post",
"postPublish": "Publish Post",
"restoreDraftTitle": "Restore Draft",
"restoreDraftMessage": "A draft was found. Do you want to restore it?",
"draft": "Draft",
"purchaseGift": "Purchase Gift",
"selectRecipient": "Select Recipient",
"changeRecipient": "Change Recipient",
"addMessage": "Add Message",
"skipRecipient": "Skip Recipient",
"giftSubscriptions": "Gift Subscriptions",
"purchaseAGift": "Purchase a Gift",
"redeemAGift": "Redeem a Gift",
"giftHistory": "Gift History",
"sentGifts": "Sent Gifts",
"receivedGifts": "Received Gifts",
"noSentGifts": "No sent gifts",
"noReceivedGifts": "No received gifts",
"stellarGift": "Stellar Gift",
"novaGift": "Nova Gift",
"supernovaGift": "Supernova Gift",
"sameAsMembership": "Same as membership",
"enterGiftCodeToRedeem": "Enter gift code to redeem",
"enterGiftCode": "Enter gift code",
"giftPurchased": "Gift Purchased!",
"shareCodeWithRecipient": "Share this code with the recipient to redeem the gift.",
"openGiftAnyoneCanRedeem": "This is an open gift that anyone can redeem.",
"ok": "OK",
"selectedRecipient": "Selected recipient",
"noRecipientSelected": "No recipient selected",
"thisWillBeAnOpenGift": "This will be an open gift",
"personalMessage": "Personal Message",
"addPersonalMessageForRecipient": "Add a personal message for the recipient",
"giftStatusCreated": "Created",
"giftStatusSent": "Sent",
"giftStatusRedeemed": "Redeemed",
"giftStatusCancelled": "Cancelled",
"giftStatusExpired": "Expired",
"giftStatusUnknown": "Unknown",
"giftCodeCopiedToClipboard": "Gift code copied to clipboard",
"codeLabel": "Code: ",
"subscriptionLabel": "Subscription: ",
"toLabel": "To: ",
"fromLabel": "From: ",
"messageLabel": "Message: ",
"giftRedeemed": "Gift Redeemed!",
"giftRedeemedSuccessfully": "You have successfully redeemed the gift. Your new subscription is now active.",
"cancelGift": "Cancel Gift",
"cancelGiftConfirm": "Are you sure you want to cancel this gift? This action cannot be undone.",
"giftCancelledSuccessfully": "Gift cancelled successfully",
"createFund": "Create Fund",
"fundAmount": "Fund Amount",
"enterAmount": "Enter Amount",
"selectCurrency": "Select Currency",
"splitType": "Split Type",
"evenSplit": "Even Split",
"equalAmountEach": "Equal amount for each recipient",
"randomSplit": "Random Split",
"randomAmountEach": "Random amount for each recipient",
"recipientCount": "Recipient Count",
"numberOfRecipients": "Number of Recipients",
"addPersonalMessageForRecipients": "Add a personal message for recipients",
"invalidAmount": "Invalid amount",
"invalidRecipientCount": "Invalid recipient count",
"fundOverview": "Fund Overview",
"totalFundsSent": "Total Funds Sent",
"totalFundsReceived": "Total Funds Received",
"transactions": "Transactions",
"myFunds": "My Funds",
"availableFunds": "Available Funds",
"fundStatusCreated": "Created",
"fundStatusPartial": "Partially Claimed",
"fundStatusCompleted": "Fully Claimed",
"fundStatusExpired": "Expired",
"fundStatusUnknown": "Unknown",
"recipients": "Recipients",
"fundClaimedSuccessfully": "Fund claimed successfully!",
"claim": "Claim",
"noFundsCreated": "No funds created yet",
"createYourFirstFund": "Create your first fund to get started",
"noAvailableFunds": "No available funds",
"fundsWillAppearHere": "Funds you can claim will appear here",
"fundCreatedSuccessfully": "Fund created successfully!",
"selectRecipients": "Select Recipients",
"noRecipientsSelected": "No recipients selected",
"selectRecipientsToSendFund": "Select recipients to send the fund to",
"addRecipient": "Add Recipient",
"addMoreRecipients": "Add More Recipients",
"transactionDetails": "Transaction Details",
"remarks": "Remarks",
"payer": "Payer",
"payee": "Payee",
"transactionType": "Transaction Type",
"transfer": "Transfer",
"payment": "Payment",
"systemWallet": "System Wallet",
"date": "Date",
"createTransfer": "Create Transfer",
"transferAmount": "Transfer Amount",
"selectPayee": "Select Payee",
"selectedPayee": "Selected Payee",
"noPayeeSelected": "No payee selected",
"selectPayeeToTransfer": "Select payee to transfer to",
"addRemark": "Add Remark",
"transferRemark": "Transfer Remark",
"addRemarkForTransfer": "Add remark for transfer",
"enterPinToConfirmTransfer": "Enter your 6-digit PIN to confirm transfer",
"transferCreatedSuccessfully": "Transfer created successfully!",
"postUpdate": "Update",
"fileMetadata": "File Metadata",
"resend": "Resend",
"fileInfoTitle": "File Information",
"download": "Download",
"info": "Info",
"noStickers": "No Stickers",
"noStickersInPack": "This pack does not contains stickers",
"noStickerPacks": "No Sticker Packs",
"refresh": "Refresh",
"spoiler": "Spoiler",
"activityHeatmap": "Activity Heatmap",
"custom": "Custom",
"usernameColor": "Username Color",
"colorType": "Color Type",
"plain": "Plain",
"gradient": "Gradient",
"colorValue": "Color Value",
"gradientDirection": "Gradient Direction",
"gradientDirectionToRight": "To Right",
"gradientDirectionToLeft": "To Left",
"gradientDirectionToBottom": "To Bottom",
"gradientDirectionToTop": "To Top",
"gradientDirectionToBottomRight": "To Bottom Right",
"gradientDirectionToBottomLeft": "To Bottom Left",
"gradientDirectionToTopRight": "To Top Right",
"gradientDirectionToTopLeft": "To Top Left",
"gradientColors": "Gradient Colors",
"color": "Color",
"addColor": "Add Color",
"availableWithYourPlan": "Available with your plan",
"upgradeRequired": "Upgrade required",
"settingsDisableAnimation": "Disable Animation",
"addTag": "Add Tag",
"accountConnectionProviderSpotify": "Spotify",
"accountConnectionProviderSteam": "Steam",
"timezoneNotFound": "Time zone not found",
"awardPoints": "Awarded {} points",
"postFeaturedOn": "Post featured on {}",
"messageSentAt": "Sent at {}",
"myTickets": "My Tickets",
"drawHistory": "Draw History",
"lottery": "Lottery",
"noLotteryTickets": "No lottery tickets yet",
"buyYourFirstTicket": "Buy your first lottery ticket to get started!",
"buyTicket": "Buy Ticket",
"ticketNumbers": "Numbers: {}, Special: {}",
"cost": "Cost",
"multiplier": "Multiplier",
"prizeWon": "Prize Won",
"pending": "Pending",
"drawn": "Drawn",
"won": "Won",
"lost": "Lost",
"noDrawHistory": "No draw history yet",
"buyLotteryTicket": "Buy Lottery Ticket",
"selectNumbers": "Select Numbers",
"select5UniqueNumbers": "Select 5 unique numbers",
"selectSpecialNumber": "Select Special Number",
"selectMultiplier": "Select Multiplier",
"baseCost": "Base Cost",
"totalCost": "Total Cost",
"prizeStructure": "Prize Structure",
"enterPinToConfirmPurchase": "Enter your PIN to confirm purchase",
"ticketPurchasedSuccessfully": "Ticket purchased successfully!",
"winningNumbers": "Winning Numbers",
"specialNumber": "Special Number",
"totalTickets": "Total Tickets",
"totalWinners": "Total Winners",
"prizePool": "Prize Pool",
"enterPinToConfirmPayment": "Enter your PIN code to confirm payment",
"purchase": "Purchase",
"multiplierLabel": "Multiplier",
"specialOnly": "Special Only",
"matches": "Matches",
"thoughtDefaultTopic": "Reflection",
"thoughtAiName": "SN-chan",
"thoughtUserName": "You",
"thoughtStreamingHint": "Sn-chan is thinking...",
"thoughtInputHint": "Ask sn-chan anything...",
"thoughtNewConversation": "Start New Conversation",
"thoughtParseError": "Failed to parse AI response",
"thoughtFunctionCall": "Use {}",
"aiThought": "AI Thought",
"aiThoughtTitle": "Let sn-chan think",
"postReferenceUnavailable": "Referenced post is unavailable",
"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",
"customReactionHint": "Custom Reaction allow you to use user uploaded stickers as the symbol of the reaction for the post. Exclusive for Stellar Program members.",
"publicationSites": "Publication Sites",
"uploadTasks": "Upload Tasks",
"thoughtFunctionCallBegin": "Calling tool {}",
"thoughtFunctionCallFinish": "{} responded",
"thoughtUnpaidHint": "Thinking unavaiable due to unpaid orders",
"more": "More",
"collapse": "Collapse",
"pollConfirmDiscard": "Are you sure you want to leave? All the poll data you're editing will not be saved.",
"discard": "Discard",
"fund": "Fund",
"fundsRecent": "Recent Funds",
"fundCreateNew": "Create New",
"fundCreateNewHint": "Create a new fund for your message. Select recipients and amount.",
"amountOfSplits": "Amount of Splits",
"enterNumberOfSplits": "Enter Splits Amount",
"orCreateWith": "Or\ncreate with",
"unindexedFiles": "Unindexed files",
"folder": "Folder",
"clearCompleted": "Clear Completed",
"uploadSuccess": "Upload successful!",
"wouldYouLikeToViewFile": "Would you like to view the file?",
"contentCantEmpty": "Content cannot be empty",
"features": "Features",
"unnamed": "Unnamed",
"fundEnvelopeLoadFailed": "Failed to load fund envelope",
"fundEnvelope": "Fund Envelope",
"fundEnvelopeRemaining": "Remaining: {} {}",
"fundEnvelopeSplit": "Split: {}",
"fundEnvelopeSplitEvenly": "Evenly",
"fundEnvelopeSplitRandomly": "Randomly",
"fundEnvelopeClaimSuccess": "Fund claimed successfully!",
"fundEnvelopeStatusCreated": "Created",
"fundEnvelopeStatusPartial": "Partially Claimed",
"fundEnvelopeStatusCompleted": "Fully Claimed",
"fundEnvelopeStatusExpired": "Expired",
"fundEnvelopeStatusUnknown": "Unknown",
"fundEnvelopeRecipients": "Recipients ({}/{} claimed)",
"fundEnvelopeExpiredDaysAgo": {
"one": "Expired {} day ago",
"other": "Expired {} days ago"
},
"fundEnvelopeExpiresSoon": "Expires soon",
"fundEnvelopeExpiresInHours": {
"one": "Expires in {} hour",
"other": "Expires in {} hours"
},
"fundEnvelopeExpiresInDays": {
"one": "Expires in {} day",
"other": "Expires in {} days"
},
"fundEnvelopeRemainingWithSplits": "{} {} / {} splits",
"fundEnvelopeUnknownUser": "Unknown User",
"deleteSite": "Delete Site",
"deleteSiteConfirm": "Are you sure you want to delete this site?",
"siteDeletedSuccess": "Site deleted successfully",
"siteSlug": "Slug",
"siteSlugHint": "my-site",
"siteSlugRequired": "Please enter a slug",
"siteSlugInvalid": "Slug can only contain lowercase letters, numbers, and dashes",
"siteName": "Site Name",
"siteNameHint": "My Publication Site",
"siteNameRequired": "Please enter a site name",
"siteMode": "Mode",
"siteModeFullyManaged": "Fully Managed",
"siteModeSelfManaged": "Self-Managed",
"editPublicationSite": "Edit Publication Site",
"deletePublicationSite": "Delete Publication Site",
"publicationSiteSavedSuccess": "Publication site saved successfully",
"publicationSiteDeleteConfirm": "Are you sure you want to delete this publication site? This action cannot be undone.",
"publicationSiteDeletedSuccess": "Publication site deleted successfully",
"newPublicationSite": "New Publication Site",
"siteDetails": "Site Details",
"siteInformation": "Site Information",
"siteDomain": "Domain",
"siteCreated": "Created",
"siteUpdated": "Updated",
"failedToLoadSite": "Failed to load site",
"sitePages": "Pages",
"noPagesYet": "No pages yet",
"createFirstPage": "Create your first page to get started",
"failedToLoadPages": "Failed to load pages",
"fileManagement": "File Management",
"siteFiles": "Files",
"siteFolder": "Folder",
"siteRoot": "Root",
"noFilesUploadedYet": "No files uploaded yet",
"uploadFirstFile": "Upload your first file to get started",
"failedToLoadFiles": "Failed to load files",
"noFilesFoundInFolder": "No files found in the selected folder",
"fileActions": "File Actions",
"purgeFiles": "Purge Files",
"purgeFilesDescription": "Remove all uploaded files from the site",
"deploySite": "Deploy Site",
"deploySiteDescription": "Upload and deploy a new version from ZIP archive",
"confirmPurge": "Confirm Purge",
"purgeFilesConfirm": "This will permanently delete all files uploaded to this site. This action cannot be undone. Are you sure you want to continue?",
"purgeAllFiles": "Purge All Files",
"allFilesPurgedSuccess": "All files purged successfully",
"failedToPurgeFiles": "Failed to purge files: {}",
"siteDeployedSuccess": "Site deployed successfully",
"failedToDeploySite": "Failed to deploy site: {}",
"createPage": "Create Page",
"editPage": "Edit Page",
"pageType": "Page Type",
"htmlPage": "HTML Page",
"redirectPage": "Redirect Page",
"pageTypeRequired": "Please select a page type",
"pagePath": "Page Path",
"pagePathHint": "/about, /contact, etc.",
"pagePathRequired": "Please enter a page path",
"pagePathInvalid": "Page path can only contain letters, numbers, hyphens, underscores, and slashes",
"pagePathMustStartWithSlash": "Page path must start with /",
"pagePathNoConsecutiveSlashes": "Page path cannot have consecutive slashes",
"pageTitle": "Page Title",
"pageTitleHint": "About Us, Contact, etc.",
"pageTitleRequired": "Please enter a page title",
"pageContentHtml": "Page Content (HTML)",
"pageContentHint": "<h1>Hello World</h1><p>This is my page content...</p>",
"pageContentRequired": "Please enter HTML content for the page",
"redirectTarget": "Redirect Target",
"redirectTargetHint": "/new-page, https://example.com, etc.",
"redirectTargetRequired": "Please enter a redirect target",
"redirectTargetInvalid": "Target must be a relative path (/) or absolute URL (http/https)",
"deletePage": "Delete Page",
"deletePageConfirm": "Are you sure you want to delete this page?",
"savePage": "Save Page",
"pageCreatedSuccess": "Page created successfully",
"pageUpdatedSuccess": "Page updated successfully",
"pageDeletedSuccess": "Page deleted successfully",
"uploadFiles": "Upload Files",
"uploadPath": "Upload Path",
"uploadPathHint": "/ (root) or /assets/images/",
"uploadPathRequired": "Please enter an upload path",
"uploadPathMustStartWithSlash": "Path must start with /",
"uploadPathNoSpaces": "Path cannot contain spaces",
"uploadPathNoConsecutiveSlashes": "Path cannot have consecutive slashes",
"percentCompleted": "{}% completed",
"filesToUpload": "{} files to upload",
"fileSizeKb": "Size: {} KB",
"uploadingEllipsis": "Uploading...",
"uploadFilesCount": {
"one": "Upload {} File",
"other": "Upload {} Files"
},
"allUploadsCompleted": "All uploads completed",
"someUploadsFailed": "Some uploads failed",
"uploadingInProgress": "Uploading in progress",
"readyToUpload": "Ready to upload",
"allFilesUploadedSuccess": "All files uploaded successfully",
"lotteryLastNumberSpecial": "The last selected number will be your special number.",
"lotteryMultiplierRequired": "Please enter a multiplier",
"lotteryMultiplierRange": "Multiplier must be between 1 and 10",
"dropToShare": "Drop to share"
} }

File diff suppressed because it is too large Load Diff

View File

@@ -164,177 +164,175 @@
"checkInResultLevel3": "吉", "checkInResultLevel3": "吉",
"checkInResultLevel4": "大吉", "checkInResultLevel4": "大吉",
"checkInActivityTitle": "{} 於 {} 簽到,獲 {} 籤", "checkInActivityTitle": "{} 於 {} 簽到,獲 {} 籤",
"eventCalander": "Event Calander", "fortuneGraph": "运程谶",
"eventCalanderEmpty": "No events on that day.", "noFortuneData": "本月气数未显。",
"fortuneGraph": "Fortune Trend", "creatorHub": "著書閣",
"noFortuneData": "No fortune data available for this month.", "creatorHubDescription": "司理帖文、析策諸務。",
"creatorHub": "Creator Hub", "developerPortal": "匠造司",
"creatorHubDescription": "Manage posts, analytics, and more.", "developerPortalDescription": "築基於 Solar Network™。",
"developerPortal": "Developer Portal", "statusCreateHint": "何所思?可添近況。",
"developerPortalDescription": "Build with Solar Network™.", "statusCreate": "增境況",
"statusCreateHint": "What's on your mind? Add a status.", "statusUpdate": "更境況",
"statusCreate": "Add a Status", "statusLabel": "心境",
"statusUpdate": "Update Status", "statusAttitude": "意態",
"statusLabel": "Status", "attitudePositive": "昂揚",
"statusAttitude": "Attitude", "attitudeNeutral": "平瀾",
"attitudePositive": "Positive", "attitudeNegative": "消沉",
"attitudeNeutral": "Neutral", "statusInvisible": "隱跡",
"attitudeNegative": "Negative", "statusInvisibleDescription": "汝之形跡不現於人前。",
"statusInvisible": "Invisible", "statusNotDisturb": "勿擾",
"statusInvisibleDescription": "Your will be showing as offline to others.", "statusNotDisturbDescription": "急報皆止。",
"statusNotDisturb": "Do Not Disturb", "statusClearTime": "滌除於",
"statusNotDisturbDescription": "Push notification will be disabled.", "statusNoAutoClear": "不自滌",
"statusClearTime": "Cleared At", "online": "在朝",
"statusNoAutoClear": "Do not auto clear", "offline": "在野",
"online": "Online", "status": "心境",
"offline": "Offline", "statusActivityTitle": "{} 正於 {} {}",
"status": "Status", "statusActivityEndedTitle": "{} 正於 {} {} 迄於 {}",
"statusActivityTitle": "{} is {} {}", "appSettings": "應用設置",
"statusActivityEndedTitle": "{} is {} {} until {}", "accountSettings": "賬戶設置",
"appSettings": "App Settings", "settings": "設置",
"accountSettings": "Account Settings", "language": "言語",
"settings": "Settings", "accountLanguageHint": "此言語將行於電郵並推送告示。",
"language": "Language", "settingsDisplayLanguage": "顯示言語",
"accountLanguageHint": "This language will be used for email and push notifications.", "languageFollowSystem": "從系統之制",
"settingsDisplayLanguage": "Display Language", "postsCreatedCount": "貼文",
"languageFollowSystem": "Follow System", "stickerPacksCreatedCount": "貼札集",
"postsCreatedCount": "Posts", "stickersCreatedCount": "貼札",
"stickerPacksCreatedCount": "Sticker Packs", "upvoteReceived": "收擢",
"stickersCreatedCount": "Stickers", "downvoteReceived": "收黜",
"upvoteReceived": "Upvotes Recieved", "stickerPacks": "貼札集",
"downvoteReceived": "Downvotes Recieved", "createStickerPack": "始創一貼札集",
"stickerPacks": "Sticker Packs", "editStickerPack": "修訂貼札集",
"createStickerPack": "Create a Sticker Pack", "deleteStickerPack": "革去貼札集",
"editStickerPack": "Edit Sticker Pack", "deleteStickerPackHint": "決革去此貼札譜否?此令無可更赦。",
"deleteStickerPack": "Delete Sticker Pack", "stickerPackPrefix": "首綴",
"deleteStickerPackHint": "Are you sure to delete this sticker pack? This action cannot be undone.", "stickerPackPrefixHint": "此首綴將加於本帙諸貼札縮略名之前。",
"stickerPackPrefix": "Prefix", "stickers": "貼札",
"stickerPackPrefixHint": "The prefix will be added before each stickers' slug in this pack.", "createSticker": "始創一貼札",
"stickers": "Stickers", "editSticker": "修訂貼札",
"createSticker": "Create a Sticker", "deleteSticker": "革去貼札",
"editSticker": "Edit Sticker", "deleteStickerHint": "確欲革去此貼札乎?此勢無可逆轉。",
"deleteSticker": "Delete Sticker", "stickerImage": "圖像",
"deleteStickerHint": "Are you sure to delete this sticker? This action cannot be undone.", "stickerSlug": "籤碼",
"stickerImage": "Image", "stickerSlugHint": "此籤碼將聯題端以構貼札獨契。",
"stickerSlug": "Slug", "dataEmpty": "此間尚虛。",
"stickerSlugHint": "The slug will be combined with the prefix to form the sticker's unique identifier.", "pickFile": "擇一卷宗",
"dataEmpty": "Nothing's here yet.", "uploading": "上呈中",
"pickFile": "Pick a file", "uploadingProgress": "正呈 {} 於 {}",
"uploading": "Uploading", "uploadAll": "全盤呈上",
"uploadingProgress": "Uploading {} of {}", "stickerCopyPlaceholder": "摹寫虛位",
"uploadAll": "Upload All", "realmSelection": "擇一界域",
"stickerCopyPlaceholder": "Copy Placeholder", "individual": "獨詣",
"realmSelection": "Select a Realm", "firstPostBadgeName": "處女作",
"individual": "Individual", "firstPostBadgeDescription": "始發帖於 Solar Network",
"firstPostBadgeName": "First Post", "popularPostBadgeName": "熱帖",
"firstPostBadgeDescription": "Created your first post on Solar Network", "popularPostBadgeDescription": "君作得眾譽",
"popularPostBadgeName": "Popular Post", "viralPostBadgeName": "洛陽紙",
"popularPostBadgeDescription": "Your post received significant engagement from the community", "viralPostBadgeDescription": "君文傳抄殆遍",
"viralPostBadgeName": "Viral Post", "helpfulCommentBadgeName": "裨益論",
"viralPostBadgeDescription": "Your post went viral and reached a wide audience", "helpfulCommentBadgeDescription": "諸君以為斯論足以啟迪來者",
"helpfulCommentBadgeName": "Helpful Comment", "newcomerBadgeName": "新客",
"helpfulCommentBadgeDescription": "Your comment was marked as helpful by others", "newcomerBadgeDescription": "欣迎加入 Solar Network請開始遊覽結緣",
"newcomerBadgeName": "Newcomer", "contributorBadgeName": "資士",
"newcomerBadgeDescription": "Welcome to Solar Network! Start exploring and connecting", "contributorBadgeDescription": "勤力貢獻於 Solar Network 社群",
"contributorBadgeName": "Contributor", "expertBadgeName": "達人",
"contributorBadgeDescription": "Actively contributing to the Solar Network community", "expertBadgeDescription": "以專精與厚獻見稱於世",
"expertBadgeName": "Expert", "founderBadgeName": "肇基",
"expertBadgeDescription": "Recognized for your expertise and valuable contributions", "founderBadgeDescription": "屬 Solar Network 草創先驅",
"founderBadgeName": "Founder", "betaTesterBadgeName": "預勘師",
"founderBadgeDescription": "One of the earliest members of Solar Network", "betaTesterBadgeDescription": "嘗輔試 Solar Network 於初創之境",
"betaTesterBadgeName": "Beta Tester", "moderatorBadgeName": "持衡官",
"betaTesterBadgeDescription": "Helped test and improve Solar Network during beta", "moderatorBadgeDescription": "協理談筵規矩與秩序",
"moderatorBadgeName": "Moderator", "developerBadgeName": "開物者",
"moderatorBadgeDescription": "Helping maintain and moderate the community", "developerBadgeDescription": "獻力於 Solar Network 之功業",
"developerBadgeName": "Developer", "translatorBadgeName": "譯介士",
"developerBadgeDescription": "Contributing to Solar Network's development", "translatorBadgeDescription": "助譯 Solar Network 為諸邦之言",
"translatorBadgeName": "Translator", "wallet": "荷包",
"translatorBadgeDescription": "Helping translate Solar Network into different languages", "walletCurrencyPoints": "新太陽點",
"wallet": "Wallet",
"walletCurrencyPoints": "New Solar Points",
"walletCurrencyShortPoints": "NSP", "walletCurrencyShortPoints": "NSP",
"walletCurrencyGolds": "The Solar Dollars", "walletCurrencyGolds": "太陽幣",
"walletCurrencyShortGolds": "NSD", "walletCurrencyShortGolds": "NSD",
"retry": "Retry", "retry": "復行",
"creatorHubUnselectedHint": "Pick / create a publisher to get started.", "creatorHubUnselectedHint": "擇/創發布者以啟程。",
"relationships": "Relationships", "relationships": "交誼錄",
"addFriend": "Send a Friend Request", "addFriend": "送締交書",
"addFriendShort": "Add as Friend", "addFriendShort": "結為知交",
"addFriendHint": "Add a friend to your relationship list.", "addFriendHint": "添友於交誼錄中。",
"pendingRequest": "Pending", "pendingRequest": "待決",
"waitingRequest": "Waiting", "waitingRequest": "候音",
"relationshipStatusFriend": "Friend", "relationshipStatusFriend": "知交",
"relationshipStatusBlocked": "Blocked", "relationshipStatusBlocked": "已阻",
"blockUser": "Block User", "blockUser": "禁此戶",
"unblockUser": "Unblock User", "unblockUser": "解禁",
"friendRequestAccepted": "Accepted friend request from {}", "friendRequestAccepted": "已納 {} 締交書",
"friendRequestDeclined": "Declined friend request from {}", "friendRequestDeclined": "已謝 {} 締交書",
"requestExpiredIn": "Expired in {}", "requestExpiredIn": "效期 {}",
"friendSentRequest": "Sent Friend Requests", "friendSentRequest": "已送締交書",
"friendSentRequestEmpty": "No sent friend requests", "friendSentRequestEmpty": "未送締交書",
"friendSentRequestHint": { "friendSentRequestHint": {
"one": "{} friend request sent", "one": "已送{}締交書",
"other": "{} friend requests sent" "other": "已送{}締交書"
}, },
"levelingProgress": "Leveling Progress", "levelingProgress": "修為進境",
"levelingProgressExperience": "{} EXP", "levelingProgressExperience": "{} 修為",
"levelingProgressLevel": "Level {}", "levelingProgressLevel": " {}",
"fileUploadingProgress": "Uploading file #{}: {}%", "fileUploadingProgress": "傳輸第 {} 宗卷:{}%",
"removeChatMember": "Remove Chat Room Member", "removeChatMember": "逐出談席",
"removeChatMemberHint": "Are you sure to remove this member from the room?", "removeChatMemberHint": "確欲逐此客出談筵乎?",
"removeRealmMember": "Remove Realm Member", "removeRealmMember": "革出界域",
"removeRealmMemberHint": "Are you sure to remove this member from the realm?", "removeRealmMemberHint": "確欲革此員出界域乎?",
"memberRole": "Member Role", "memberRole": "成員權階",
"memberRoleHint": "Greater number has higher permission.", "memberRoleHint": "數高則權重。",
"memberRoleEdit": "Edit role for @{}", "memberRoleEdit": "修訂 @{} 之職",
"openLinkConfirm": "Leaving the Solar Network", "openLinkConfirm": "行將離 Solar Network",
"openLinkConfirmDescription": "You're going to leave the Solar Network and open the link ({}) in your browser. It is not related to Solar Network. Beware of phishing and scams.", "openLinkConfirmDescription": "君將辭 Solar Network 而啟 {} 之徑於瀏覽器,此徑與 Solar Network 無干,當心詐偽之害。",
"brokenLink": "Unable open link {}... It might be broken or missing uri parts...", "brokenLink": "未能開啟 {} 之鏈……或為殘鏈佚址……",
"copyToClipboard": "Copy to clipboard", "copyToClipboard": "抄入剪貼板",
"leaveChatRoom": "Leave Chat Room", "leaveChatRoom": "辭離談席",
"leaveChatRoomHint": "Are you sure to leave this chat room?", "leaveChatRoomHint": "君確要辭此談席否?",
"leaveRealm": "Leave Realm", "leaveRealm": "辭離界域",
"leaveRealmHint": "Are you sure to leave this realm?", "leaveRealmHint": "君確要辭此界域否?",
"walletNotFound": "Wallet not found", "walletNotFound": "未見荷包",
"walletCreateHint": "You don't have a wallet yet. Create one to start using the Solar Network eWallet.", "walletCreateHint": "君尚未備荷包,請創之以啟用 Solar Network 電子荷包。",
"walletCreate": "Create a Wallet", "walletCreate": "始創一荷包",
"settingsServerUrl": "Server URL", "settingsServerUrl": "伺服器網址",
"settingsApplied": "The settings has been applied.", "settingsApplied": "諸設定已施畢。",
"notifications": "Notifications", "notifications": "報聞",
"posts": "Posts", "posts": "貼文",
"settingsBackgroundImage": "Background Image", "settingsBackgroundImage": "底圖",
"settingsBackgroundImageClear": "Clear Background Image", "settingsBackgroundImageClear": "清底圖",
"settingsBackgroundGenerateColor": "Generate color scheme from Bacground Image", "settingsBackgroundGenerateColor": "取背景畫配色",
"messageNone": "No content to display", "messageNone": "無文可示",
"unreadMessages": { "unreadMessages": {
"one": "{} unread message", "one": "{} 條未讀訊",
"other": "{} unread messages" "other": "{} 條未讀訊"
}, },
"chatBreakNone": "None", "chatBreakNone": "",
"settingsRealmCompactView": "Compact Realm View", "settingsRealmCompactView": "緊湊界域視圖",
"settingsMixedFeed": "Mixed Feed", "settingsMixedFeed": "混流饋訊",
"settingsAutoTranslate": "Auto Translate", "settingsAutoTranslate": "自轉譯",
"settingsHideBottomNav": "Hide Bottom Navigation", "settingsHideBottomNav": "隱底航",
"settingsSoundEffects": "Sound Effects", "settingsSoundEffects": "音效",
"settingsAprilFoolFeatures": "April Fool Features", "settingsAprilFoolFeatures": "謔辰妙能",
"settingsEnterToSend": "Enter to Send", "settingsEnterToSend": "叩回車鍵即送",
"settingsTransparentAppBar": "Transparent App Bar", "settingsTransparentAppBar": "透光應用欄",
"settingsCustomFonts": "Custom Fonts", "settingsCustomFonts": "自定字體",
"settingsCustomFontsHint": "Custom fonts will be used for all text in the app. Make sure it is installed on your device.", "settingsCustomFontsHint": "此字體將通施於應用內文,請先確安於裝置。",
"settingsColorScheme": "Color Scheme", "settingsColorScheme": "設置色彩方案",
"postTitle": "Title", "postTitle": "標題",
"postDescription": "Description", "postDescription": "",
"call": "Call", "call": "传信",
"done": "Done", "done": "已成矣",
"loginResetPasswordSent": "Password reset link sent, please check your email inbox.", "loginResetPasswordSent": "密码重置之链已送至,恳愿于汝之邮址查阅。",
"accountDeletion": "Delete Account", "accountDeletion": "除去此账户",
"accountDeletionHint": "Are you sure to delete your account? If you confirmed, we will send an confirmation email to your primary email address, you can continue the deletion process by follow the insturctions in the email.", "accountDeletionHint": "汝已存心除此账户?如已一意,信将至汝邮址,随信中之言续此行。",
"accountDeletionSent": "Account deletion confirmation email sent, please check your email inbox.", "accountDeletionSent": "除此账户之信已送至,请于汝之邮址查阅。",
"accountSecurityTitle": "Security", "accountSecurityTitle": "Security",
"accountDangerZoneTitle": "Danger Zone", "accountDangerZoneTitle": "Danger Zone",
"accountPassword": "Password", "accountPassword": "密碼",
"accountPasswordDescription": "Change your account password", "accountPasswordDescription": "變更账户密碼",
"accountPasswordChange": "Change Password", "accountPasswordChange": "變更密碼",
"accountPasswordChangeSent": "Password reset link sent, please check your email inbox.", "accountPasswordChangeSent": "Password reset link sent, please check your email inbox.",
"accountPasswordChangeDescription": "We will send an email to your primary email address to reset your password.", "accountPasswordChangeDescription": "We will send an email to your primary email address to reset your password.",
"accountAuthFactor": "Auth factors", "accountAuthFactor": "Auth factors",
@@ -346,14 +344,14 @@
"unauthorizedHint": "You're not signed in or session expired, please sign in again.", "unauthorizedHint": "You're not signed in or session expired, please sign in again.",
"publisherBelongsTo": "Belongs to {}", "publisherBelongsTo": "Belongs to {}",
"postContent": "Content", "postContent": "Content",
"postSettings": "Settings", "postSettings": "設定",
"postPublisherUnselected": "Publisher Unspecified", "postPublisherUnselected": "发信者未定",
"postVisibilityPublic": "Public", "postVisibilityPublic": "众以见",
"postVisibilityFriends": "Friends Only", "postVisibilityFriends": "唯友以见",
"postVisibilityUnlisted": "Unlisted", "postVisibilityUnlisted": "非指之人以见",
"postVisibilityPrivate": "Private", "postVisibilityPrivate": "唯己可见",
"postTruncated": "Content truncated, tap to view full post", "postTruncated": "Content truncated, tap to view full post",
"copyMessage": "Copy Message", "copyMessage": "抄文",
"authFactor": "Authentication Factor", "authFactor": "Authentication Factor",
"authFactorDelete": "Delete the Factor", "authFactorDelete": "Delete the Factor",
"authFactorDeleteHint": "Are you sure you want to delete this authentication factor? This action cannot be undone.", "authFactorDeleteHint": "Are you sure you want to delete this authentication factor? This action cannot be undone.",
@@ -366,8 +364,8 @@
"authFactorSecretHint": "Create an secret for this factor.", "authFactorSecretHint": "Create an secret for this factor.",
"authFactorQrCodeScan": "Scan this QR code with your authenticator app to set up TOTP authentication", "authFactorQrCodeScan": "Scan this QR code with your authenticator app to set up TOTP authentication",
"authFactorNoQrCode": "No QR code available for this authentication factor", "authFactorNoQrCode": "No QR code available for this authentication factor",
"cancel": "Cancel", "cancel": "",
"confirm": "Confirm", "confirm": "",
"authFactorAdditional": "One more step", "authFactorAdditional": "One more step",
"authFactorHint": "Contact method hint", "authFactorHint": "Contact method hint",
"authFactorHintHelper": "You need provide a part of your contact method and we will send the verification code to that contact method if it matched our records", "authFactorHintHelper": "You need provide a part of your contact method and we will send the verification code to that contact method if it matched our records",
@@ -387,12 +385,12 @@
"authDeviceSwipeEditHint": "Swipe left to edit label", "authDeviceSwipeEditHint": "Swipe left to edit label",
"authDeviceSwipeLogoutHint": "Swipe right to logout device", "authDeviceSwipeLogoutHint": "Swipe right to logout device",
"typingHint": { "typingHint": {
"one": "{} is typing...", "one": "{} 正在执笔……",
"other": "{} are typing..." "other": "{} 正在执笔……"
}, },
"settingsAppearance": "Appearance", "settingsAppearance": "Appearance",
"settingsServer": "Server", "settingsServer": "伺服器",
"settingsBehavior": "Behavior", "settingsBehavior": "",
"settingsDesktop": "Desktop", "settingsDesktop": "Desktop",
"settingsKeyboardShortcuts": "Keyboard Shortcuts", "settingsKeyboardShortcuts": "Keyboard Shortcuts",
"settingsEnterToSendDesktopHint": "Press Enter to send messages, use Shift+Enter for new line.", "settingsEnterToSendDesktopHint": "Press Enter to send messages, use Shift+Enter for new line.",
@@ -752,21 +750,6 @@
"rename": "Rename", "rename": "Rename",
"markAsSensitive": "Mark as Sensitive", "markAsSensitive": "Mark as Sensitive",
"fileName": "File name", "fileName": "File name",
"sensitiveCategories": {
"language": "Language",
"sexualContent": "Sexual Content",
"violence": "Violence",
"profanity": "Profanity",
"hateSpeech": "Hate Speech",
"racism": "Racism",
"adultContent": "Adult Content",
"drugAbuse": "Drug Abuse",
"alcoholAbuse": "Alcohol Abuse",
"gambling": "Gambling",
"selfHarm": "Self-harm",
"childAbuse": "Child Abuse",
"other": "Other"
},
"poll": "Poll", "poll": "Poll",
"pollsRecent": "Recent Polls", "pollsRecent": "Recent Polls",
"pollCreateNew": "Create New", "pollCreateNew": "Create New",
@@ -1075,5 +1058,421 @@
"deleteRecycledFiles": "Delete Recycled Files", "deleteRecycledFiles": "Delete Recycled Files",
"recycledFilesDeleted": "Recycled files deleted successfully", "recycledFilesDeleted": "Recycled files deleted successfully",
"failedToDeleteRecycledFiles": "Failed to delete recycled files", "failedToDeleteRecycledFiles": "Failed to delete recycled files",
"upload": "Upload" "upload": "Upload",
"deleteMessage": "Delete Message",
"deleteMessageConfirmation": "Are you sure you want to delete this message?",
"customReaction": "Custom Reaction",
"customReactions": "Custom Reactions",
"stickerPlaceholder": "Sticker Placeholder",
"reactionAttitude": "Reaction Attitude",
"addReaction": "Add Reaction",
"eventCalendar": "Event Calendar",
"eventCalendarEmpty": "No events on that day.",
"walletStats": "Wallet Statistics",
"totalTransactions": "Total Transactions",
"totalOrders": "Total Orders",
"totalIncome": "Total Income",
"totalOutgoing": "Total Outgoing",
"netBalance": "Net Balance",
"messageUpdateLinks": "Server generated links previews",
"messageUpdateEdited": "Edited a message",
"settingsCardBackgroundOpacity": "Card Background Opacity",
"settingsThemeMode": "Theme Mode",
"settingsThemeModeSystem": "System",
"settingsThemeModeLight": "Light",
"settingsThemeModeDark": "Dark",
"enterPin": "Enter your PIN code",
"chatReplyingTo": "Replying to {}",
"chatForwarding": "Forwarding message",
"chatEditing": "Editing message",
"chatNoContent": "No content",
"sensitiveCategories": {
"language": "Language",
"sexualContent": "Sexual Content",
"violence": "Violence",
"profanity": "Profanity",
"hateSpeech": "Hate Speech",
"racism": "Racism",
"adultContent": "Adult Content",
"drugAbuse": "Drug Abuse",
"alcoholAbuse": "Alcohol Abuse",
"gambling": "Gambling",
"selfHarm": "Self-harm",
"childAbuse": "Child Abuse",
"other": "Other"
},
"Searching...": "Searching...",
"searchError": "Search failed. Please try again.",
"tryDifferentKeywords": "Try different keywords or remove search filters",
"settingsWindowOpacity": "Window Opacity",
"messageContent": "Message Content",
"updateAvailable": "Update available",
"noChangelogProvided": "No changelog provided.",
"useSecondarySourceForDownload": "Use secondary source for download",
"installUpdate": "Install update",
"openReleasePage": "Open release page",
"postCompose": "Compose Post",
"postPublish": "Publish Post",
"restoreDraftTitle": "Restore Draft",
"restoreDraftMessage": "A draft was found. Do you want to restore it?",
"draft": "Draft",
"purchaseGift": "Purchase Gift",
"selectRecipient": "Select Recipient",
"changeRecipient": "Change Recipient",
"addMessage": "Add Message",
"skipRecipient": "Skip Recipient",
"giftSubscriptions": "Gift Subscriptions",
"purchaseAGift": "Purchase a Gift",
"redeemAGift": "Redeem a Gift",
"giftHistory": "Gift History",
"sentGifts": "Sent Gifts",
"receivedGifts": "Received Gifts",
"noSentGifts": "No sent gifts",
"noReceivedGifts": "No received gifts",
"stellarGift": "Stellar Gift",
"novaGift": "Nova Gift",
"supernovaGift": "Supernova Gift",
"sameAsMembership": "Same as membership",
"enterGiftCodeToRedeem": "Enter gift code to redeem",
"enterGiftCode": "Enter gift code",
"giftPurchased": "Gift Purchased!",
"shareCodeWithRecipient": "Share this code with the recipient to redeem the gift.",
"openGiftAnyoneCanRedeem": "This is an open gift that anyone can redeem.",
"ok": "OK",
"selectedRecipient": "Selected recipient",
"noRecipientSelected": "No recipient selected",
"thisWillBeAnOpenGift": "This will be an open gift",
"personalMessage": "Personal Message",
"addPersonalMessageForRecipient": "Add a personal message for the recipient",
"giftStatusCreated": "Created",
"giftStatusSent": "Sent",
"giftStatusRedeemed": "Redeemed",
"giftStatusCancelled": "Cancelled",
"giftStatusExpired": "Expired",
"giftStatusUnknown": "Unknown",
"giftCodeCopiedToClipboard": "Gift code copied to clipboard",
"codeLabel": "Code: ",
"subscriptionLabel": "Subscription: ",
"toLabel": "To: ",
"fromLabel": "From: ",
"messageLabel": "Message: ",
"giftRedeemed": "Gift Redeemed!",
"giftRedeemedSuccessfully": "You have successfully redeemed the gift. Your new subscription is now active.",
"cancelGift": "Cancel Gift",
"cancelGiftConfirm": "Are you sure you want to cancel this gift? This action cannot be undone.",
"giftCancelledSuccessfully": "Gift cancelled successfully",
"createFund": "Create Fund",
"fundAmount": "Fund Amount",
"enterAmount": "Enter Amount",
"selectCurrency": "Select Currency",
"splitType": "Split Type",
"evenSplit": "Even Split",
"equalAmountEach": "Equal amount for each recipient",
"randomSplit": "Random Split",
"randomAmountEach": "Random amount for each recipient",
"recipientCount": "Recipient Count",
"numberOfRecipients": "Number of Recipients",
"addPersonalMessageForRecipients": "Add a personal message for recipients",
"invalidAmount": "Invalid amount",
"invalidRecipientCount": "Invalid recipient count",
"fundOverview": "Fund Overview",
"totalFundsSent": "Total Funds Sent",
"totalFundsReceived": "Total Funds Received",
"transactions": "Transactions",
"myFunds": "My Funds",
"availableFunds": "Available Funds",
"fundStatusCreated": "Created",
"fundStatusPartial": "Partially Claimed",
"fundStatusCompleted": "Fully Claimed",
"fundStatusExpired": "Expired",
"fundStatusUnknown": "Unknown",
"recipients": "Recipients",
"fundClaimedSuccessfully": "Fund claimed successfully!",
"claim": "Claim",
"noFundsCreated": "No funds created yet",
"createYourFirstFund": "Create your first fund to get started",
"noAvailableFunds": "No available funds",
"fundsWillAppearHere": "Funds you can claim will appear here",
"fundCreatedSuccessfully": "Fund created successfully!",
"selectRecipients": "Select Recipients",
"noRecipientsSelected": "No recipients selected",
"selectRecipientsToSendFund": "Select recipients to send the fund to",
"addRecipient": "Add Recipient",
"addMoreRecipients": "Add More Recipients",
"transactionDetails": "Transaction Details",
"remarks": "Remarks",
"payer": "Payer",
"payee": "Payee",
"transactionType": "Transaction Type",
"transfer": "Transfer",
"payment": "Payment",
"systemWallet": "System Wallet",
"date": "Date",
"createTransfer": "Create Transfer",
"transferAmount": "Transfer Amount",
"selectPayee": "Select Payee",
"selectedPayee": "Selected Payee",
"noPayeeSelected": "No payee selected",
"selectPayeeToTransfer": "Select payee to transfer to",
"addRemark": "Add Remark",
"transferRemark": "Transfer Remark",
"addRemarkForTransfer": "Add remark for transfer",
"enterPinToConfirmTransfer": "Enter your 6-digit PIN to confirm transfer",
"transferCreatedSuccessfully": "Transfer created successfully!",
"postUpdate": "Update",
"fileMetadata": "File Metadata",
"resend": "Resend",
"fileInfoTitle": "File Information",
"download": "Download",
"info": "Info",
"noStickers": "No Stickers",
"noStickersInPack": "This pack does not contains stickers",
"noStickerPacks": "No Sticker Packs",
"refresh": "Refresh",
"spoiler": "Spoiler",
"activityHeatmap": "Activity Heatmap",
"custom": "Custom",
"usernameColor": "Username Color",
"colorType": "Color Type",
"plain": "Plain",
"gradient": "Gradient",
"colorValue": "Color Value",
"gradientDirection": "Gradient Direction",
"gradientDirectionToRight": "To Right",
"gradientDirectionToLeft": "To Left",
"gradientDirectionToBottom": "To Bottom",
"gradientDirectionToTop": "To Top",
"gradientDirectionToBottomRight": "To Bottom Right",
"gradientDirectionToBottomLeft": "To Bottom Left",
"gradientDirectionToTopRight": "To Top Right",
"gradientDirectionToTopLeft": "To Top Left",
"gradientColors": "Gradient Colors",
"color": "Color",
"addColor": "Add Color",
"availableWithYourPlan": "Available with your plan",
"upgradeRequired": "Upgrade required",
"settingsDisableAnimation": "Disable Animation",
"addTag": "Add Tag",
"accountConnectionProviderSpotify": "Spotify",
"accountConnectionProviderSteam": "Steam",
"timezoneNotFound": "Time zone not found",
"awardPoints": "Awarded {} points",
"postFeaturedOn": "Post featured on {}",
"messageSentAt": "Sent at {}",
"myTickets": "My Tickets",
"drawHistory": "Draw History",
"lottery": "Lottery",
"noLotteryTickets": "No lottery tickets yet",
"buyYourFirstTicket": "Buy your first lottery ticket to get started!",
"buyTicket": "Buy Ticket",
"ticketNumbers": "Numbers: {}, Special: {}",
"cost": "Cost",
"multiplier": "Multiplier",
"prizeWon": "Prize Won",
"pending": "Pending",
"drawn": "Drawn",
"won": "Won",
"lost": "Lost",
"noDrawHistory": "No draw history yet",
"buyLotteryTicket": "Buy Lottery Ticket",
"selectNumbers": "Select Numbers",
"select5UniqueNumbers": "Select 5 unique numbers",
"selectSpecialNumber": "Select Special Number",
"selectMultiplier": "Select Multiplier",
"baseCost": "Base Cost",
"totalCost": "Total Cost",
"prizeStructure": "Prize Structure",
"enterPinToConfirmPurchase": "Enter your PIN to confirm purchase",
"ticketPurchasedSuccessfully": "Ticket purchased successfully!",
"winningNumbers": "Winning Numbers",
"specialNumber": "Special Number",
"totalTickets": "Total Tickets",
"totalWinners": "Total Winners",
"prizePool": "Prize Pool",
"enterPinToConfirmPayment": "Enter your PIN code to confirm payment",
"purchase": "Purchase",
"multiplierLabel": "Multiplier",
"specialOnly": "Special Only",
"matches": "Matches",
"thoughtDefaultTopic": "Reflection",
"thoughtAiName": "SN-chan",
"thoughtUserName": "You",
"thoughtStreamingHint": "Sn-chan is thinking...",
"thoughtInputHint": "Ask sn-chan anything...",
"thoughtNewConversation": "Start New Conversation",
"thoughtParseError": "Failed to parse AI response",
"thoughtFunctionCall": "Use {}",
"aiThought": "AI Thought",
"aiThoughtTitle": "Let sn-chan think",
"postReferenceUnavailable": "Referenced post is unavailable",
"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",
"customReactionHint": "Custom Reaction allow you to use user uploaded stickers as the symbol of the reaction for the post. Exclusive for Stellar Program members.",
"publicationSites": "Publication Sites",
"uploadTasks": "Upload Tasks",
"thoughtFunctionCallBegin": "Calling tool {}",
"thoughtFunctionCallFinish": "{} responded",
"thoughtUnpaidHint": "Thinking unavaiable due to unpaid orders",
"more": "More",
"collapse": "Collapse",
"pollConfirmDiscard": "Are you sure you want to leave? All the poll data you're editing will not be saved.",
"discard": "Discard",
"fund": "Fund",
"fundsRecent": "Recent Funds",
"fundCreateNew": "Create New",
"fundCreateNewHint": "Create a new fund for your message. Select recipients and amount.",
"amountOfSplits": "Amount of Splits",
"enterNumberOfSplits": "Enter Splits Amount",
"orCreateWith": "Or\ncreate with",
"unindexedFiles": "Unindexed files",
"folder": "Folder",
"clearCompleted": "Clear Completed",
"uploadSuccess": "Upload successful!",
"wouldYouLikeToViewFile": "Would you like to view the file?",
"contentCantEmpty": "Content cannot be empty",
"features": "Features",
"unnamed": "Unnamed",
"fundEnvelopeLoadFailed": "Failed to load fund envelope",
"fundEnvelope": "Fund Envelope",
"fundEnvelopeRemaining": "Remaining: {} {}",
"fundEnvelopeSplit": "Split: {}",
"fundEnvelopeSplitEvenly": "Evenly",
"fundEnvelopeSplitRandomly": "Randomly",
"fundEnvelopeClaimSuccess": "Fund claimed successfully!",
"fundEnvelopeStatusCreated": "Created",
"fundEnvelopeStatusPartial": "Partially Claimed",
"fundEnvelopeStatusCompleted": "Fully Claimed",
"fundEnvelopeStatusExpired": "Expired",
"fundEnvelopeStatusUnknown": "Unknown",
"fundEnvelopeRecipients": "Recipients ({}/{} claimed)",
"fundEnvelopeExpiredDaysAgo": {
"one": "Expired {} day ago",
"other": "Expired {} days ago"
},
"fundEnvelopeExpiresSoon": "Expires soon",
"fundEnvelopeExpiresInHours": {
"one": "Expires in {} hour",
"other": "Expires in {} hours"
},
"fundEnvelopeExpiresInDays": {
"one": "Expires in {} day",
"other": "Expires in {} days"
},
"fundEnvelopeRemainingWithSplits": "{} {} / {} splits",
"fundEnvelopeUnknownUser": "Unknown User",
"deleteSite": "Delete Site",
"deleteSiteConfirm": "Are you sure you want to delete this site?",
"siteDeletedSuccess": "Site deleted successfully",
"siteSlug": "Slug",
"siteSlugHint": "my-site",
"siteSlugRequired": "Please enter a slug",
"siteSlugInvalid": "Slug can only contain lowercase letters, numbers, and dashes",
"siteName": "Site Name",
"siteNameHint": "My Publication Site",
"siteNameRequired": "Please enter a site name",
"siteMode": "Mode",
"siteModeFullyManaged": "Fully Managed",
"siteModeSelfManaged": "Self-Managed",
"editPublicationSite": "Edit Publication Site",
"deletePublicationSite": "Delete Publication Site",
"publicationSiteSavedSuccess": "Publication site saved successfully",
"publicationSiteDeleteConfirm": "Are you sure you want to delete this publication site? This action cannot be undone.",
"publicationSiteDeletedSuccess": "Publication site deleted successfully",
"newPublicationSite": "New Publication Site",
"siteDetails": "Site Details",
"siteInformation": "Site Information",
"siteDomain": "Domain",
"siteCreated": "Created",
"siteUpdated": "Updated",
"failedToLoadSite": "Failed to load site",
"sitePages": "Pages",
"noPagesYet": "No pages yet",
"createFirstPage": "Create your first page to get started",
"failedToLoadPages": "Failed to load pages",
"fileManagement": "File Management",
"siteFiles": "Files",
"siteFolder": "Folder",
"siteRoot": "Root",
"noFilesUploadedYet": "No files uploaded yet",
"uploadFirstFile": "Upload your first file to get started",
"failedToLoadFiles": "Failed to load files",
"noFilesFoundInFolder": "No files found in the selected folder",
"fileActions": "File Actions",
"purgeFiles": "Purge Files",
"purgeFilesDescription": "Remove all uploaded files from the site",
"deploySite": "Deploy Site",
"deploySiteDescription": "Upload and deploy a new version from ZIP archive",
"confirmPurge": "Confirm Purge",
"purgeFilesConfirm": "This will permanently delete all files uploaded to this site. This action cannot be undone. Are you sure you want to continue?",
"purgeAllFiles": "Purge All Files",
"allFilesPurgedSuccess": "All files purged successfully",
"failedToPurgeFiles": "Failed to purge files: {}",
"siteDeployedSuccess": "Site deployed successfully",
"failedToDeploySite": "Failed to deploy site: {}",
"createPage": "Create Page",
"editPage": "Edit Page",
"pageType": "Page Type",
"htmlPage": "HTML Page",
"redirectPage": "Redirect Page",
"pageTypeRequired": "Please select a page type",
"pagePath": "Page Path",
"pagePathHint": "/about, /contact, etc.",
"pagePathRequired": "Please enter a page path",
"pagePathInvalid": "Page path can only contain letters, numbers, hyphens, underscores, and slashes",
"pagePathMustStartWithSlash": "Page path must start with /",
"pagePathNoConsecutiveSlashes": "Page path cannot have consecutive slashes",
"pageTitle": "Page Title",
"pageTitleHint": "About Us, Contact, etc.",
"pageTitleRequired": "Please enter a page title",
"pageContentHtml": "Page Content (HTML)",
"pageContentHint": "<h1>Hello World</h1><p>This is my page content...</p>",
"pageContentRequired": "Please enter HTML content for the page",
"redirectTarget": "Redirect Target",
"redirectTargetHint": "/new-page, https://example.com, etc.",
"redirectTargetRequired": "Please enter a redirect target",
"redirectTargetInvalid": "Target must be a relative path (/) or absolute URL (http/https)",
"deletePage": "Delete Page",
"deletePageConfirm": "Are you sure you want to delete this page?",
"savePage": "Save Page",
"pageCreatedSuccess": "Page created successfully",
"pageUpdatedSuccess": "Page updated successfully",
"pageDeletedSuccess": "Page deleted successfully",
"uploadFiles": "Upload Files",
"uploadPath": "Upload Path",
"uploadPathHint": "/ (root) or /assets/images/",
"uploadPathRequired": "Please enter an upload path",
"uploadPathMustStartWithSlash": "Path must start with /",
"uploadPathNoSpaces": "Path cannot contain spaces",
"uploadPathNoConsecutiveSlashes": "Path cannot have consecutive slashes",
"percentCompleted": "{}% completed",
"filesToUpload": "{} files to upload",
"fileSizeKb": "Size: {} KB",
"uploadingEllipsis": "Uploading...",
"uploadFilesCount": {
"one": "Upload {} File",
"other": "Upload {} Files"
},
"allUploadsCompleted": "All uploads completed",
"someUploadsFailed": "Some uploads failed",
"uploadingInProgress": "Uploading in progress",
"readyToUpload": "Ready to upload",
"allFilesUploadedSuccess": "All files uploaded successfully",
"lotteryLastNumberSpecial": "The last selected number will be your special number.",
"lotteryMultiplierRequired": "Please enter a multiplier",
"lotteryMultiplierRange": "Multiplier must be between 1 and 10",
"dropToShare": "Drop to share"
} }

View File

@@ -164,8 +164,6 @@
"checkInResultLevel3": "好運", "checkInResultLevel3": "好運",
"checkInResultLevel4": "最佳運氣", "checkInResultLevel4": "最佳運氣",
"checkInActivityTitle": "{} 在 {} 簽到並獲得了 {}", "checkInActivityTitle": "{} 在 {} 簽到並獲得了 {}",
"eventCalander": "活動日曆",
"eventCalanderEmpty": "該日無活動。",
"fortuneGraph": "時運趨勢", "fortuneGraph": "時運趨勢",
"noFortuneData": "本月沒有時運數據。", "noFortuneData": "本月沒有時運數據。",
"creatorHub": "創作者中心", "creatorHub": "創作者中心",
@@ -752,21 +750,6 @@
"rename": "重命名", "rename": "重命名",
"markAsSensitive": "標記為敏感", "markAsSensitive": "標記為敏感",
"fileName": "文件名", "fileName": "文件名",
"sensitiveCategories": {
"language": "語言",
"sexualContent": "色情內容",
"violence": "暴力",
"profanity": "褻瀆",
"hateSpeech": "仇恨言論",
"racism": "種族主義",
"adultContent": "成人內容",
"drugAbuse": "藥物濫用",
"alcoholAbuse": "酗酒",
"gambling": "賭博",
"selfHarm": "自殘",
"childAbuse": "虐待兒童",
"other": "其他"
},
"poll": "投票", "poll": "投票",
"pollsRecent": "最近投票", "pollsRecent": "最近投票",
"pollCreateNew": "創建新投票", "pollCreateNew": "創建新投票",
@@ -1076,6 +1059,420 @@
"recycledFilesDeleted": "已回收檔案刪除成功", "recycledFilesDeleted": "已回收檔案刪除成功",
"failedToDeleteRecycledFiles": "已回收檔案刪除失敗", "failedToDeleteRecycledFiles": "已回收檔案刪除失敗",
"upload": "上傳", "upload": "上傳",
"deleteMessage": "刪除訊息",
"deleteMessageConfirmation": "確定要刪除此郵件嗎?",
"customReaction": "自訂反應",
"customReactions": "自訂反應",
"stickerPlaceholder": "貼紙佔位符",
"reactionAttitude": "反應態度",
"addReaction": "添加反應",
"eventCalendar": "事件日曆",
"eventCalendarEmpty": "該日無活動。",
"walletStats": "錢包統計",
"totalTransactions": "交易總數",
"totalOrders": "訂單總數",
"totalIncome": "總收入",
"totalOutgoing": "總支出",
"netBalance": "淨餘額",
"messageUpdateLinks": "伺服器產生的連結預覽",
"messageUpdateEdited": "編輯一則訊息",
"settingsCardBackgroundOpacity": "卡片背景不透明度",
"settingsThemeMode": "主題模式",
"settingsThemeModeSystem": "跟隨系統",
"settingsThemeModeLight": "淺色",
"settingsThemeModeDark": "暗色",
"enterPin": "請輸入您的PIN碼",
"chatReplyingTo": "回復給 {}",
"chatForwarding": "正在轉傳訊息",
"chatEditing": "訊息編輯中",
"chatNoContent": "內容為空",
"sensitiveCategories": {
"language": "語言",
"sexualContent": "色情內容",
"violence": "暴力",
"profanity": "褻瀆",
"hateSpeech": "仇恨言論",
"racism": "種族主義",
"adultContent": "成人內容",
"drugAbuse": "藥物濫用",
"alcoholAbuse": "酗酒",
"gambling": "賭博",
"selfHarm": "自殘",
"childAbuse": "虐待兒童",
"other": "其他"
},
"Searching...": "檢索中……",
"searchError": "付款失敗,請重試。",
"tryDifferentKeywords": "嘗試不同的關鍵字或刪除搜尋過濾器",
"settingsWindowOpacity": "視窗不透明度",
"messageContent": "訊息內容",
"updateAvailable": "更新可用",
"noChangelogProvided": "無更新紀錄。",
"useSecondarySourceForDownload": "使用次要來源下載",
"installUpdate": "安装更新",
"openReleasePage": "開啟發行頁面",
"postCompose": "撰寫帖子", "postCompose": "撰寫帖子",
"postPublish": "發佈帖子" "postPublish": "發佈帖子",
"restoreDraftTitle": "還原草稿",
"restoreDraftMessage": "發現了一個草稿。你想要恢復它嗎?",
"draft": "草稿",
"purchaseGift": "充值有禮",
"selectRecipient": "選擇收件者",
"changeRecipient": "修改款件人",
"addMessage": "添加消息",
"skipRecipient": "跳過款件人",
"giftSubscriptions": "贈送訂閱",
"purchaseAGift": "充值有禮",
"redeemAGift": "兌換禮物",
"giftHistory": "禮物記錄",
"sentGifts": "發送禮物",
"receivedGifts": "接收禮物",
"noSentGifts": "沒有送過禮物",
"noReceivedGifts": "没有收到过礼物",
"stellarGift": "恆星禮物",
"novaGift": "新星禮物",
"supernovaGift": "Supernova Gift",
"sameAsMembership": "Same as membership",
"enterGiftCodeToRedeem": "Enter gift code to redeem",
"enterGiftCode": "Enter gift code",
"giftPurchased": "Gift Purchased!",
"shareCodeWithRecipient": "Share this code with the recipient to redeem the gift.",
"openGiftAnyoneCanRedeem": "This is an open gift that anyone can redeem.",
"ok": "OK",
"selectedRecipient": "Selected recipient",
"noRecipientSelected": "No recipient selected",
"thisWillBeAnOpenGift": "This will be an open gift",
"personalMessage": "Personal Message",
"addPersonalMessageForRecipient": "Add a personal message for the recipient",
"giftStatusCreated": "Created",
"giftStatusSent": "Sent",
"giftStatusRedeemed": "Redeemed",
"giftStatusCancelled": "Cancelled",
"giftStatusExpired": "Expired",
"giftStatusUnknown": "Unknown",
"giftCodeCopiedToClipboard": "Gift code copied to clipboard",
"codeLabel": "Code: ",
"subscriptionLabel": "Subscription: ",
"toLabel": "To: ",
"fromLabel": "From: ",
"messageLabel": "Message: ",
"giftRedeemed": "Gift Redeemed!",
"giftRedeemedSuccessfully": "You have successfully redeemed the gift. Your new subscription is now active.",
"cancelGift": "Cancel Gift",
"cancelGiftConfirm": "Are you sure you want to cancel this gift? This action cannot be undone.",
"giftCancelledSuccessfully": "Gift cancelled successfully",
"createFund": "Create Fund",
"fundAmount": "Fund Amount",
"enterAmount": "Enter Amount",
"selectCurrency": "Select Currency",
"splitType": "Split Type",
"evenSplit": "Even Split",
"equalAmountEach": "Equal amount for each recipient",
"randomSplit": "Random Split",
"randomAmountEach": "Random amount for each recipient",
"recipientCount": "Recipient Count",
"numberOfRecipients": "Number of Recipients",
"addPersonalMessageForRecipients": "Add a personal message for recipients",
"invalidAmount": "Invalid amount",
"invalidRecipientCount": "Invalid recipient count",
"fundOverview": "Fund Overview",
"totalFundsSent": "Total Funds Sent",
"totalFundsReceived": "Total Funds Received",
"transactions": "Transactions",
"myFunds": "My Funds",
"availableFunds": "Available Funds",
"fundStatusCreated": "Created",
"fundStatusPartial": "Partially Claimed",
"fundStatusCompleted": "Fully Claimed",
"fundStatusExpired": "Expired",
"fundStatusUnknown": "Unknown",
"recipients": "Recipients",
"fundClaimedSuccessfully": "Fund claimed successfully!",
"claim": "Claim",
"noFundsCreated": "No funds created yet",
"createYourFirstFund": "Create your first fund to get started",
"noAvailableFunds": "No available funds",
"fundsWillAppearHere": "Funds you can claim will appear here",
"fundCreatedSuccessfully": "Fund created successfully!",
"selectRecipients": "Select Recipients",
"noRecipientsSelected": "No recipients selected",
"selectRecipientsToSendFund": "Select recipients to send the fund to",
"addRecipient": "Add Recipient",
"addMoreRecipients": "Add More Recipients",
"transactionDetails": "Transaction Details",
"remarks": "Remarks",
"payer": "Payer",
"payee": "Payee",
"transactionType": "Transaction Type",
"transfer": "Transfer",
"payment": "Payment",
"systemWallet": "System Wallet",
"date": "Date",
"createTransfer": "Create Transfer",
"transferAmount": "Transfer Amount",
"selectPayee": "Select Payee",
"selectedPayee": "Selected Payee",
"noPayeeSelected": "No payee selected",
"selectPayeeToTransfer": "Select payee to transfer to",
"addRemark": "Add Remark",
"transferRemark": "Transfer Remark",
"addRemarkForTransfer": "Add remark for transfer",
"enterPinToConfirmTransfer": "Enter your 6-digit PIN to confirm transfer",
"transferCreatedSuccessfully": "Transfer created successfully!",
"postUpdate": "Update",
"fileMetadata": "File Metadata",
"resend": "Resend",
"fileInfoTitle": "File Information",
"download": "Download",
"info": "Info",
"noStickers": "No Stickers",
"noStickersInPack": "This pack does not contains stickers",
"noStickerPacks": "No Sticker Packs",
"refresh": "Refresh",
"spoiler": "Spoiler",
"activityHeatmap": "Activity Heatmap",
"custom": "Custom",
"usernameColor": "Username Color",
"colorType": "Color Type",
"plain": "Plain",
"gradient": "Gradient",
"colorValue": "Color Value",
"gradientDirection": "Gradient Direction",
"gradientDirectionToRight": "To Right",
"gradientDirectionToLeft": "To Left",
"gradientDirectionToBottom": "To Bottom",
"gradientDirectionToTop": "To Top",
"gradientDirectionToBottomRight": "To Bottom Right",
"gradientDirectionToBottomLeft": "To Bottom Left",
"gradientDirectionToTopRight": "To Top Right",
"gradientDirectionToTopLeft": "To Top Left",
"gradientColors": "Gradient Colors",
"color": "Color",
"addColor": "Add Color",
"availableWithYourPlan": "Available with your plan",
"upgradeRequired": "Upgrade required",
"settingsDisableAnimation": "Disable Animation",
"addTag": "Add Tag",
"accountConnectionProviderSpotify": "Spotify",
"accountConnectionProviderSteam": "Steam",
"timezoneNotFound": "Time zone not found",
"awardPoints": "Awarded {} points",
"postFeaturedOn": "Post featured on {}",
"messageSentAt": "Sent at {}",
"myTickets": "My Tickets",
"drawHistory": "Draw History",
"lottery": "Lottery",
"noLotteryTickets": "No lottery tickets yet",
"buyYourFirstTicket": "Buy your first lottery ticket to get started!",
"buyTicket": "Buy Ticket",
"ticketNumbers": "Numbers: {}, Special: {}",
"cost": "Cost",
"multiplier": "Multiplier",
"prizeWon": "Prize Won",
"pending": "Pending",
"drawn": "Drawn",
"won": "Won",
"lost": "Lost",
"noDrawHistory": "No draw history yet",
"buyLotteryTicket": "Buy Lottery Ticket",
"selectNumbers": "Select Numbers",
"select5UniqueNumbers": "Select 5 unique numbers",
"selectSpecialNumber": "Select Special Number",
"selectMultiplier": "Select Multiplier",
"baseCost": "Base Cost",
"totalCost": "Total Cost",
"prizeStructure": "Prize Structure",
"enterPinToConfirmPurchase": "Enter your PIN to confirm purchase",
"ticketPurchasedSuccessfully": "Ticket purchased successfully!",
"winningNumbers": "Winning Numbers",
"specialNumber": "Special Number",
"totalTickets": "Total Tickets",
"totalWinners": "Total Winners",
"prizePool": "Prize Pool",
"enterPinToConfirmPayment": "Enter your PIN code to confirm payment",
"purchase": "Purchase",
"multiplierLabel": "Multiplier",
"specialOnly": "Special Only",
"matches": "Matches",
"thoughtDefaultTopic": "Reflection",
"thoughtAiName": "SN-chan",
"thoughtUserName": "You",
"thoughtStreamingHint": "Sn-chan is thinking...",
"thoughtInputHint": "Ask sn-chan anything...",
"thoughtNewConversation": "Start New Conversation",
"thoughtParseError": "Failed to parse AI response",
"thoughtFunctionCall": "Use {}",
"aiThought": "AI Thought",
"aiThoughtTitle": "Let sn-chan think",
"postReferenceUnavailable": "Referenced post is unavailable",
"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",
"customReactionHint": "Custom Reaction allow you to use user uploaded stickers as the symbol of the reaction for the post. Exclusive for Stellar Program members.",
"publicationSites": "Publication Sites",
"uploadTasks": "Upload Tasks",
"thoughtFunctionCallBegin": "Calling tool {}",
"thoughtFunctionCallFinish": "{} responded",
"thoughtUnpaidHint": "Thinking unavaiable due to unpaid orders",
"more": "More",
"collapse": "Collapse",
"pollConfirmDiscard": "Are you sure you want to leave? All the poll data you're editing will not be saved.",
"discard": "Discard",
"fund": "Fund",
"fundsRecent": "Recent Funds",
"fundCreateNew": "Create New",
"fundCreateNewHint": "Create a new fund for your message. Select recipients and amount.",
"amountOfSplits": "Amount of Splits",
"enterNumberOfSplits": "Enter Splits Amount",
"orCreateWith": "Or\ncreate with",
"unindexedFiles": "Unindexed files",
"folder": "Folder",
"clearCompleted": "Clear Completed",
"uploadSuccess": "Upload successful!",
"wouldYouLikeToViewFile": "Would you like to view the file?",
"contentCantEmpty": "Content cannot be empty",
"features": "Features",
"unnamed": "Unnamed",
"fundEnvelopeLoadFailed": "Failed to load fund envelope",
"fundEnvelope": "Fund Envelope",
"fundEnvelopeRemaining": "Remaining: {} {}",
"fundEnvelopeSplit": "Split: {}",
"fundEnvelopeSplitEvenly": "Evenly",
"fundEnvelopeSplitRandomly": "Randomly",
"fundEnvelopeClaimSuccess": "Fund claimed successfully!",
"fundEnvelopeStatusCreated": "Created",
"fundEnvelopeStatusPartial": "Partially Claimed",
"fundEnvelopeStatusCompleted": "Fully Claimed",
"fundEnvelopeStatusExpired": "Expired",
"fundEnvelopeStatusUnknown": "Unknown",
"fundEnvelopeRecipients": "Recipients ({}/{} claimed)",
"fundEnvelopeExpiredDaysAgo": {
"one": "Expired {} day ago",
"other": "Expired {} days ago"
},
"fundEnvelopeExpiresSoon": "Expires soon",
"fundEnvelopeExpiresInHours": {
"one": "Expires in {} hour",
"other": "Expires in {} hours"
},
"fundEnvelopeExpiresInDays": {
"one": "Expires in {} day",
"other": "Expires in {} days"
},
"fundEnvelopeRemainingWithSplits": "{} {} / {} splits",
"fundEnvelopeUnknownUser": "Unknown User",
"deleteSite": "Delete Site",
"deleteSiteConfirm": "Are you sure you want to delete this site?",
"siteDeletedSuccess": "Site deleted successfully",
"siteSlug": "Slug",
"siteSlugHint": "my-site",
"siteSlugRequired": "Please enter a slug",
"siteSlugInvalid": "Slug can only contain lowercase letters, numbers, and dashes",
"siteName": "Site Name",
"siteNameHint": "My Publication Site",
"siteNameRequired": "Please enter a site name",
"siteMode": "Mode",
"siteModeFullyManaged": "Fully Managed",
"siteModeSelfManaged": "Self-Managed",
"editPublicationSite": "Edit Publication Site",
"deletePublicationSite": "Delete Publication Site",
"publicationSiteSavedSuccess": "Publication site saved successfully",
"publicationSiteDeleteConfirm": "Are you sure you want to delete this publication site? This action cannot be undone.",
"publicationSiteDeletedSuccess": "Publication site deleted successfully",
"newPublicationSite": "New Publication Site",
"siteDetails": "Site Details",
"siteInformation": "Site Information",
"siteDomain": "Domain",
"siteCreated": "Created",
"siteUpdated": "Updated",
"failedToLoadSite": "Failed to load site",
"sitePages": "Pages",
"noPagesYet": "No pages yet",
"createFirstPage": "Create your first page to get started",
"failedToLoadPages": "Failed to load pages",
"fileManagement": "File Management",
"siteFiles": "Files",
"siteFolder": "Folder",
"siteRoot": "Root",
"noFilesUploadedYet": "No files uploaded yet",
"uploadFirstFile": "Upload your first file to get started",
"failedToLoadFiles": "Failed to load files",
"noFilesFoundInFolder": "No files found in the selected folder",
"fileActions": "File Actions",
"purgeFiles": "Purge Files",
"purgeFilesDescription": "Remove all uploaded files from the site",
"deploySite": "Deploy Site",
"deploySiteDescription": "Upload and deploy a new version from ZIP archive",
"confirmPurge": "Confirm Purge",
"purgeFilesConfirm": "This will permanently delete all files uploaded to this site. This action cannot be undone. Are you sure you want to continue?",
"purgeAllFiles": "Purge All Files",
"allFilesPurgedSuccess": "All files purged successfully",
"failedToPurgeFiles": "Failed to purge files: {}",
"siteDeployedSuccess": "Site deployed successfully",
"failedToDeploySite": "Failed to deploy site: {}",
"createPage": "Create Page",
"editPage": "Edit Page",
"pageType": "Page Type",
"htmlPage": "HTML Page",
"redirectPage": "Redirect Page",
"pageTypeRequired": "Please select a page type",
"pagePath": "Page Path",
"pagePathHint": "/about, /contact, etc.",
"pagePathRequired": "Please enter a page path",
"pagePathInvalid": "Page path can only contain letters, numbers, hyphens, underscores, and slashes",
"pagePathMustStartWithSlash": "Page path must start with /",
"pagePathNoConsecutiveSlashes": "Page path cannot have consecutive slashes",
"pageTitle": "Page Title",
"pageTitleHint": "About Us, Contact, etc.",
"pageTitleRequired": "Please enter a page title",
"pageContentHtml": "Page Content (HTML)",
"pageContentHint": "<h1>Hello World</h1><p>This is my page content...</p>",
"pageContentRequired": "Please enter HTML content for the page",
"redirectTarget": "Redirect Target",
"redirectTargetHint": "/new-page, https://example.com, etc.",
"redirectTargetRequired": "Please enter a redirect target",
"redirectTargetInvalid": "Target must be a relative path (/) or absolute URL (http/https)",
"deletePage": "Delete Page",
"deletePageConfirm": "Are you sure you want to delete this page?",
"savePage": "Save Page",
"pageCreatedSuccess": "Page created successfully",
"pageUpdatedSuccess": "Page updated successfully",
"pageDeletedSuccess": "Page deleted successfully",
"uploadFiles": "Upload Files",
"uploadPath": "Upload Path",
"uploadPathHint": "/ (root) or /assets/images/",
"uploadPathRequired": "Please enter an upload path",
"uploadPathMustStartWithSlash": "Path must start with /",
"uploadPathNoSpaces": "Path cannot contain spaces",
"uploadPathNoConsecutiveSlashes": "Path cannot have consecutive slashes",
"percentCompleted": "{}% completed",
"filesToUpload": "{} files to upload",
"fileSizeKb": "Size: {} KB",
"uploadingEllipsis": "Uploading...",
"uploadFilesCount": {
"one": "Upload {} File",
"other": "Upload {} Files"
},
"allUploadsCompleted": "All uploads completed",
"someUploadsFailed": "Some uploads failed",
"uploadingInProgress": "Uploading in progress",
"readyToUpload": "Ready to upload",
"allFilesUploadedSuccess": "All files uploaded successfully",
"lotteryLastNumberSpecial": "The last selected number will be your special number.",
"lotteryMultiplierRequired": "Please enter a multiplier",
"lotteryMultiplierRange": "Multiplier must be between 1 and 10",
"dropToShare": "Drop to share"
} }

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

@@ -1,3 +1,6 @@
description: This file stores settings for Dart & Flutter DevTools. description: This file stores settings for Dart & Flutter DevTools.
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
extensions: extensions:
- drift: true
- provider: true
- shared_preferences: true

File diff suppressed because one or more lines are too long

View File

@@ -1,4 +1,3 @@
# Uncomment this line to define a global platform for your project
platform :ios, '15.0' platform :ios, '15.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency. # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
@@ -32,6 +31,8 @@ target 'Runner' do
use_modular_headers! use_modular_headers!
pod 'Alamofire' pod 'Alamofire'
pod 'Kingfisher', '~> 8.0'
pod 'KingfisherWebP'
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
@@ -41,8 +42,6 @@ target 'Runner' do
target 'SolianNotificationService' do target 'SolianNotificationService' do
inherit! :search_paths inherit! :search_paths
pod 'Kingfisher', '~> 8.0'
pod 'Alamofire'
end end
target 'SolianShareExtension' do target 'SolianShareExtension' do
@@ -50,6 +49,16 @@ target 'Runner' do
end end
end end
target 'Solian Watch App' do
platform :watchos, '11.0'
use_frameworks!
use_modular_headers!
pod 'Kingfisher', '~> 8.0'
pod 'KingfisherWebP'
end
post_install do |installer| post_install do |installer|
installer.pods_project.targets.each do |target| installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target) flutter_additional_ios_build_settings(target)

View File

@@ -1,7 +1,5 @@
PODS: PODS:
- Alamofire (5.10.2) - Alamofire (5.10.2)
- app_links (6.4.1):
- Flutter
- connectivity_plus (0.0.1): - connectivity_plus (0.0.1):
- Flutter - Flutter
- croppy (0.0.1): - croppy (0.0.1):
@@ -52,18 +50,18 @@ PODS:
- Firebase/Messaging (12.4.0): - Firebase/Messaging (12.4.0):
- Firebase/CoreOnly - Firebase/CoreOnly
- FirebaseMessaging (~> 12.4.0) - FirebaseMessaging (~> 12.4.0)
- firebase_analytics (12.0.3): - firebase_analytics (12.0.4):
- firebase_core - firebase_core
- FirebaseAnalytics (= 12.4.0) - FirebaseAnalytics (= 12.4.0)
- Flutter - Flutter
- firebase_core (4.2.0): - firebase_core (4.2.1):
- Firebase/CoreOnly (= 12.4.0) - Firebase/CoreOnly (= 12.4.0)
- Flutter - Flutter
- firebase_crashlytics (5.0.3): - firebase_crashlytics (5.0.5):
- Firebase/Crashlytics (= 12.4.0) - Firebase/Crashlytics (= 12.4.0)
- firebase_core - firebase_core
- Flutter - Flutter
- firebase_messaging (16.0.3): - firebase_messaging (16.0.4):
- Firebase/Messaging (= 12.4.0) - Firebase/Messaging (= 12.4.0)
- firebase_core - firebase_core
- Flutter - Flutter
@@ -142,15 +140,13 @@ PODS:
- Flutter - Flutter
- flutter_native_splash (2.4.3): - flutter_native_splash (2.4.3):
- Flutter - Flutter
- flutter_platform_alert (0.0.1):
- Flutter
- flutter_secure_storage (6.0.0): - flutter_secure_storage (6.0.0):
- Flutter - Flutter
- flutter_timezone (0.0.1): - flutter_timezone (0.0.1):
- Flutter - Flutter
- flutter_udid (0.0.1): - flutter_udid (0.0.1):
- Flutter - Flutter
- SAMKeychain - KeychainAccess
- flutter_webrtc (1.2.0): - flutter_webrtc (1.2.0):
- Flutter - Flutter
- WebRTC-SDK (= 137.7151.04) - WebRTC-SDK (= 137.7151.04)
@@ -218,8 +214,24 @@ PODS:
- Flutter - Flutter
- irondash_engine_context (0.0.1): - irondash_engine_context (0.0.1):
- Flutter - Flutter
- Kingfisher (8.6.0) - KeychainAccess (4.2.2)
- livekit_client (2.5.3): - Kingfisher (8.6.2)
- KingfisherWebP (1.7.2):
- Kingfisher (~> 8.0)
- libwebp (>= 1.1.0)
- libwebp (1.5.0):
- libwebp/demux (= 1.5.0)
- libwebp/mux (= 1.5.0)
- libwebp/sharpyuv (= 1.5.0)
- libwebp/webp (= 1.5.0)
- libwebp/demux (1.5.0):
- libwebp/webp
- libwebp/mux (1.5.0):
- libwebp/demux
- libwebp/sharpyuv (1.5.0)
- libwebp/webp (1.5.0):
- libwebp/sharpyuv
- livekit_client (2.5.4):
- Flutter - Flutter
- flutter_webrtc - flutter_webrtc
- WebRTC-SDK (= 137.7151.04) - WebRTC-SDK (= 137.7151.04)
@@ -250,11 +262,12 @@ PODS:
- PromisesObjC (2.4.0) - PromisesObjC (2.4.0)
- PromisesSwift (2.4.0): - PromisesSwift (2.4.0):
- PromisesObjC (= 2.4.0) - PromisesObjC (= 2.4.0)
- protocol_handler_ios (0.0.1):
- Flutter
- receive_sharing_intent (1.8.1): - receive_sharing_intent (1.8.1):
- Flutter - Flutter
- record_ios (1.1.0): - record_ios (1.1.0):
- Flutter - Flutter
- SAMKeychain (1.5.3)
- SDWebImage (5.21.3): - SDWebImage (5.21.3):
- SDWebImage/Core (= 5.21.3) - SDWebImage/Core (= 5.21.3)
- SDWebImage/Core (5.21.3) - SDWebImage/Core (5.21.3)
@@ -300,15 +313,12 @@ PODS:
- Flutter - Flutter
- url_launcher_ios (0.0.1): - url_launcher_ios (0.0.1):
- Flutter - Flutter
- volume_controller (0.0.1):
- Flutter
- wakelock_plus (0.0.1): - wakelock_plus (0.0.1):
- Flutter - Flutter
- WebRTC-SDK (137.7151.04) - WebRTC-SDK (137.7151.04)
DEPENDENCIES: DEPENDENCIES:
- Alamofire - Alamofire
- app_links (from `.symlinks/plugins/app_links/ios`)
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`) - connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
- croppy (from `.symlinks/plugins/croppy/ios`) - croppy (from `.symlinks/plugins/croppy/ios`)
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
@@ -324,7 +334,6 @@ DEPENDENCIES:
- flutter_keyboard_visibility (from `.symlinks/plugins/flutter_keyboard_visibility/ios`) - flutter_keyboard_visibility (from `.symlinks/plugins/flutter_keyboard_visibility/ios`)
- flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`) - flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`) - flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
- flutter_platform_alert (from `.symlinks/plugins/flutter_platform_alert/ios`)
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`) - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
- flutter_timezone (from `.symlinks/plugins/flutter_timezone/ios`) - flutter_timezone (from `.symlinks/plugins/flutter_timezone/ios`)
- flutter_udid (from `.symlinks/plugins/flutter_udid/ios`) - flutter_udid (from `.symlinks/plugins/flutter_udid/ios`)
@@ -333,6 +342,7 @@ DEPENDENCIES:
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`) - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
- irondash_engine_context (from `.symlinks/plugins/irondash_engine_context/ios`) - irondash_engine_context (from `.symlinks/plugins/irondash_engine_context/ios`)
- Kingfisher (~> 8.0) - Kingfisher (~> 8.0)
- KingfisherWebP
- livekit_client (from `.symlinks/plugins/livekit_client/ios`) - livekit_client (from `.symlinks/plugins/livekit_client/ios`)
- local_auth_darwin (from `.symlinks/plugins/local_auth_darwin/darwin`) - local_auth_darwin (from `.symlinks/plugins/local_auth_darwin/darwin`)
- media_kit_libs_ios_video (from `.symlinks/plugins/media_kit_libs_ios_video/ios`) - media_kit_libs_ios_video (from `.symlinks/plugins/media_kit_libs_ios_video/ios`)
@@ -342,6 +352,7 @@ DEPENDENCIES:
- pasteboard (from `.symlinks/plugins/pasteboard/ios`) - pasteboard (from `.symlinks/plugins/pasteboard/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- pointer_interceptor_ios (from `.symlinks/plugins/pointer_interceptor_ios/ios`) - pointer_interceptor_ios (from `.symlinks/plugins/pointer_interceptor_ios/ios`)
- protocol_handler_ios (from `.symlinks/plugins/protocol_handler_ios/ios`)
- receive_sharing_intent (from `.symlinks/plugins/receive_sharing_intent/ios`) - receive_sharing_intent (from `.symlinks/plugins/receive_sharing_intent/ios`)
- record_ios (from `.symlinks/plugins/record_ios/ios`) - record_ios (from `.symlinks/plugins/record_ios/ios`)
- share_plus (from `.symlinks/plugins/share_plus/ios`) - share_plus (from `.symlinks/plugins/share_plus/ios`)
@@ -352,7 +363,6 @@ DEPENDENCIES:
- super_native_extensions (from `.symlinks/plugins/super_native_extensions/ios`) - super_native_extensions (from `.symlinks/plugins/super_native_extensions/ios`)
- syncfusion_flutter_pdfviewer (from `.symlinks/plugins/syncfusion_flutter_pdfviewer/ios`) - syncfusion_flutter_pdfviewer (from `.symlinks/plugins/syncfusion_flutter_pdfviewer/ios`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
- volume_controller (from `.symlinks/plugins/volume_controller/ios`)
- wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`) - wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`)
SPEC REPOS: SPEC REPOS:
@@ -374,20 +384,20 @@ SPEC REPOS:
- GoogleAppMeasurement - GoogleAppMeasurement
- GoogleDataTransport - GoogleDataTransport
- GoogleUtilities - GoogleUtilities
- KeychainAccess
- Kingfisher - Kingfisher
- KingfisherWebP
- libwebp
- nanopb - nanopb
- OrderedSet - OrderedSet
- PromisesObjC - PromisesObjC
- PromisesSwift - PromisesSwift
- SAMKeychain
- SDWebImage - SDWebImage
- sqlite3 - sqlite3
- SwiftyGif - SwiftyGif
- WebRTC-SDK - WebRTC-SDK
EXTERNAL SOURCES: EXTERNAL SOURCES:
app_links:
:path: ".symlinks/plugins/app_links/ios"
connectivity_plus: connectivity_plus:
:path: ".symlinks/plugins/connectivity_plus/ios" :path: ".symlinks/plugins/connectivity_plus/ios"
croppy: croppy:
@@ -418,8 +428,6 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/flutter_local_notifications/ios" :path: ".symlinks/plugins/flutter_local_notifications/ios"
flutter_native_splash: flutter_native_splash:
:path: ".symlinks/plugins/flutter_native_splash/ios" :path: ".symlinks/plugins/flutter_native_splash/ios"
flutter_platform_alert:
:path: ".symlinks/plugins/flutter_platform_alert/ios"
flutter_secure_storage: flutter_secure_storage:
:path: ".symlinks/plugins/flutter_secure_storage/ios" :path: ".symlinks/plugins/flutter_secure_storage/ios"
flutter_timezone: flutter_timezone:
@@ -452,6 +460,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/path_provider_foundation/darwin" :path: ".symlinks/plugins/path_provider_foundation/darwin"
pointer_interceptor_ios: pointer_interceptor_ios:
:path: ".symlinks/plugins/pointer_interceptor_ios/ios" :path: ".symlinks/plugins/pointer_interceptor_ios/ios"
protocol_handler_ios:
:path: ".symlinks/plugins/protocol_handler_ios/ios"
receive_sharing_intent: receive_sharing_intent:
:path: ".symlinks/plugins/receive_sharing_intent/ios" :path: ".symlinks/plugins/receive_sharing_intent/ios"
record_ios: record_ios:
@@ -472,14 +482,11 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/syncfusion_flutter_pdfviewer/ios" :path: ".symlinks/plugins/syncfusion_flutter_pdfviewer/ios"
url_launcher_ios: url_launcher_ios:
:path: ".symlinks/plugins/url_launcher_ios/ios" :path: ".symlinks/plugins/url_launcher_ios/ios"
volume_controller:
:path: ".symlinks/plugins/volume_controller/ios"
wakelock_plus: wakelock_plus:
:path: ".symlinks/plugins/wakelock_plus/ios" :path: ".symlinks/plugins/wakelock_plus/ios"
SPEC CHECKSUMS: SPEC CHECKSUMS:
Alamofire: 7193b3b92c74a07f85569e1a6c4f4237291e7496 Alamofire: 7193b3b92c74a07f85569e1a6c4f4237291e7496
app_links: 3dbc685f76b1693c66a6d9dd1e9ab6f73d97dc0a
connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd
croppy: 979e8ddc254f4642bffe7d52dc7193354b27ba30 croppy: 979e8ddc254f4642bffe7d52dc7193354b27ba30
device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe
@@ -488,10 +495,10 @@ SPEC CHECKSUMS:
file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be
file_saver: 6cdbcddd690cb02b0c1a0c225b37cd805c2bf8b6 file_saver: 6cdbcddd690cb02b0c1a0c225b37cd805c2bf8b6
Firebase: f07b15ae5a6ec0f93713e30b923d9970d144af3e Firebase: f07b15ae5a6ec0f93713e30b923d9970d144af3e
firebase_analytics: 1d024068b1d4707d5ba7a42a12976ddf3316d835 firebase_analytics: 67fbdd9f3c04e55048024f3da21cfc36f05e56cf
firebase_core: 744984dbbed8b3036abf34f0b98d80f130a7e464 firebase_core: f1aafb21c14f497e5498f7ffc4dc63cbb52b2594
firebase_crashlytics: f3a9a4338ab99b67042f64e9e22e1bf349cb44ed firebase_crashlytics: c039028126cb45e32f4c217aa392408b0963d081
firebase_messaging: 82c70650c426a0a14873e1acdb9ec2b443c4e8b4 firebase_messaging: c17a29984eafce4b2997fe078bb0a9e0b06f5dde
FirebaseAnalytics: 0fc2b20091f0ddd21bf73397cf8f0eb5346dc24f FirebaseAnalytics: 0fc2b20091f0ddd21bf73397cf8f0eb5346dc24f
FirebaseCore: bb595f3114953664e3c1dc032f008a244147cfd3 FirebaseCore: bb595f3114953664e3c1dc032f008a244147cfd3
FirebaseCoreExtension: 7e1f7118ee970e001a8013719fb90950ee5e0018 FirebaseCoreExtension: 7e1f7118ee970e001a8013719fb90950ee5e0018
@@ -507,10 +514,9 @@ SPEC CHECKSUMS:
flutter_keyboard_visibility: 4625131e43015dbbe759d9b20daaf77e0e3f6619 flutter_keyboard_visibility: 4625131e43015dbbe759d9b20daaf77e0e3f6619
flutter_local_notifications: a5a732f069baa862e728d839dd2ebb904737effb flutter_local_notifications: a5a732f069baa862e728d839dd2ebb904737effb
flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf
flutter_platform_alert: bf3b5fcd4ac14bd637e20527e9c471633071afd3
flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13 flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13
flutter_timezone: 7c838e17ffd4645d261e87037e5bebf6d38fe544 flutter_timezone: 7c838e17ffd4645d261e87037e5bebf6d38fe544
flutter_udid: f7c3884e6ec2951efe4f9de082257fc77c4d15e9 flutter_udid: 92a5d31fe0526b7b6002a2318df702e12e7eb300
flutter_webrtc: c3e21fc0dcd9d8eb246ae4d5256fcbeb2f5ecd22 flutter_webrtc: c3e21fc0dcd9d8eb246ae4d5256fcbeb2f5ecd22
gal: baecd024ebfd13c441269ca7404792a7152fde89 gal: baecd024ebfd13c441269ca7404792a7152fde89
GoogleAdsOnDeviceConversion: e03a386840803ea7eef3fd22a061930142c039c1 GoogleAdsOnDeviceConversion: e03a386840803ea7eef3fd22a061930142c039c1
@@ -519,8 +525,11 @@ SPEC CHECKSUMS:
GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1 GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
image_picker_ios: e0ece4aa2a75771a7de3fa735d26d90817041326 image_picker_ios: e0ece4aa2a75771a7de3fa735d26d90817041326
irondash_engine_context: 8e58ca8e0212ee9d1c7dc6a42121849986c88486 irondash_engine_context: 8e58ca8e0212ee9d1c7dc6a42121849986c88486
Kingfisher: 64278f126a815d0e2d391cdf71311b85882c4de0 KeychainAccess: c0c4f7f38f6fc7bbe58f5702e25f7bd2f65abf51
livekit_client: 86c8af579274e4b7a215185a8080db2d4e176f40 Kingfisher: 23d18f54677d973b713e54ce6a8f5eef6e7056ba
KingfisherWebP: 38b9721821947f547afb78f933f75f4f9e0ae402
libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8
livekit_client: 53ca658779b78710fb458cccee28b53a13356c15
local_auth_darwin: c3ee6cce0a8d56be34c8ccb66ba31f7f180aaebb local_auth_darwin: c3ee6cce0a8d56be34c8ccb66ba31f7f180aaebb
media_kit_libs_ios_video: 5a18affdb97d1f5d466dc79988b13eff6c5e2854 media_kit_libs_ios_video: 5a18affdb97d1f5d466dc79988b13eff6c5e2854
media_kit_video: 1746e198cb697d1ffb734b1d05ec429d1fcd1474 media_kit_video: 1746e198cb697d1ffb734b1d05ec429d1fcd1474
@@ -533,9 +542,9 @@ SPEC CHECKSUMS:
pointer_interceptor_ios: da06a662d5bfd329602b45b2ab41bc0fb5fdb0f0 pointer_interceptor_ios: da06a662d5bfd329602b45b2ab41bc0fb5fdb0f0
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851 PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851
protocol_handler_ios: 59f23ee71f3ec602d67902ca7f669a80957888d5
receive_sharing_intent: 222384f00ffe7e952bbfabaa9e3967cb87e5fe00 receive_sharing_intent: 222384f00ffe7e952bbfabaa9e3967cb87e5fe00
record_ios: f75fa1d57f840012775c0e93a38a7f3ceea1a374 record_ios: f75fa1d57f840012775c0e93a38a7f3ceea1a374
SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c
SDWebImage: 16309af6d214ba3f77a7c6f6fdda888cb313a50a SDWebImage: 16309af6d214ba3f77a7c6f6fdda888cb313a50a
share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
@@ -547,10 +556,9 @@ SPEC CHECKSUMS:
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4 SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
syncfusion_flutter_pdfviewer: 90dc48305d2e33d4aa20681d1e98ddeda891bc14 syncfusion_flutter_pdfviewer: 90dc48305d2e33d4aa20681d1e98ddeda891bc14
url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b
volume_controller: 3657a1f65bedb98fa41ff7dc5793537919f31b12
wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556 wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556
WebRTC-SDK: 40d4f5ba05cadff14e4db5614aec402a633f007e WebRTC-SDK: 40d4f5ba05cadff14e4db5614aec402a633f007e
PODFILE CHECKSUM: c818292390b02fa379036ea099713a332bd7193f PODFILE CHECKSUM: 585198f58dca90ac6492607c83a8d17045ab3852
COCOAPODS: 1.16.2 COCOAPODS: 1.16.2

View File

@@ -10,6 +10,8 @@
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
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 */; }; 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, ); }; }; 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, ); }; }; 73C305D82E0BE878009035B9 /* SolianShareExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 73C305CE2E0BE878009035B9 /* SolianShareExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
@@ -58,6 +60,17 @@
/* End PBXContainerItemProxy section */ /* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */ /* Begin PBXCopyFilesBuildPhase section */
7310A7DE2EB10963002C0FD3 /* Embed Watch Content */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 12;
dstPath = "$(CONTENTS_FOLDER_PATH)/Watch";
dstSubfolderSpec = 16;
files = (
7310A7DF2EB10963002C0FD3 /* Solian Watch App.app in Embed Watch Content */,
);
name = "Embed Watch Content";
runOnlyForDeploymentPostprocessing = 0;
};
73268D1D2DEAFD670076E970 /* Embed Foundation Extensions */ = { 73268D1D2DEAFD670076E970 /* Embed Foundation Extensions */ = {
isa = PBXCopyFilesBuildPhase; isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
@@ -84,6 +97,8 @@
/* End PBXCopyFilesBuildPhase section */ /* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference 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>"; }; 14118AC858B441AB16B7309E /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; }; 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
@@ -91,15 +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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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; }; 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>"; }; 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>"; }; 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; }; 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; }; 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>"; }; 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>"; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
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>"; }; 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; }; 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; }; 73ACDFAC2E3D0E6100B63535 /* ReplayKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ReplayKit.framework; path = System/Library/Frameworks/ReplayKit.framework; sourceTree = SDKROOT; };
@@ -111,6 +129,7 @@
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
7B40764A2C4CC0E7DC70A0D3 /* Pods_SolianShareExtension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SolianShareExtension.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 7B40764A2C4CC0E7DC70A0D3 /* Pods_SolianShareExtension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SolianShareExtension.framework; sourceTree = BUILT_PRODUCTS_DIR; };
86D60BA96DA647E1B11AA7F0 /* Pods-WatchRunner Watch App.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WatchRunner Watch App.debug.xcconfig"; path = "Target Support Files/Pods-WatchRunner Watch App/Pods-WatchRunner Watch App.debug.xcconfig"; sourceTree = "<group>"; };
8B40620B1EEBB09456406A3C /* Pods-SolianNotificationService.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SolianNotificationService.profile.xcconfig"; path = "Target Support Files/Pods-SolianNotificationService/Pods-SolianNotificationService.profile.xcconfig"; sourceTree = "<group>"; }; 8B40620B1EEBB09456406A3C /* Pods-SolianNotificationService.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SolianNotificationService.profile.xcconfig"; path = "Target Support Files/Pods-SolianNotificationService/Pods-SolianNotificationService.profile.xcconfig"; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
@@ -120,10 +139,12 @@
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
9AE244813FCDFAA941430393 /* GoogleService-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "Runner/GoogleService-Info.plist"; sourceTree = "<group>"; }; 9AE244813FCDFAA941430393 /* GoogleService-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "Runner/GoogleService-Info.plist"; sourceTree = "<group>"; };
A2EB1DAFDE9B8E6D88BBF7A3 /* Pods-WatchRunner Watch App.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WatchRunner Watch App.release.xcconfig"; path = "Target Support Files/Pods-WatchRunner Watch App/Pods-WatchRunner Watch App.release.xcconfig"; sourceTree = "<group>"; };
A499FDB2082EB000933AA8C5 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; }; A499FDB2082EB000933AA8C5 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
A85FF612AE7623A9934E57CE /* Pods-SolianShareExtension.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SolianShareExtension.profile.xcconfig"; path = "Target Support Files/Pods-SolianShareExtension/Pods-SolianShareExtension.profile.xcconfig"; sourceTree = "<group>"; }; A85FF612AE7623A9934E57CE /* Pods-SolianShareExtension.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SolianShareExtension.profile.xcconfig"; path = "Target Support Files/Pods-SolianShareExtension/Pods-SolianShareExtension.profile.xcconfig"; sourceTree = "<group>"; };
AA0CA8A3E15DEE023BB27438 /* Pods_NotificationService.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_NotificationService.framework; sourceTree = BUILT_PRODUCTS_DIR; }; AA0CA8A3E15DEE023BB27438 /* Pods_NotificationService.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_NotificationService.framework; sourceTree = BUILT_PRODUCTS_DIR; };
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>"; }; 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>"; }; 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; }; 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>"; }; 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>"; };
@@ -162,6 +183,13 @@
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ /* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
/* Begin PBXFileSystemSynchronizedRootGroup section */ /* Begin PBXFileSystemSynchronizedRootGroup section */
7310A7D52EB10962002C0FD3 /* Solian Watch App */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
);
path = "Solian Watch App";
sourceTree = "<group>";
};
73268D272DEB012A0076E970 /* Services */ = { 73268D272DEB012A0076E970 /* Services */ = {
isa = PBXFileSystemSynchronizedRootGroup; isa = PBXFileSystemSynchronizedRootGroup;
exceptions = ( exceptions = (
@@ -205,6 +233,14 @@
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
7310A7D12EB10962002C0FD3 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
5D8143680678FCD1D1827271 /* Pods_Solian_Watch_App.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
73ACDFA82E3D0E6100B63535 /* Frameworks */ = { 73ACDFA82E3D0E6100B63535 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
@@ -258,6 +294,7 @@
7B40764A2C4CC0E7DC70A0D3 /* Pods_SolianShareExtension.framework */, 7B40764A2C4CC0E7DC70A0D3 /* Pods_SolianShareExtension.framework */,
73ACDFAC2E3D0E6100B63535 /* ReplayKit.framework */, 73ACDFAC2E3D0E6100B63535 /* ReplayKit.framework */,
73ACDFB82E3D0E6100B63535 /* UIKit.framework */, 73ACDFB82E3D0E6100B63535 /* UIKit.framework */,
C9C046CF867AE03DC170F861 /* Pods_Solian_Watch_App.framework */,
); );
name = Frameworks; name = Frameworks;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -280,6 +317,12 @@
17FAB080A9C53193ABD9C15B /* Pods-SolianShareExtension.debug.xcconfig */, 17FAB080A9C53193ABD9C15B /* Pods-SolianShareExtension.debug.xcconfig */,
27C66EFB5A705F1A822C3EB0 /* Pods-SolianShareExtension.release.xcconfig */, 27C66EFB5A705F1A822C3EB0 /* Pods-SolianShareExtension.release.xcconfig */,
A85FF612AE7623A9934E57CE /* Pods-SolianShareExtension.profile.xcconfig */, A85FF612AE7623A9934E57CE /* Pods-SolianShareExtension.profile.xcconfig */,
86D60BA96DA647E1B11AA7F0 /* Pods-WatchRunner Watch App.debug.xcconfig */,
A2EB1DAFDE9B8E6D88BBF7A3 /* Pods-WatchRunner Watch App.release.xcconfig */,
103EA2362B9E9F127016A1F1 /* Pods-WatchRunner Watch App.profile.xcconfig */,
31EA49B10397BD4145AD765E /* Pods-Solian Watch App.debug.xcconfig */,
2440CEDEAAD6D51FDA95FA62 /* Pods-Solian Watch App.release.xcconfig */,
0ECC3D56D018DD87FC342699 /* Pods-Solian Watch App.profile.xcconfig */,
); );
path = Pods; path = Pods;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -303,6 +346,7 @@
73CDD67B2DEC00480059D95D /* SolianNotificationService */, 73CDD67B2DEC00480059D95D /* SolianNotificationService */,
73C305CF2E0BE878009035B9 /* SolianShareExtension */, 73C305CF2E0BE878009035B9 /* SolianShareExtension */,
73ACDFAE2E3D0E6100B63535 /* SolianBroadcastExtension */, 73ACDFAE2E3D0E6100B63535 /* SolianBroadcastExtension */,
7310A7D52EB10962002C0FD3 /* Solian Watch App */,
97C146EF1CF9000F007C117D /* Products */, 97C146EF1CF9000F007C117D /* Products */,
331C8082294A63A400263BE5 /* RunnerTests */, 331C8082294A63A400263BE5 /* RunnerTests */,
91E124CE95BCB4DCD890160D /* Pods */, 91E124CE95BCB4DCD890160D /* Pods */,
@@ -319,6 +363,7 @@
73CDD67A2DEC00480059D95D /* SolianNotificationService.appex */, 73CDD67A2DEC00480059D95D /* SolianNotificationService.appex */,
73C305CE2E0BE878009035B9 /* SolianShareExtension.appex */, 73C305CE2E0BE878009035B9 /* SolianShareExtension.appex */,
73ACDFAB2E3D0E6100B63535 /* SolianBroadcastExtension.appex */, 73ACDFAB2E3D0E6100B63535 /* SolianBroadcastExtension.appex */,
7310A7D42EB10962002C0FD3 /* Solian Watch App.app */,
); );
name = Products; name = Products;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -363,6 +408,28 @@
productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test"; productType = "com.apple.product-type.bundle.unit-test";
}; };
7310A7D32EB10962002C0FD3 /* Solian Watch App */ = {
isa = PBXNativeTarget;
buildConfigurationList = 7310A7E32EB10963002C0FD3 /* Build configuration list for PBXNativeTarget "Solian Watch App" */;
buildPhases = (
DDEDA1BA6278B94F0F7B9B61 /* [CP] Check Pods Manifest.lock */,
7310A7D02EB10962002C0FD3 /* Sources */,
7310A7D12EB10962002C0FD3 /* Frameworks */,
7310A7D22EB10962002C0FD3 /* Resources */,
E29ECA5954168075BDB000DC /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
dependencies = (
);
fileSystemSynchronizedGroups = (
7310A7D52EB10962002C0FD3 /* Solian Watch App */,
);
name = "Solian Watch App";
productName = "WatchRunner Watch App";
productReference = 7310A7D42EB10962002C0FD3 /* Solian Watch App.app */;
productType = "com.apple.product-type.application";
};
73ACDFAA2E3D0E6100B63535 /* SolianBroadcastExtension */ = { 73ACDFAA2E3D0E6100B63535 /* SolianBroadcastExtension */ = {
isa = PBXNativeTarget; isa = PBXNativeTarget;
buildConfigurationList = 73ACDFCB2E3D0E6100B63535 /* Build configuration list for PBXNativeTarget "SolianBroadcastExtension" */; buildConfigurationList = 73ACDFCB2E3D0E6100B63535 /* Build configuration list for PBXNativeTarget "SolianBroadcastExtension" */;
@@ -434,6 +501,7 @@
97C146EA1CF9000F007C117D /* Sources */, 97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */, 97C146EB1CF9000F007C117D /* Frameworks */,
73268D1D2DEAFD670076E970 /* Embed Foundation Extensions */, 73268D1D2DEAFD670076E970 /* Embed Foundation Extensions */,
7310A7DE2EB10963002C0FD3 /* Embed Watch Content */,
97C146EC1CF9000F007C117D /* Resources */, 97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */, 9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */,
@@ -463,7 +531,7 @@
isa = PBXProject; isa = PBXProject;
attributes = { attributes = {
BuildIndependentTargetsInParallel = YES; BuildIndependentTargetsInParallel = YES;
LastSwiftUpdateCheck = 1640; LastSwiftUpdateCheck = 2600;
LastUpgradeCheck = 1510; LastUpgradeCheck = 1510;
ORGANIZATIONNAME = ""; ORGANIZATIONNAME = "";
TargetAttributes = { TargetAttributes = {
@@ -471,6 +539,9 @@
CreatedOnToolsVersion = 14.0; CreatedOnToolsVersion = 14.0;
TestTargetID = 97C146ED1CF9000F007C117D; TestTargetID = 97C146ED1CF9000F007C117D;
}; };
7310A7D32EB10962002C0FD3 = {
CreatedOnToolsVersion = 26.0.1;
};
73ACDFAA2E3D0E6100B63535 = { 73ACDFAA2E3D0E6100B63535 = {
CreatedOnToolsVersion = 16.4; CreatedOnToolsVersion = 16.4;
}; };
@@ -504,6 +575,7 @@
73CDD6792DEC00480059D95D /* SolianNotificationService */, 73CDD6792DEC00480059D95D /* SolianNotificationService */,
73C305CD2E0BE878009035B9 /* SolianShareExtension */, 73C305CD2E0BE878009035B9 /* SolianShareExtension */,
73ACDFAA2E3D0E6100B63535 /* SolianBroadcastExtension */, 73ACDFAA2E3D0E6100B63535 /* SolianBroadcastExtension */,
7310A7D32EB10962002C0FD3 /* Solian Watch App */,
); );
}; };
/* End PBXProject section */ /* End PBXProject section */
@@ -516,6 +588,13 @@
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
7310A7D22EB10962002C0FD3 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
73ACDFA92E3D0E6100B63535 /* Resources */ = { 73ACDFA92E3D0E6100B63535 /* Resources */ = {
isa = PBXResourcesBuildPhase; isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
@@ -683,6 +762,45 @@
shellPath = /bin/sh; shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
}; };
DDEDA1BA6278B94F0F7B9B61 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-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" */ = { E86CDE9D6464F4F52B910856 /* FlutterFire: "flutterfire upload-crashlytics-symbols" */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
@@ -734,6 +852,13 @@
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
7310A7D02EB10962002C0FD3 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
73ACDFA72E3D0E6100B63535 /* Sources */ = { 73ACDFA72E3D0E6100B63535 /* Sources */ = {
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
@@ -873,6 +998,7 @@
CUSTOM_GROUP_ID = group.solsynth.solian; CUSTOM_GROUP_ID = group.solsynth.solian;
DEVELOPMENT_TEAM = W7HPZ53V6B; DEVELOPMENT_TEAM = W7HPZ53V6B;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
EXCLUDED_SOURCE_FILE_NAMES = "";
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = Solian; INFOPLIST_KEY_CFBundleDisplayName = Solian;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking"; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
@@ -883,10 +1009,12 @@
); );
PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian; PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos";
SWIFT_ENABLE_EXPLICIT_MODULES = "$(SWIFT_USE_INTEGRATED_DRIVER)"; SWIFT_ENABLE_EXPLICIT_MODULES = "$(SWIFT_USE_INTEGRATED_DRIVER)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic"; VERSIONING_SYSTEM = "apple-generic";
WATCHOS_DEPLOYMENT_TARGET = 11.6;
}; };
name = Profile; name = Profile;
}; };
@@ -894,6 +1022,7 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = 14DFD79BE7C26E51B117583C /* Pods-RunnerTests.debug.xcconfig */; baseConfigurationReference = 14DFD79BE7C26E51B117583C /* Pods-RunnerTests.debug.xcconfig */;
buildSettings = { buildSettings = {
ALLOW_TARGET_PLATFORM_SPECIALIZATION = YES;
BUNDLE_LOADER = "$(TEST_HOST)"; BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
@@ -902,6 +1031,8 @@
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian.RunnerTests; PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos";
SUPPORTS_MACCATALYST = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
@@ -913,6 +1044,7 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = 14118AC858B441AB16B7309E /* Pods-RunnerTests.release.xcconfig */; baseConfigurationReference = 14118AC858B441AB16B7309E /* Pods-RunnerTests.release.xcconfig */;
buildSettings = { buildSettings = {
ALLOW_TARGET_PLATFORM_SPECIALIZATION = YES;
BUNDLE_LOADER = "$(TEST_HOST)"; BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
@@ -921,6 +1053,8 @@
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian.RunnerTests; PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos";
SUPPORTS_MACCATALYST = YES;
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
}; };
@@ -930,6 +1064,7 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = E6B10A9A85BECA2E576C91FF /* Pods-RunnerTests.profile.xcconfig */; baseConfigurationReference = E6B10A9A85BECA2E576C91FF /* Pods-RunnerTests.profile.xcconfig */;
buildSettings = { buildSettings = {
ALLOW_TARGET_PLATFORM_SPECIALIZATION = YES;
BUNDLE_LOADER = "$(TEST_HOST)"; BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
@@ -938,11 +1073,162 @@
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian.RunnerTests; PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos";
SUPPORTS_MACCATALYST = YES;
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
}; };
name = Profile; name = Profile;
}; };
7310A7E02EB10963002C0FD3 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 31EA49B10397BD4145AD765E /* Pods-Solian Watch App.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = W7HPZ53V6B;
ENABLE_PREVIEWS = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "WatchRunner-Watch-App-Info.plist";
INFOPLIST_KEY_CFBundleDisplayName = Solian;
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
INFOPLIST_KEY_WKCompanionAppBundleIdentifier = dev.solsynth.solian;
INFOPLIST_KEY_WKRunsIndependentlyOfCompanionApp = NO;
IPHONEOS_DEPLOYMENT_TARGET = 18.6;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MARKETING_VERSION = 1.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian.watchkitapp;
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = watchos;
SKIP_INSTALL = YES;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
SWIFT_APPROACHABLE_CONCURRENCY = YES;
SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 4;
WATCHOS_DEPLOYMENT_TARGET = 11.6;
};
name = Debug;
};
7310A7E12EB10963002C0FD3 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 2440CEDEAAD6D51FDA95FA62 /* Pods-Solian Watch App.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = W7HPZ53V6B;
ENABLE_PREVIEWS = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "WatchRunner-Watch-App-Info.plist";
INFOPLIST_KEY_CFBundleDisplayName = Solian;
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
INFOPLIST_KEY_WKCompanionAppBundleIdentifier = dev.solsynth.solian;
INFOPLIST_KEY_WKRunsIndependentlyOfCompanionApp = NO;
IPHONEOS_DEPLOYMENT_TARGET = 18.6;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MARKETING_VERSION = 1.0;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian.watchkitapp;
PRODUCT_NAME = "$(TARGET_NAME)";
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;
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 4;
WATCHOS_DEPLOYMENT_TARGET = 11.6;
};
name = Release;
};
7310A7E22EB10963002C0FD3 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 0ECC3D56D018DD87FC342699 /* Pods-Solian Watch App.profile.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = W7HPZ53V6B;
ENABLE_PREVIEWS = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "WatchRunner-Watch-App-Info.plist";
INFOPLIST_KEY_CFBundleDisplayName = Solian;
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
INFOPLIST_KEY_WKCompanionAppBundleIdentifier = dev.solsynth.solian;
INFOPLIST_KEY_WKRunsIndependentlyOfCompanionApp = NO;
IPHONEOS_DEPLOYMENT_TARGET = 18.6;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MARKETING_VERSION = 1.0;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian.watchkitapp;
PRODUCT_NAME = "$(TARGET_NAME)";
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;
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 4;
WATCHOS_DEPLOYMENT_TARGET = 11.6;
};
name = Profile;
};
73ACDFC42E3D0E6100B63535 /* Debug */ = { 73ACDFC42E3D0E6100B63535 /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
@@ -976,6 +1262,7 @@
PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian.SolianBroadcastExtension; PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian.SolianBroadcastExtension;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
@@ -1016,6 +1303,7 @@
PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian.SolianBroadcastExtension; PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian.SolianBroadcastExtension;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos";
SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2"; TARGETED_DEVICE_FAMILY = "1,2";
@@ -1054,6 +1342,7 @@
PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian.SolianBroadcastExtension; PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian.SolianBroadcastExtension;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos";
SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2"; TARGETED_DEVICE_FAMILY = "1,2";
@@ -1095,6 +1384,7 @@
PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian.SolianShareExtension; PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian.SolianShareExtension;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_ENABLE_EXPLICIT_MODULES = NO; SWIFT_ENABLE_EXPLICIT_MODULES = NO;
@@ -1138,6 +1428,7 @@
PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian.SolianShareExtension; PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian.SolianShareExtension;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos";
SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_ENABLE_EXPLICIT_MODULES = NO; SWIFT_ENABLE_EXPLICIT_MODULES = NO;
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
@@ -1179,6 +1470,7 @@
PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian.SolianShareExtension; PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian.SolianShareExtension;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos";
SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_ENABLE_EXPLICIT_MODULES = NO; SWIFT_ENABLE_EXPLICIT_MODULES = NO;
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
@@ -1428,6 +1720,7 @@
CUSTOM_GROUP_ID = group.solsynth.solian; CUSTOM_GROUP_ID = group.solsynth.solian;
DEVELOPMENT_TEAM = W7HPZ53V6B; DEVELOPMENT_TEAM = W7HPZ53V6B;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
EXCLUDED_SOURCE_FILE_NAMES = "";
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = Solian; INFOPLIST_KEY_CFBundleDisplayName = Solian;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking"; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
@@ -1443,6 +1736,7 @@
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic"; VERSIONING_SYSTEM = "apple-generic";
WATCHOS_DEPLOYMENT_TARGET = 11.6;
}; };
name = Debug; name = Debug;
}; };
@@ -1457,6 +1751,7 @@
CUSTOM_GROUP_ID = group.solsynth.solian; CUSTOM_GROUP_ID = group.solsynth.solian;
DEVELOPMENT_TEAM = W7HPZ53V6B; DEVELOPMENT_TEAM = W7HPZ53V6B;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
EXCLUDED_SOURCE_FILE_NAMES = "";
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = Solian; INFOPLIST_KEY_CFBundleDisplayName = Solian;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking"; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
@@ -1465,12 +1760,15 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
ONLY_ACTIVE_ARCH = NO;
PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian; PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos";
SWIFT_ENABLE_EXPLICIT_MODULES = "$(SWIFT_USE_INTEGRATED_DRIVER)"; SWIFT_ENABLE_EXPLICIT_MODULES = "$(SWIFT_USE_INTEGRATED_DRIVER)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic"; VERSIONING_SYSTEM = "apple-generic";
WATCHOS_DEPLOYMENT_TARGET = 11.6;
}; };
name = Release; name = Release;
}; };
@@ -1487,6 +1785,16 @@
defaultConfigurationIsVisible = 0; defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release; defaultConfigurationName = Release;
}; };
7310A7E32EB10963002C0FD3 /* Build configuration list for PBXNativeTarget "Solian Watch App" */ = {
isa = XCConfigurationList;
buildConfigurations = (
7310A7E02EB10963002C0FD3 /* Debug */,
7310A7E12EB10963002C0FD3 /* Release */,
7310A7E22EB10963002C0FD3 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
73ACDFCB2E3D0E6100B63535 /* Build configuration list for PBXNativeTarget "SolianBroadcastExtension" */ = { 73ACDFCB2E3D0E6100B63535 /* Build configuration list for PBXNativeTarget "SolianBroadcastExtension" */ = {
isa = XCConfigurationList; isa = XCConfigurationList;
buildConfigurations = ( buildConfigurations = (

View File

@@ -20,6 +20,20 @@
ReferencedContainer = "container:Runner.xcodeproj"> ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference> </BuildableReference>
</BuildActionEntry> </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> </BuildActionEntries>
</BuildAction> </BuildAction>
<TestAction <TestAction

View File

@@ -1,9 +1,11 @@
import Flutter import Flutter
import UIKit import UIKit
import WatchConnectivity
@main @main
@objc class AppDelegate: FlutterAppDelegate { @objc class AppDelegate: FlutterAppDelegate {
let notifyDelegate = NotifyDelegate() let notifyDelegate = NotifyDelegate()
private static var sharedWatchConnectivityService: WatchConnectivityService?
override func application( override func application(
_ application: UIApplication, _ application: UIApplication,
@@ -23,11 +25,85 @@ import UIKit
intentIdentifiers: [], intentIdentifiers: [],
options: [] options: []
) )
UNUserNotificationCenter.current().setNotificationCategories([replyableMessageCategory]) UNUserNotificationCenter.current().setNotificationCategories([replyableMessageCategory])
GeneratedPluginRegistrant.register(with: self) GeneratedPluginRegistrant.register(with: self)
// Always initialize and retain a strong reference
if WCSession.isSupported() {
AppDelegate.sharedWatchConnectivityService = WatchConnectivityService.shared
} else {
print("[iOS] WCSession not supported on this device.")
}
return super.application(application, didFinishLaunchingWithOptions: launchOptions) return super.application(application, didFinishLaunchingWithOptions: launchOptions)
} }
} }
final class WatchConnectivityService: NSObject, WCSessionDelegate {
static let shared = WatchConnectivityService()
private let session: WCSession = .default
private override init() {
super.init()
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: \(error.localizedDescription)")
} else {
print("[iOS] WCSession activated with state: \(activationState.rawValue)")
if activationState == .activated {
sendDataToWatch()
}
}
}
func sessionDidBecomeInactive(_ session: WCSession) {}
func sessionDidDeactivate(_ session: WCSession) {
session.activate()
}
func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: @escaping ([String : Any]) -> Void) {
print("[iOS] Received message: \(message)")
if let request = message["request"] as? String, request == "data" {
let token = UserDefaults.standard.getFlutterToken()
let serverUrl = UserDefaults.standard.getServerUrl()
var data: [String: Any] = ["serverUrl": serverUrl ?? ""]
if let token = token {
data["token"] = token
}
print("[iOS] Replying with data: \(data)")
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 +1,334 @@
{"images":[{"size":"20x20","idiom":"universal","filename":"Icon-App-20x20@2x.png","scale":"2x","platform":"ios"},{"size":"20x20","idiom":"universal","filename":"Icon-App-20x20@3x.png","scale":"3x","platform":"ios"},{"size":"29x29","idiom":"universal","filename":"Icon-App-29x29@2x.png","scale":"2x","platform":"ios"},{"size":"29x29","idiom":"universal","filename":"Icon-App-29x29@3x.png","scale":"3x","platform":"ios"},{"size":"38x38","idiom":"universal","filename":"Icon-App-38x38@2x.png","scale":"2x","platform":"ios"},{"size":"38x38","idiom":"universal","filename":"Icon-App-38x38@3x.png","scale":"3x","platform":"ios"},{"size":"40x40","idiom":"universal","filename":"Icon-App-40x40@2x.png","scale":"2x","platform":"ios"},{"size":"40x40","idiom":"universal","filename":"Icon-App-40x40@3x.png","scale":"3x","platform":"ios"},{"size":"60x60","idiom":"universal","filename":"Icon-App-60x60@2x.png","scale":"2x","platform":"ios"},{"size":"60x60","idiom":"universal","filename":"Icon-App-60x60@3x.png","scale":"3x","platform":"ios"},{"size":"64x64","idiom":"universal","filename":"Icon-App-64x64@2x.png","scale":"2x","platform":"ios"},{"size":"64x64","idiom":"universal","filename":"Icon-App-64x64@3x.png","scale":"3x","platform":"ios"},{"size":"68x68","idiom":"universal","filename":"Icon-App-68x68@2x.png","scale":"2x","platform":"ios"},{"size":"76x76","idiom":"universal","filename":"Icon-App-76x76@2x.png","scale":"2x","platform":"ios"},{"size":"83.5x83.5","idiom":"universal","filename":"Icon-App-83.5x83.5@2x.png","scale":"2x","platform":"ios"},{"size":"1024x1024","idiom":"universal","filename":"Icon-App-1024x1024@1x.png","scale":"1x","platform":"ios"},{"size":"1024x1024","idiom":"ios-marketing","filename":"Icon-App-1024x1024@1x.png","scale":"1x"},{"size":"20x20","idiom":"universal","filename":"Icon-App-Dark-20x20@2x.png","scale":"2x","platform":"ios","appearances":[{"appearance":"luminosity","value":"dark"}]},{"size":"20x20","idiom":"universal","filename":"Icon-App-Dark-20x20@3x.png","scale":"3x","platform":"ios","appearances":[{"appearance":"luminosity","value":"dark"}]},{"size":"29x29","idiom":"universal","filename":"Icon-App-Dark-29x29@2x.png","scale":"2x","platform":"ios","appearances":[{"appearance":"luminosity","value":"dark"}]},{"size":"29x29","idiom":"universal","filename":"Icon-App-Dark-29x29@3x.png","scale":"3x","platform":"ios","appearances":[{"appearance":"luminosity","value":"dark"}]},{"size":"38x38","idiom":"universal","filename":"Icon-App-Dark-38x38@2x.png","scale":"2x","platform":"ios","appearances":[{"appearance":"luminosity","value":"dark"}]},{"size":"38x38","idiom":"universal","filename":"Icon-App-Dark-38x38@3x.png","scale":"3x","platform":"ios","appearances":[{"appearance":"luminosity","value":"dark"}]},{"size":"40x40","idiom":"universal","filename":"Icon-App-Dark-40x40@2x.png","scale":"2x","platform":"ios","appearances":[{"appearance":"luminosity","value":"dark"}]},{"size":"40x40","idiom":"universal","filename":"Icon-App-Dark-40x40@3x.png","scale":"3x","platform":"ios","appearances":[{"appearance":"luminosity","value":"dark"}]},{"size":"60x60","idiom":"universal","filename":"Icon-App-Dark-60x60@2x.png","scale":"2x","platform":"ios","appearances":[{"appearance":"luminosity","value":"dark"}]},{"size":"60x60","idiom":"universal","filename":"Icon-App-Dark-60x60@3x.png","scale":"3x","platform":"ios","appearances":[{"appearance":"luminosity","value":"dark"}]},{"size":"64x64","idiom":"universal","filename":"Icon-App-Dark-64x64@2x.png","scale":"2x","platform":"ios","appearances":[{"appearance":"luminosity","value":"dark"}]},{"size":"64x64","idiom":"universal","filename":"Icon-App-Dark-64x64@3x.png","scale":"3x","platform":"ios","appearances":[{"appearance":"luminosity","value":"dark"}]},{"size":"68x68","idiom":"universal","filename":"Icon-App-Dark-68x68@2x.png","scale":"2x","platform":"ios","appearances":[{"appearance":"luminosity","value":"dark"}]},{"size":"76x76","idiom":"universal","filename":"Icon-App-Dark-76x76@2x.png","scale":"2x","platform":"ios","appearances":[{"appearance":"luminosity","value":"dark"}]},{"size":"83.5x83.5","idiom":"universal","filename":"Icon-App-Dark-83.5x83.5@2x.png","scale":"2x","platform":"ios","appearances":[{"appearance":"luminosity","value":"dark"}]},{"size":"1024x1024","idiom":"universal","filename":"Icon-App-Dark-1024x1024@1x.png","scale":"1x","platform":"ios","appearances":[{"appearance":"luminosity","value":"dark"}]}],"info":{"version":1,"author":"xcode"}} {
"images" : [
{
"filename" : "Icon-App-20x20@2x.png",
"idiom" : "universal",
"platform" : "ios",
"scale" : "2x",
"size" : "20x20"
},
{
"filename" : "Icon-App-20x20@3x.png",
"idiom" : "universal",
"platform" : "ios",
"scale" : "3x",
"size" : "20x20"
},
{
"filename" : "Icon-App-29x29@2x.png",
"idiom" : "universal",
"platform" : "ios",
"scale" : "2x",
"size" : "29x29"
},
{
"filename" : "Icon-App-29x29@3x.png",
"idiom" : "universal",
"platform" : "ios",
"scale" : "3x",
"size" : "29x29"
},
{
"filename" : "Icon-App-38x38@2x.png",
"idiom" : "universal",
"platform" : "ios",
"scale" : "2x",
"size" : "38x38"
},
{
"filename" : "Icon-App-38x38@3x.png",
"idiom" : "universal",
"platform" : "ios",
"scale" : "3x",
"size" : "38x38"
},
{
"filename" : "Icon-App-40x40@2x.png",
"idiom" : "universal",
"platform" : "ios",
"scale" : "2x",
"size" : "40x40"
},
{
"filename" : "Icon-App-40x40@3x.png",
"idiom" : "universal",
"platform" : "ios",
"scale" : "3x",
"size" : "40x40"
},
{
"filename" : "Icon-App-60x60@2x.png",
"idiom" : "universal",
"platform" : "ios",
"scale" : "2x",
"size" : "60x60"
},
{
"filename" : "Icon-App-60x60@3x.png",
"idiom" : "universal",
"platform" : "ios",
"scale" : "3x",
"size" : "60x60"
},
{
"filename" : "Icon-App-64x64@2x.png",
"idiom" : "universal",
"platform" : "ios",
"scale" : "2x",
"size" : "64x64"
},
{
"filename" : "Icon-App-64x64@3x.png",
"idiom" : "universal",
"platform" : "ios",
"scale" : "3x",
"size" : "64x64"
},
{
"filename" : "Icon-App-68x68@2x.png",
"idiom" : "universal",
"platform" : "ios",
"scale" : "2x",
"size" : "68x68"
},
{
"filename" : "Icon-App-76x76@2x.png",
"idiom" : "universal",
"platform" : "ios",
"scale" : "2x",
"size" : "76x76"
},
{
"filename" : "Icon-App-83.5x83.5@2x.png",
"idiom" : "universal",
"platform" : "ios",
"scale" : "2x",
"size" : "83.5x83.5"
},
{
"filename" : "Icon-App-1024x1024@1x.png",
"idiom" : "universal",
"platform" : "ios",
"scale" : "1x",
"size" : "1024x1024"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "Icon-App-Dark-20x20@2x.png",
"idiom" : "universal",
"platform" : "ios",
"scale" : "2x",
"size" : "20x20"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "Icon-App-Dark-20x20@3x.png",
"idiom" : "universal",
"platform" : "ios",
"scale" : "3x",
"size" : "20x20"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "Icon-App-Dark-29x29@2x.png",
"idiom" : "universal",
"platform" : "ios",
"scale" : "2x",
"size" : "29x29"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "Icon-App-Dark-29x29@3x.png",
"idiom" : "universal",
"platform" : "ios",
"scale" : "3x",
"size" : "29x29"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "Icon-App-Dark-38x38@2x.png",
"idiom" : "universal",
"platform" : "ios",
"scale" : "2x",
"size" : "38x38"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "Icon-App-Dark-38x38@3x.png",
"idiom" : "universal",
"platform" : "ios",
"scale" : "3x",
"size" : "38x38"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "Icon-App-Dark-40x40@2x.png",
"idiom" : "universal",
"platform" : "ios",
"scale" : "2x",
"size" : "40x40"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "Icon-App-Dark-40x40@3x.png",
"idiom" : "universal",
"platform" : "ios",
"scale" : "3x",
"size" : "40x40"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "Icon-App-Dark-60x60@2x.png",
"idiom" : "universal",
"platform" : "ios",
"scale" : "2x",
"size" : "60x60"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "Icon-App-Dark-60x60@3x.png",
"idiom" : "universal",
"platform" : "ios",
"scale" : "3x",
"size" : "60x60"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "Icon-App-Dark-64x64@2x.png",
"idiom" : "universal",
"platform" : "ios",
"scale" : "2x",
"size" : "64x64"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "Icon-App-Dark-64x64@3x.png",
"idiom" : "universal",
"platform" : "ios",
"scale" : "3x",
"size" : "64x64"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "Icon-App-Dark-68x68@2x.png",
"idiom" : "universal",
"platform" : "ios",
"scale" : "2x",
"size" : "68x68"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "Icon-App-Dark-76x76@2x.png",
"idiom" : "universal",
"platform" : "ios",
"scale" : "2x",
"size" : "76x76"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "Icon-App-Dark-83.5x83.5@2x.png",
"idiom" : "universal",
"platform" : "ios",
"scale" : "2x",
"size" : "83.5x83.5"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "Icon-App-Dark-1024x1024@1x.png",
"idiom" : "universal",
"platform" : "ios",
"scale" : "1x",
"size" : "1024x1024"
},
{
"filename" : "Icon-App-1024x1024@1x.png",
"idiom" : "ios-marketing",
"scale" : "1x",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 295 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 282 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 762 B

View File

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

View File

@@ -8,7 +8,7 @@
import Foundation import Foundation
func getAttachmentUrl(for identifier: String) -> String { func getAttachmentUrl(for identifier: String) -> String {
let serverBaseUrl = "https://api.solian.app" let serverBaseUrl = UserDefaults.standard.getServerUrl()
return identifier.starts(with: "http") ? identifier : "\(serverBaseUrl)/drive/files/\(identifier)" return identifier.starts(with: "http") ? identifier : "\(serverBaseUrl)/drive/files/\(identifier)"
} }

View File

@@ -26,6 +26,6 @@ extension UserDefaults {
} }
func getServerUrl(forKey key: String = "app_server_url") -> String { func getServerUrl(forKey key: String = "app_server_url") -> String {
return self.getFlutterValue(forKey: key) ?? "https://nt.solian.app" return self.getFlutterValue(forKey: key) ?? "https://api.solian.app"
} }
} }

View File

@@ -0,0 +1,15 @@
{
"colors" : [
{
"color" : {
"platform" : "universal",
"reference" : "systemIndigoColor"
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,318 @@
{
"images" : [
{
"filename" : "icon-ios-20x20@2x.png",
"idiom" : "universal",
"platform" : "ios",
"scale" : "2x",
"size" : "20x20"
},
{
"filename" : "icon-ios-20x20@3x.png",
"idiom" : "universal",
"platform" : "ios",
"scale" : "3x",
"size" : "20x20"
},
{
"filename" : "icon-ios-29x29@2x.png",
"idiom" : "universal",
"platform" : "ios",
"scale" : "2x",
"size" : "29x29"
},
{
"filename" : "icon-ios-29x29@3x.png",
"idiom" : "universal",
"platform" : "ios",
"scale" : "3x",
"size" : "29x29"
},
{
"filename" : "icon-ios-38x38@2x.png",
"idiom" : "universal",
"platform" : "ios",
"scale" : "2x",
"size" : "38x38"
},
{
"filename" : "icon-ios-38x38@3x.png",
"idiom" : "universal",
"platform" : "ios",
"scale" : "3x",
"size" : "38x38"
},
{
"filename" : "icon-ios-40x40@2x.png",
"idiom" : "universal",
"platform" : "ios",
"scale" : "2x",
"size" : "40x40"
},
{
"filename" : "icon-ios-40x40@3x.png",
"idiom" : "universal",
"platform" : "ios",
"scale" : "3x",
"size" : "40x40"
},
{
"filename" : "icon-ios-60x60@2x.png",
"idiom" : "universal",
"platform" : "ios",
"scale" : "2x",
"size" : "60x60"
},
{
"filename" : "icon-ios-60x60@3x.png",
"idiom" : "universal",
"platform" : "ios",
"scale" : "3x",
"size" : "60x60"
},
{
"filename" : "icon-ios-64x64@2x.png",
"idiom" : "universal",
"platform" : "ios",
"scale" : "2x",
"size" : "64x64"
},
{
"filename" : "icon-ios-64x64@3x.png",
"idiom" : "universal",
"platform" : "ios",
"scale" : "3x",
"size" : "64x64"
},
{
"filename" : "icon-ios-68x68@2x.png",
"idiom" : "universal",
"platform" : "ios",
"scale" : "2x",
"size" : "68x68"
},
{
"filename" : "icon-ios-76x76@2x.png",
"idiom" : "universal",
"platform" : "ios",
"scale" : "2x",
"size" : "76x76"
},
{
"filename" : "icon-ios-83.5x83.5@2x.png",
"idiom" : "universal",
"platform" : "ios",
"scale" : "2x",
"size" : "83.5x83.5"
},
{
"filename" : "icon-ios-1024x1024.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"filename" : "icon-mac-16x16.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "16x16"
},
{
"filename" : "icon-mac-16x16@2x.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "16x16"
},
{
"filename" : "icon-mac-32x32.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "32x32"
},
{
"filename" : "icon-mac-32x32@2x.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "32x32"
},
{
"filename" : "icon-mac-128x128.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "128x128"
},
{
"filename" : "icon-mac-128x128@2x.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "128x128"
},
{
"filename" : "icon-mac-256x256.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "256x256"
},
{
"filename" : "icon-mac-256x256@2x.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "256x256"
},
{
"filename" : "icon-mac-512x512.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "512x512"
},
{
"filename" : "icon-mac-512x512@2x.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "512x512"
},
{
"filename" : "icon-watchos-22x22@2x.png",
"idiom" : "universal",
"platform" : "watchos",
"scale" : "2x",
"size" : "22x22"
},
{
"filename" : "icon-watchos-24x24@2x.png",
"idiom" : "universal",
"platform" : "watchos",
"scale" : "2x",
"size" : "24x24"
},
{
"filename" : "icon-watchos-27.5x27.5@2x.png",
"idiom" : "universal",
"platform" : "watchos",
"scale" : "2x",
"size" : "27.5x27.5"
},
{
"filename" : "icon-watchos-29x29@2x.png",
"idiom" : "universal",
"platform" : "watchos",
"scale" : "2x",
"size" : "29x29"
},
{
"filename" : "icon-watchos-30x30@2x.png",
"idiom" : "universal",
"platform" : "watchos",
"scale" : "2x",
"size" : "30x30"
},
{
"filename" : "icon-watchos-32x32@2x.png",
"idiom" : "universal",
"platform" : "watchos",
"scale" : "2x",
"size" : "32x32"
},
{
"filename" : "icon-watchos-33x33@2x.png",
"idiom" : "universal",
"platform" : "watchos",
"scale" : "2x",
"size" : "33x33"
},
{
"filename" : "icon-watchos-40x40@2x.png",
"idiom" : "universal",
"platform" : "watchos",
"scale" : "2x",
"size" : "40x40"
},
{
"filename" : "icon-watchos-43.5x43.5@2x.png",
"idiom" : "universal",
"platform" : "watchos",
"scale" : "2x",
"size" : "43.5x43.5"
},
{
"filename" : "icon-watchos-44x44@2x.png",
"idiom" : "universal",
"platform" : "watchos",
"scale" : "2x",
"size" : "44x44"
},
{
"filename" : "icon-watchos-46x46@2x.png",
"idiom" : "universal",
"platform" : "watchos",
"scale" : "2x",
"size" : "46x46"
},
{
"filename" : "icon-watchos-50x50@2x.png",
"idiom" : "universal",
"platform" : "watchos",
"scale" : "2x",
"size" : "50x50"
},
{
"filename" : "icon-watchos-51x51@2x.png",
"idiom" : "universal",
"platform" : "watchos",
"scale" : "2x",
"size" : "51x51"
},
{
"filename" : "icon-watchos-54x54@2x.png",
"idiom" : "universal",
"platform" : "watchos",
"scale" : "2x",
"size" : "54x54"
},
{
"filename" : "icon-watchos-86x86@2x.png",
"idiom" : "universal",
"platform" : "watchos",
"scale" : "2x",
"size" : "86x86"
},
{
"filename" : "icon-watchos-98x98@2x.png",
"idiom" : "universal",
"platform" : "watchos",
"scale" : "2x",
"size" : "98x98"
},
{
"filename" : "icon-watchos-108x108@2x.png",
"idiom" : "universal",
"platform" : "watchos",
"scale" : "2x",
"size" : "108x108"
},
{
"filename" : "icon-watchos-117x117@2x.png",
"idiom" : "universal",
"platform" : "watchos",
"scale" : "2x",
"size" : "117x117"
},
{
"filename" : "icon-watchos-129x129@2x.png",
"idiom" : "universal",
"platform" : "watchos",
"scale" : "2x",
"size" : "129x129"
},
{
"filename" : "icon-watchos-1024x1024.png",
"idiom" : "universal",
"platform" : "watchos",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 473 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "icon.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

View File

@@ -0,0 +1,58 @@
//
// ContentView.swift
// WatchRunner Watch App
//
// Created by LittleSheep on 2025/10/28.
//
import SwiftUI
// The root view of the app.
struct ContentView: View {
@StateObject private var appState = AppState()
@State private var selection: Panel? = .explore
enum Panel: Hashable {
case explore
case chat
case notifications
case account
}
var body: some View {
NavigationSplitView {
List(selection: $selection) {
AppInfoHeaderView()
.listRowBackground(Color.clear)
.environmentObject(appState)
Label("Explore", systemImage: "globe.fill").tag(Panel.explore)
Label("Chat", systemImage: "message.fill").tag(Panel.chat)
Label("Notifications", systemImage: "bell.fill").tag(Panel.notifications)
Label("Account", systemImage: "person.circle.fill").tag(Panel.account)
}
.listStyle(.automatic)
} detail: {
switch selection {
case .explore:
ExploreView().environmentObject(appState)
case .chat:
ChatView().environmentObject(appState)
case .notifications:
NotificationView().environmentObject(appState)
case .account:
AccountView().environmentObject(appState)
case .none:
Text("Select a panel")
}
}
}
}
// --- Placeholder Implementations for Preview ---
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

View File

@@ -0,0 +1,88 @@
//
// FlowLayout.swift
// WatchRunner Watch App
//
// Created by LittleSheep on 2025/10/29.
//
import SwiftUI
// MARK: - Custom Layouts
struct FlowLayout: Layout {
var alignment: HorizontalAlignment = .leading
var spacing: CGFloat = 10
func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize {
let containerWidth = proposal.width ?? 0
let sizes = subviews.map { $0.sizeThatFits(.unspecified) }
var currentX: CGFloat = 0
var currentY: CGFloat = 0
var lineHeight: CGFloat = 0
var totalHeight: CGFloat = 0
for size in sizes {
if currentX + size.width > containerWidth {
// New line
currentX = 0
currentY += lineHeight + spacing
totalHeight = currentY + size.height
lineHeight = 0
}
currentX += size.width + spacing
lineHeight = max(lineHeight, size.height)
}
totalHeight = currentY + lineHeight
return CGSize(width: containerWidth, height: totalHeight)
}
func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) {
let containerWidth = bounds.width
let sizes = subviews.map { $0.sizeThatFits(.unspecified) }
var currentX: CGFloat = 0
var currentY: CGFloat = 0
var lineHeight: CGFloat = 0
var lineElements: [(offset: Int, size: CGSize)] = []
func placeLine() {
let lineWidth = lineElements.map { $0.size.width }.reduce(0, +) + CGFloat(lineElements.count - 1) * spacing
var startX: CGFloat = 0
switch alignment {
case .leading:
startX = bounds.minX
case .center:
startX = bounds.minX + (containerWidth - lineWidth) / 2
case .trailing:
startX = bounds.maxX - lineWidth
default:
startX = bounds.minX
}
var xOffset = startX
for (offset, size) in lineElements {
subviews[offset].place(at: CGPoint(x: xOffset, y: bounds.minY + currentY), proposal: ProposedViewSize(size)) // Use bounds.minY + currentY
xOffset += size.width + spacing
}
lineElements.removeAll() // Clear elements for the next line
}
for (offset, size) in sizes.enumerated() {
if currentX + size.width > containerWidth && !lineElements.isEmpty {
// New line
placeLine()
currentX = 0
currentY += lineHeight + spacing
lineHeight = 0
}
lineElements.append((offset, size))
currentX += size.width + spacing
lineHeight = max(lineHeight, size.height)
}
placeLine() // Place the last line
}
}

View File

@@ -0,0 +1,365 @@
// Models.swift
// WatchRunner Watch App
//
// Created by LittleSheep on 2025/10/29.
//
import Foundation
// MARK: - Models
struct AppToken: Codable {
let token: String
}
struct SnActivity: Codable, Identifiable {
let id: String
let type: String
let data: ActivityData?
let createdAt: Date
}
enum ActivityData: Codable {
case post(SnPost)
case discovery(DiscoveryData)
case unknown
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let post = try? container.decode(SnPost.self) {
self = .post(post)
return
}
if let discoveryData = try? container.decode(DiscoveryData.self) {
self = .discovery(discoveryData)
return
}
self = .unknown
}
func encode(to encoder: Encoder) throws {
// Not needed for decoding
}
}
struct SnPost: Codable, Identifiable {
let id: String
let title: String?
let content: String?
let publisher: SnPublisher
let attachments: [SnCloudFile]
let tags: [SnPostTag]
}
struct DiscoveryData: Codable {
let items: [DiscoveryItem]
}
struct DiscoveryItem: Codable, Identifiable {
var id = UUID()
let type: String
let data: DiscoveryItemData
enum CodingKeys: String, CodingKey {
case type, data
}
}
enum DiscoveryItemData: Codable {
case realm(SnRealm)
case publisher(SnPublisher)
case article(SnWebArticle)
case unknown
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let realm = try? container.decode(SnRealm.self) {
self = .realm(realm)
return
}
if let publisher = try? container.decode(SnPublisher.self) {
self = .publisher(publisher)
return
}
if let article = try? container.decode(SnWebArticle.self) {
self = .article(article)
return
}
self = .unknown
}
func encode(to encoder: Encoder) throws {
// Not needed for decoding
}
}
struct SnRealm: Codable, Identifiable {
let id: String
let name: String
let description: String?
}
struct SnPublisher: Codable, Identifiable {
let id: String
let name: String
let nick: String?
let description: String?
let picture: SnCloudFile?
}
struct SnCloudFile: Codable, Identifiable {
let id: String
let mimeType: String?
}
struct SnPostTag: Codable, Identifiable {
let id: String
let slug: String
let name: String?
}
struct SnWebArticle: Codable, Identifiable {
let id: String
let title: String
let url: String
}
struct SnNotification: Codable, Identifiable {
let id: String
let topic: String
let title: String
let subtitle: String
let content: String
let meta: [String: AnyCodable]?
let priority: Int
let viewedAt: Date?
let accountId: String
let createdAt: Date
let updatedAt: Date
let deletedAt: Date?
enum CodingKeys: String, CodingKey {
case id
case topic
case title
case subtitle
case content
case meta
case priority
case viewedAt = "viewedAt"
case accountId = "accountId"
case createdAt = "createdAt"
case updatedAt = "updatedAt"
case deletedAt = "deletedAt"
}
}
struct AnyCodable: Codable {
let value: Any
init(_ value: Any) {
self.value = value
}
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let intValue = try? container.decode(Int.self) {
value = intValue
} else if let doubleValue = try? container.decode(Double.self) {
value = doubleValue
} else if let boolValue = try? container.decode(Bool.self) {
value = boolValue
} else if let stringValue = try? container.decode(String.self) {
value = stringValue
} else if let arrayValue = try? container.decode([AnyCodable].self) {
value = arrayValue
} else if let dictValue = try? container.decode([String: AnyCodable].self) {
value = dictValue
} else {
value = NSNull()
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch value {
case let intValue as Int:
try container.encode(intValue)
case let doubleValue as Double:
try container.encode(doubleValue)
case let boolValue as Bool:
try container.encode(boolValue)
case let stringValue as String:
try container.encode(stringValue)
case let arrayValue as [AnyCodable]:
try container.encode(arrayValue)
case let dictValue as [String: AnyCodable]:
try container.encode(dictValue)
default:
try container.encodeNil()
}
}
}
struct NotificationResponse {
let notifications: [SnNotification]
let total: Int
let hasMore: Bool
}
struct ActivityResponse {
let activities: [SnActivity]
let hasMore: Bool
let nextCursor: String?
}
struct SnAccount: Codable {
let id: String
let name: String
let nick: String
let profile: SnUserProfile
let createdAt: Date
}
struct SnUserProfile: Codable {
let bio: String?
let picture: SnCloudFile?
let background: SnCloudFile?
let level: Int
let experience: Int
let levelingProgress: Double
}
struct SnAccountStatus: Codable {
let id: String
let attitude: Int
let isOnline: Bool
let isInvisible: Bool
let isNotDisturb: Bool
let isCustomized: Bool
let label: String
let meta: [String: AnyCodable]?
let clearedAt: Date?
let accountId: String
let createdAt: Date
let updatedAt: Date
let deletedAt: Date?
}
// MARK: - Chat Models
struct SnChatRoom: Codable, Identifiable {
let id: String
let name: String?
let description: String?
let type: Int
let isPublic: Bool
let isCommunity: Bool
let picture: SnCloudFile?
let background: SnCloudFile?
let realmId: String?
let realm: SnRealm?
let createdAt: Date
let updatedAt: Date
let deletedAt: Date?
let members: [SnChatMember]?
}
struct SnChatMessage: Codable, Identifiable {
let id: String
let type: String
let content: String?
let nonce: String?
let meta: [String: AnyCodable]
let membersMentioned: [String]?
let editedAt: Date?
let attachments: [SnCloudFile]
let reactions: [SnChatReaction]
let repliedMessageId: String?
let forwardedMessageId: String?
let senderId: String
let sender: SnChatMember
let chatRoomId: String
let createdAt: Date
let updatedAt: Date
let deletedAt: Date?
enum CodingKeys: String, CodingKey {
case id, type, content, nonce, meta, membersMentioned, editedAt, attachments, reactions, repliedMessageId, forwardedMessageId, senderId, sender, chatRoomId, createdAt, updatedAt, deletedAt
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(String.self, forKey: .id)
type = try container.decode(String.self, forKey: .type)
content = try container.decodeIfPresent(String.self, forKey: .content)
nonce = try container.decodeIfPresent(String.self, forKey: .nonce)
meta = try container.decode([String: AnyCodable].self, forKey: .meta)
membersMentioned = try container.decodeIfPresent([String].self, forKey: .membersMentioned) ?? []
editedAt = try container.decodeIfPresent(Date.self, forKey: .editedAt)
attachments = try container.decode([SnCloudFile].self, forKey: .attachments)
reactions = try container.decode([SnChatReaction].self, forKey: .reactions)
repliedMessageId = try container.decodeIfPresent(String.self, forKey: .repliedMessageId)
forwardedMessageId = try container.decodeIfPresent(String.self, forKey: .forwardedMessageId)
senderId = try container.decode(String.self, forKey: .senderId)
sender = try container.decode(SnChatMember.self, forKey: .sender)
chatRoomId = try container.decode(String.self, forKey: .chatRoomId)
createdAt = try container.decode(Date.self, forKey: .createdAt)
updatedAt = try container.decode(Date.self, forKey: .updatedAt)
deletedAt = try container.decodeIfPresent(Date.self, forKey: .deletedAt)
}
}
struct SnChatReaction: Codable, Identifiable {
let id: String
let messageId: String
let senderId: String
let sender: SnChatMember
let symbol: String
let attitude: Int
let createdAt: Date
let updatedAt: Date
let deletedAt: Date?
}
struct SnChatMember: Codable, Identifiable {
let id: String
let chatRoomId: String
let chatRoom: SnChatRoom?
let accountId: String
let account: SnAccount
let nick: String?
let role: Int
let notify: Int
let joinedAt: Date?
let breakUntil: Date?
let timeoutUntil: Date?
let isBot: Bool
let status: SnAccountStatus?
let createdAt: Date
let updatedAt: Date
let deletedAt: Date?
}
struct SnChatSummary: Codable {
let unreadCount: Int
let lastMessage: SnChatMessage?
}
struct ChatRoomsResponse {
let rooms: [SnChatRoom]
}
struct ChatInvitesResponse {
let invites: [SnChatMember]
}
struct MessageSyncResponse: Codable {
let messages: [SnChatMessage]
let currentTimestamp: Date
enum CodingKeys: String, CodingKey {
case messages
case currentTimestamp = "current_timestamp"
}
}

View File

@@ -0,0 +1,95 @@
//
// ImageLoader.swift
// WatchRunner Watch App
//
// Created by LittleSheep on 2025/10/29.
//
import SwiftUI
import Kingfisher
import KingfisherWebP
import Combine
// MARK: - Image Loader
@MainActor
class ImageLoader: ObservableObject {
@Published var image: Image?
@Published var errorMessage: String?
@Published var isLoading = false
private var currentTask: DownloadTask?
init() {}
deinit {
currentTask?.cancel()
}
func loadImage(from initialUrl: URL, token: String) async {
isLoading = true
errorMessage = nil
image = nil
// Create request modifier for authorization
let modifier = AnyModifier { request in
var r = request
r.setValue("AtField \(token)", forHTTPHeaderField: "Authorization")
r.setValue("SolianWatch/1.0", forHTTPHeaderField: "User-Agent")
return r
}
// Use WebP processor as default since the app seems to handle WebP images
let processor = WebPProcessor.default
// Use KingfisherManager to retrieve image with caching
currentTask = KingfisherManager.shared.retrieveImage(
with: initialUrl,
options: [
.requestModifier(modifier),
.processor(processor),
.cacheOriginalImage, // Cache the original image data
.loadDiskFileSynchronously // Load from disk cache synchronously if available
]
) { [weak self] result in
guard let self = self else { return }
Task { @MainActor in
switch result {
case .success(let value):
self.image = Image(uiImage: value.image)
self.isLoading = false
case .failure(_):
// If WebP processor fails (likely due to format), try with default processor
let defaultProcessor = DefaultImageProcessor.default
self.currentTask = KingfisherManager.shared.retrieveImage(
with: initialUrl,
options: [
.requestModifier(modifier),
.processor(defaultProcessor),
.cacheOriginalImage,
.loadDiskFileSynchronously
]
) { [weak self] fallbackResult in
guard let self = self else { return }
Task { @MainActor in
switch fallbackResult {
case .success(let value):
self.image = Image(uiImage: value.image)
case .failure(let fallbackError):
self.errorMessage = fallbackError.localizedDescription
print("[watchOS] Image loading failed: \(fallbackError.localizedDescription)")
}
self.isLoading = false
}
}
}
}
}
}
func cancel() {
currentTask?.cancel()
}
}

View File

@@ -0,0 +1,643 @@
//
// NetworkService.swift
// WatchRunner Watch App
//
// Created by LittleSheep on 2025/10/29. //
import Combine
import Foundation
// MARK: - WebSocket Data Structures
enum WebSocketState: Equatable {
case connected
case connecting
case disconnected
case serverDown
case duplicateDevice
case error(String)
// Equatable conformance
static func == (lhs: WebSocketState, rhs: WebSocketState) -> Bool {
switch (lhs, rhs) {
case (.connected, .connected),
(.connecting, .connecting),
(.disconnected, .disconnected),
(.serverDown, .serverDown),
(.duplicateDevice, .duplicateDevice):
return true
case let (.error(a), .error(b)):
return a == b
default:
return false
}
}
}
struct WebSocketPacket {
let type: String
let data: [String: Any]?
let endpoint: String?
let errorMessage: String?
}
// MARK: - Network Service
class NetworkService {
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")
func fetchActivities(filter: String, cursor: String? = nil, token: String, serverUrl: String) async throws -> ActivityResponse {
guard let baseURL = URL(string: serverUrl) else {
throw URLError(.badURL)
}
var components = URLComponents(url: baseURL.appendingPathComponent("/sphere/activities"), resolvingAgainstBaseURL: false)!
var queryItems = [URLQueryItem(name: "take", value: "20")]
if filter.lowercased() != "explore" {
queryItems.append(URLQueryItem(name: "filter", value: filter.lowercased()))
}
if let cursor = cursor {
queryItems.append(URLQueryItem(name: "cursor", value: cursor))
}
components.queryItems = queryItems
var request = URLRequest(url: components.url!)
request.httpMethod = "GET"
request.setValue("application/json", forHTTPHeaderField: "Accept")
request.setValue("AtField \(token)", forHTTPHeaderField: "Authorization")
request.setValue("SolianWatch/1.0", forHTTPHeaderField: "User-Agent")
let (data, _) = try await session.data(for: request)
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
decoder.keyDecodingStrategy = .convertFromSnakeCase
let activities = try decoder.decode([SnActivity].self, from: data)
let hasMore = (activities.first?.type ?? "empty") != "empty"
let nextCursor = activities.isEmpty ? nil : activities.map { $0.createdAt }.min()?.ISO8601Format()
return ActivityResponse(activities: activities, hasMore: hasMore, nextCursor: nextCursor)
}
func createPost(title: String, content: String, token: String, serverUrl: String) async throws {
guard let baseURL = URL(string: serverUrl) else {
throw URLError(.badURL)
}
let url = baseURL.appendingPathComponent("/sphere/posts")
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("application/json", forHTTPHeaderField: "Accept")
request.setValue("AtField \(token)", forHTTPHeaderField: "Authorization")
request.setValue("SolianWatch/1.0", forHTTPHeaderField: "User-Agent")
let body: [String: Any] = ["title": title, "content": content]
request.httpBody = try JSONSerialization.data(withJSONObject: body)
let (data, response) = try await session.data(for: request)
if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode != 201 {
let responseBody = String(data: data, encoding: .utf8) ?? ""
print("[watchOS] createPost failed with status code: \(httpResponse.statusCode), body: \(responseBody)")
throw URLError(URLError.Code(rawValue: httpResponse.statusCode))
}
}
func fetchNotifications(offset: Int = 0, take: Int = 20, token: String, serverUrl: String) async throws -> NotificationResponse {
guard let baseURL = URL(string: serverUrl) else {
throw URLError(.badURL)
}
var components = URLComponents(url: baseURL.appendingPathComponent("/ring/notifications"), resolvingAgainstBaseURL: false)!
let queryItems = [URLQueryItem(name: "offset", value: String(offset)), URLQueryItem(name: "take", value: String(take))]
components.queryItems = queryItems
var request = URLRequest(url: components.url!)
request.httpMethod = "GET"
request.setValue("application/json", forHTTPHeaderField: "Accept")
request.setValue("AtField \(token)", forHTTPHeaderField: "Authorization")
request.setValue("SolianWatch/1.0", forHTTPHeaderField: "User-Agent")
let (data, response) = try await session.data(for: request)
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
decoder.keyDecodingStrategy = .convertFromSnakeCase
let notifications = try decoder.decode([SnNotification].self, from: data)
let httpResponse = response as? HTTPURLResponse
let total = Int(httpResponse?.value(forHTTPHeaderField: "X-Total") ?? "0") ?? 0
let hasMore = offset + notifications.count < total
return NotificationResponse(notifications: notifications, total: total, hasMore: hasMore)
}
func fetchUserProfile(token: String, serverUrl: String) async throws -> SnAccount {
guard let baseURL = URL(string: serverUrl) else {
throw URLError(.badURL)
}
let url = baseURL.appendingPathComponent("/pass/accounts/me")
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("application/json", forHTTPHeaderField: "Accept")
request.setValue("AtField \(token)", forHTTPHeaderField: "Authorization")
request.setValue("SolianWatch/1.0", forHTTPHeaderField: "User-Agent")
let (data, _) = try await session.data(for: request)
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
decoder.keyDecodingStrategy = .convertFromSnakeCase
return try decoder.decode(SnAccount.self, from: data)
}
func fetchAccountStatus(token: String, serverUrl: String) async throws -> SnAccountStatus? {
guard let baseURL = URL(string: serverUrl) else {
throw URLError(.badURL)
}
let url = baseURL.appendingPathComponent("/pass/accounts/me/statuses")
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("application/json", forHTTPHeaderField: "Accept")
request.setValue("AtField \(token)", forHTTPHeaderField: "Authorization")
request.setValue("SolianWatch/1.0", forHTTPHeaderField: "User-Agent")
let (data, response) = try await session.data(for: request)
if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 404 {
return nil
}
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
decoder.keyDecodingStrategy = .convertFromSnakeCase
return try decoder.decode(SnAccountStatus.self, from: data)
}
func createOrUpdateStatus(attitude: Int, isInvisible: Bool, isNotDisturb: Bool, label: String?, token: String, serverUrl: String) async throws -> SnAccountStatus {
// Check if there\'s already a customized status
let existingStatus = try? await fetchAccountStatus(token: token, serverUrl: serverUrl)
let method = (existingStatus?.isCustomized == true) ? "PATCH" : "POST"
guard let baseURL = URL(string: serverUrl) else {
throw URLError(.badURL)
}
let url = baseURL.appendingPathComponent("/pass/accounts/me/statuses")
var request = URLRequest(url: url)
request.httpMethod = method
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("application/json", forHTTPHeaderField: "Accept")
request.setValue("AtField \(token)", forHTTPHeaderField: "Authorization")
request.setValue("SolianWatch/1.0", forHTTPHeaderField: "User-Agent")
var body: [String: Any] = [
"attitude": attitude,
"is_invisible": isInvisible,
"is_not_disturb": isNotDisturb,
]
if let label = label, !label.isEmpty {
body["label"] = label
}
request.httpBody = try JSONSerialization.data(withJSONObject: body)
let (data, response) = try await session.data(for: request)
if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode != 201 && httpResponse.statusCode != 200 {
let responseBody = String(data: data, encoding: .utf8) ?? ""
print("[watchOS] createOrUpdateStatus failed with status code: \(httpResponse.statusCode), body: \(responseBody)")
throw URLError(URLError.Code(rawValue: httpResponse.statusCode))
}
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
decoder.keyDecodingStrategy = .convertFromSnakeCase
return try decoder.decode(SnAccountStatus.self, from: data)
}
func clearStatus(token: String, serverUrl: String) async throws {
guard let baseURL = URL(string: serverUrl) else {
throw URLError(.badURL)
}
let url = baseURL.appendingPathComponent("/pass/accounts/me/statuses")
var request = URLRequest(url: url)
request.httpMethod = "DELETE"
request.setValue("application/json", forHTTPHeaderField: "Accept")
request.setValue("AtField \(token)", forHTTPHeaderField: "Authorization")
request.setValue("SolianWatch/1.0", forHTTPHeaderField: "User-Agent")
let (data, response) = try await session.data(for: request)
if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode != 204 {
let responseBody = String(data: data, encoding: .utf8) ?? ""
print("[watchOS] clearStatus failed with status code: \(httpResponse.statusCode), body: \(responseBody)")
throw URLError(URLError.Code(rawValue: httpResponse.statusCode))
}
}
// MARK: - Chat API Methods
func fetchChatRooms(token: String, serverUrl: String) async throws -> ChatRoomsResponse {
guard let baseURL = URL(string: serverUrl) else {
throw URLError(.badURL)
}
let url = baseURL.appendingPathComponent("/sphere/chat")
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("application/json", forHTTPHeaderField: "Accept")
request.setValue("AtField \(token)", forHTTPHeaderField: "Authorization")
request.setValue("SolianWatch/1.0", forHTTPHeaderField: "User-Agent")
let (data, _) = try await session.data(for: request)
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
decoder.keyDecodingStrategy = .convertFromSnakeCase
let rooms = try decoder.decode([SnChatRoom].self, from: data)
return ChatRoomsResponse(rooms: rooms)
}
func fetchChatRoom(identifier: String, token: String, serverUrl: String) async throws -> SnChatRoom {
guard let baseURL = URL(string: serverUrl) else {
throw URLError(.badURL)
}
let url = baseURL.appendingPathComponent("/sphere/chat/\(identifier)")
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("application/json", forHTTPHeaderField: "Accept")
request.setValue("AtField \(token)", forHTTPHeaderField: "Authorization")
request.setValue("SolianWatch/1.0", forHTTPHeaderField: "User-Agent")
let (data, response) = try await session.data(for: request)
if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 404 {
throw URLError(.resourceUnavailable)
}
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
decoder.keyDecodingStrategy = .convertFromSnakeCase
return try decoder.decode(SnChatRoom.self, from: data)
}
func fetchChatInvites(token: String, serverUrl: String) async throws -> ChatInvitesResponse {
guard let baseURL = URL(string: serverUrl) else {
throw URLError(.badURL)
}
let url = baseURL.appendingPathComponent("/sphere/chat/invites")
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("application/json", forHTTPHeaderField: "Accept")
request.setValue("AtField \(token)", forHTTPHeaderField: "Authorization")
request.setValue("SolianWatch/1.0", forHTTPHeaderField: "User-Agent")
let (data, _) = try await session.data(for: request)
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
decoder.keyDecodingStrategy = .convertFromSnakeCase
let invites = try decoder.decode([SnChatMember].self, from: data)
return ChatInvitesResponse(invites: invites)
}
func acceptChatInvite(chatRoomId: String, token: String, serverUrl: String) async throws {
guard let baseURL = URL(string: serverUrl) else {
throw URLError(.badURL)
}
let url = baseURL.appendingPathComponent("/sphere/chat/invites/\(chatRoomId)/accept")
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Accept")
request.setValue("AtField \(token)", forHTTPHeaderField: "Authorization")
request.setValue("SolianWatch/1.0", forHTTPHeaderField: "User-Agent")
let (data, response) = try await session.data(for: request)
if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode != 200 {
let responseBody = String(data: data, encoding: .utf8) ?? ""
print("[watchOS] acceptChatInvite failed with status code: \(httpResponse.statusCode), body: \(responseBody)")
throw URLError(URLError.Code(rawValue: httpResponse.statusCode))
}
}
func declineChatInvite(chatRoomId: String, token: String, serverUrl: String) async throws {
guard let baseURL = URL(string: serverUrl) else {
throw URLError(.badURL)
}
let url = baseURL.appendingPathComponent("/sphere/chat/invites/\(chatRoomId)/decline")
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Accept")
request.setValue("AtField \(token)", forHTTPHeaderField: "Authorization")
request.setValue("SolianWatch/1.0", forHTTPHeaderField: "User-Agent")
let (data, response) = try await session.data(for: request)
if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode != 200 {
let responseBody = String(data: data, encoding: .utf8) ?? ""
print("[watchOS] declineChatInvite failed with status code: \(httpResponse.statusCode), body: \(responseBody)")
throw URLError(URLError.Code(rawValue: httpResponse.statusCode))
}
}
// MARK: - Message API Methods
func fetchChatMessages(chatRoomId: String, token: String, serverUrl: String, before: Date? = nil, take: Int = 50) async throws -> [SnChatMessage] {
guard let baseURL = URL(string: serverUrl) else {
throw URLError(.badURL)
}
// Try a different pattern: /sphere/chat/messages with roomId as query param
var components = URLComponents(
url: baseURL.appendingPathComponent("/sphere/chat/\(chatRoomId)/messages"),
resolvingAgainstBaseURL: false
)!
var queryItems = [
URLQueryItem(name: "take", value: String(take)),
]
if let before = before {
queryItems.append(URLQueryItem(name: "before", value: ISO8601DateFormatter().string(from: before)))
}
components.queryItems = queryItems
var request = URLRequest(url: components.url!)
request.httpMethod = "GET"
request.setValue("application/json", forHTTPHeaderField: "Accept")
request.setValue("AtField \(token)", forHTTPHeaderField: "Authorization")
request.setValue("SolianWatch/1.0", forHTTPHeaderField: "User-Agent")
let (data, response) = try await session.data(for: request)
if let httpResponse = response as? HTTPURLResponse {
_ = String(data: data, encoding: .utf8) ?? "Unable to decode response body"
if httpResponse.statusCode != 200 {
print("[watchOS] fetchChatMessages failed with status \(httpResponse.statusCode)")
throw URLError(URLError.Code(rawValue: httpResponse.statusCode))
}
}
// Check if data is empty
if data.isEmpty {
print("[watchOS] fetchChatMessages received empty response data")
return []
}
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
decoder.keyDecodingStrategy = .convertFromSnakeCase
do {
let messages = try decoder.decode([SnChatMessage].self, from: data)
print("[watchOS] fetchChatMessages successfully decoded \(messages.count) messages")
return messages
} catch {
print("error: ", error)
throw error
}
}
// MARK: - WebSocket
private var webSocketTask: URLSessionWebSocketTask?
private var heartbeatTimer: Timer?
private var reconnectTimer: Timer?
private var isDisconnectingManually = false
private var lastToken: String?
private var lastServerUrl: String?
private var heartbeatAt: Date?
var heartbeatDelay: TimeInterval?
private let connectLock = NSLock()
private let packetSubject = PassthroughSubject<WebSocketPacket, Error>()
private let stateSubject = CurrentValueSubject<WebSocketState, Never>(.disconnected) // Changed to CurrentValueSubject
private var currentConnectionState: WebSocketState = .disconnected { // New property
didSet {
// Only send updates if the state has actually changed
if oldValue != currentConnectionState {
stateSubject.send(currentConnectionState)
}
}
}
var packetStream: AnyPublisher<WebSocketPacket, Error> {
packetSubject.eraseToAnyPublisher()
}
var stateStream: AnyPublisher<WebSocketState, Never> {
stateSubject.eraseToAnyPublisher()
}
func connectWebSocket(token: String, serverUrl: String) {
webSocketQueue.async { [weak self] in
guard let self = self else { return }
self.connectLock.lock()
defer { self.connectLock.unlock() }
// Prevent redundant connection attempts
if self.currentConnectionState == .connecting || self.currentConnectionState == .connected {
print("[WebSocket] Already connecting or connected, ignoring new connect request.")
return
}
self.currentConnectionState = .connecting
// Ensure any existing task is cancelled before starting a new one
self.webSocketTask?.cancel(with: .goingAway, reason: nil)
self.webSocketTask = nil
self.isDisconnectingManually = false // Reset this flag for a new connection attempt
self.lastToken = token
self.lastServerUrl = serverUrl
guard var urlComponents = URLComponents(string: serverUrl) else {
self.currentConnectionState = .error("Invalid server URL")
return
}
urlComponents.scheme = urlComponents.scheme?.replacingOccurrences(of: "http", with: "ws")
urlComponents.path = "/ws"
urlComponents.queryItems = [URLQueryItem(name: "deviceAlt", value: "watch")]
guard let url = urlComponents.url else {
self.currentConnectionState = .error("Invalid WebSocket URL")
return
}
var request = URLRequest(url: url)
request.setValue("AtField \(token)", forHTTPHeaderField: "Authorization")
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
print("[WebSocket] Trying connecting to \(url)")
self.webSocketTask = self.session.webSocketTask(with: request)
self.webSocketTask?.resume()
self.listenForWebSocketMessages()
self.scheduleHeartbeat()
self.currentConnectionState = .connected
}
}
private func listenForWebSocketMessages() {
// Ensure webSocketTask is still valid before attempting to receive
guard let task = webSocketTask else {
print("[WebSocket] listenForWebSocketMessages: webSocketTask is nil, stopping listen.")
return
}
task.receive { [weak self] result in
guard let self = self else { return }
switch result {
case .failure(let error):
print("[WebSocket] Error in receiving message: \(error)")
// Only attempt to reconnect if not manually disconnecting
if !self.isDisconnectingManually {
self.currentConnectionState = .error(error.localizedDescription)
self.scheduleReconnect()
} else {
// If manually disconnecting, just ensure state is disconnected
self.currentConnectionState = .disconnected
}
case .success(let message):
switch message {
case .string(let text):
self.handleWebSocketMessage(text: text)
case .data(let data):
if let text = String(data: data, encoding: .utf8) {
self.handleWebSocketMessage(text: text)
}
@unknown default:
break
}
// Continue listening for next message only if task is still valid
if self.webSocketTask === task { // Check if it's the same task
self.listenForWebSocketMessages()
} else {
print("[WebSocket] listenForWebSocketMessages: Task changed, stopping listen for old task.")
}
}
}
}
private func handleWebSocketMessage(text: String) {
guard let data = text.data(using: .utf8) else {
print("[WebSocket] Could not convert message to data")
return
}
do {
if let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
let type = json["type"] as? String
{
let packet = WebSocketPacket(
type: type,
data: json["data"] as? [String: Any],
endpoint: json["endpoint"] as? String,
errorMessage: json["errorMessage"] as? String
)
print("[WebSocket] Received packet: \(packet.type) \(packet.errorMessage ?? "")")
if packet.type == "error.dupe" {
self.currentConnectionState = .duplicateDevice
self.disconnectWebSocket()
return
}
if packet.type == "pong" {
if let beatAt = self.heartbeatAt {
let now = Date()
self.heartbeatDelay = now.timeIntervalSince(beatAt)
print("[WebSocket] Server respond last heartbeat for \((self.heartbeatDelay ?? 0) * 1000) ms")
}
}
self.packetSubject.send(packet)
}
} catch {
print("[WebSocket] Could not parse message json: \(error.localizedDescription)")
}
}
private func scheduleReconnect() {
reconnectTimer?.invalidate()
reconnectTimer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false) { [weak self] _ in
guard let self = self, let token = self.lastToken, let serverUrl = self.lastServerUrl else { return }
print("[WebSocket] Attempting to reconnect...")
// No need to call disconnectWebSocket here, connectWebSocket will handle cancelling old task
self.isDisconnectingManually = false // Reset for the new connection attempt
self.connectWebSocket(token: token, serverUrl: serverUrl)
}
}
private func scheduleHeartbeat() {
heartbeatTimer?.invalidate()
heartbeatTimer = Timer.scheduledTimer(withTimeInterval: 60.0, repeats: true) { [weak self] _ in
self?.beatTheHeart()
}
}
private func beatTheHeart() {
heartbeatAt = Date()
print("[WebSocket] We\'re beating the heart! \(String(describing: self.heartbeatAt))")
sendWebSocketMessage(message: "{\"type\":\"ping\"}")
}
func sendWebSocketMessage(message: String) {
webSocketTask?.send(.string(message)) { error in
if let error = error {
print("[WebSocket] Error sending message: \(error.localizedDescription)")
}
}
}
func disconnectWebSocket() {
isDisconnectingManually = true
reconnectTimer?.invalidate()
heartbeatTimer?.invalidate()
// Cancel the task and then nil it out
webSocketTask?.cancel(with: .goingAway, reason: nil)
webSocketTask = nil // Set to nil immediately after cancelling
self.currentConnectionState = .disconnected
}
}

View File

@@ -0,0 +1,58 @@
//
// AppState.swift
// WatchRunner Watch App
//
// Created by LittleSheep on 2025/10/29.
//
import SwiftUI
import Combine
// MARK: - App State
@MainActor
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()
private var cancellables = Set<AnyCancellable>()
private var hasAttemptedConnection = false
init() {
wcService.$token.combineLatest(wcService.$serverUrl, wcService.$isFetched, wcService.$errorMessage)
.receive(on: DispatchQueue.main)
.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
// Only connect once when we have valid credentials and tried fetch from phone
if !self.hasAttemptedConnection && isFetched == true {
self.hasAttemptedConnection = true
print("[AppState] Connecting WebSocket to server: \(serverUrl)")
self.networkService.connectWebSocket(token: token, serverUrl: serverUrl)
}
} else {
self.isReady = false
if self.hasAttemptedConnection {
self.hasAttemptedConnection = false
// Disconnect WebSocket if token or serverUrl become invalid
self.networkService.disconnectWebSocket()
}
}
}
.store(in: &cancellables)
}
func requestData() {
wcService.requestDataFromPhone()
}
}

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

@@ -0,0 +1,20 @@
//
// AttachmentUtils.swift
// WatchRunner Watch App
//
// Created by LittleSheep on 2025/10/29.
//
import Foundation
// MARK: - Helper Functions
func getAttachmentUrl(for fileId: String, serverUrl: String) -> URL? {
let urlString: String
if fileId.starts(with: "http") {
urlString = fileId
} else {
urlString = "\(serverUrl)/drive/files/\(fileId)"
}
return URL(string: urlString)
}

View File

@@ -0,0 +1,73 @@
//
// ActivityViewModel.swift
// WatchRunner Watch App
//
// Created by LittleSheep on 2025/10/29.
//
import Foundation
import Combine
// MARK: - View Models
@MainActor
class ActivityViewModel: ObservableObject {
@Published var activities: [SnActivity] = []
@Published var isLoading = false
@Published var isLoadingMore = false
@Published var errorMessage: String?
@Published var hasMore = false
private let networkService = NetworkService()
let filter: String
private var isMock = false
private var hasFetched = false
private var nextCursor: String?
init(filter: String, mockActivities: [SnActivity]? = nil) {
self.filter = filter
if let mockActivities = mockActivities {
self.activities = mockActivities
self.isMock = true
}
}
func fetchActivities(token: String, serverUrl: String) async {
if isMock || hasFetched { return }
guard !isLoading else { return }
isLoading = true
errorMessage = nil
hasFetched = true
nextCursor = nil
do {
let response = try await networkService.fetchActivities(filter: filter, cursor: nil, token: token, serverUrl: serverUrl)
self.activities = response.activities
self.hasMore = response.hasMore
self.nextCursor = response.nextCursor
} catch {
self.errorMessage = error.localizedDescription
print("[watchOS] fetchActivities failed with error: \(error)")
hasFetched = false
}
isLoading = false
}
func loadMoreActivities(token: String, serverUrl: String) async {
guard !isLoadingMore && hasMore && nextCursor != nil else { return }
isLoadingMore = true
do {
let response = try await networkService.fetchActivities(filter: filter, cursor: nextCursor, token: token, serverUrl: serverUrl)
self.activities.append(contentsOf: response.activities)
self.hasMore = response.hasMore
self.nextCursor = response.nextCursor
} catch {
self.errorMessage = error.localizedDescription
print("[watchOS] loadMoreActivities failed with error: \(error)")
}
isLoadingMore = false
}
}

View File

@@ -0,0 +1,35 @@
//
// ComposePostViewModel.swift
// WatchRunner Watch App
//
// Created by LittleSheep on 2025/10/29.
//
import Foundation
import Combine
@MainActor
class ComposePostViewModel: ObservableObject {
@Published var title = ""
@Published var content = ""
@Published var isPosting = false
@Published var errorMessage: String?
@Published var didPost = false
private let networkService = NetworkService()
func createPost(token: String, serverUrl: String) async {
guard !isPosting else { return }
isPosting = true
errorMessage = nil
do {
try await networkService.createPost(title: title, content: content, token: token, serverUrl: serverUrl)
didPost = true
} catch {
errorMessage = error.localizedDescription
}
isPosting = false
}
}

View File

@@ -0,0 +1,284 @@
//
// AccountView.swift
// WatchRunner Watch App
//
// Created by LittleSheep on 2025/10/30.
//
import SwiftUI
struct AccountView: View {
@EnvironmentObject var appState: AppState
@State private var user: SnAccount?
@State private var status: SnAccountStatus?
@State private var isLoading = false
@State private var error: Error?
@State private var showingClearConfirmation = false
@StateObject private var profileImageLoader = ImageLoader()
@StateObject private var bannerImageLoader = ImageLoader()
private let networkService = NetworkService()
var body: some View {
ScrollView {
if isLoading {
ProgressView()
.padding()
} else if let error = error {
VStack {
Text("Failed to load account")
.foregroundColor(.red)
Text(error.localizedDescription)
.font(.caption)
.foregroundColor(.secondary)
}
.padding()
} else if let user = user {
VStack(spacing: 16) {
// Banner
if user.profile.background != nil {
if bannerImageLoader.isLoading {
ProgressView()
.frame(height: 80)
} else if let bannerImage = bannerImageLoader.image {
bannerImage
.resizable()
.aspectRatio(contentMode: .fill)
.frame(height: 80)
.clipped()
.cornerRadius(8)
} else if bannerImageLoader.errorMessage != nil {
Rectangle()
.fill(Color.gray.opacity(0.3))
.frame(height: 80)
.cornerRadius(8)
} else {
Rectangle()
.fill(Color.gray.opacity(0.3))
.frame(height: 80)
.cornerRadius(8)
}
}
// Profile Picture
HStack(spacing: 16)
{
if profileImageLoader.isLoading {
ProgressView()
.frame(width: 60, height: 60)
} else if let profileImage = profileImageLoader.image {
profileImage
.resizable()
.frame(width: 60, height: 60)
.clipShape(Circle())
} else if profileImageLoader.errorMessage != nil {
Circle()
.fill(Color.red.opacity(0.3))
.frame(width: 60, height: 60)
.overlay(
Image(systemName: "exclamationmark.triangle")
.resizable()
.scaledToFit()
.foregroundColor(.red)
)
} else {
Circle()
.fill(Color.gray.opacity(0.3))
.frame(width: 60, height: 60)
.overlay(
Image(systemName: "person.circle.fill")
.resizable()
.scaledToFit()
.foregroundColor(.gray)
)
}
// Username and Handle
VStack(alignment: .leading) {
Text(user.nick)
.font(.headline)
Text("@\(user.name)")
.font(.caption)
.foregroundColor(.secondary)
}
}
// Status
VStack(alignment: .leading, spacing: 8) {
HStack {
Text("Status")
.font(.subheadline)
.foregroundColor(.secondary)
Spacer()
if status?.isCustomized == true {
Button(action: {
showingClearConfirmation = true
}) {
ZStack {
Circle()
.fill(Color.red.opacity(0.1))
.frame(width: 28, height: 28)
Image(systemName: "trash")
.foregroundColor(.red)
}
}
.buttonStyle(.plain)
.frame(width: 28, height: 28)
}
NavigationLink(
destination: StatusCreationView(initialStatus: status?.isCustomized == true ? status : nil)
.environmentObject(appState)
) {
ZStack {
Circle()
.fill(Color.blue.opacity(0.1))
.frame(width: 28, height: 28)
Image(systemName: "pencil")
.foregroundColor(.blue)
}
}
.buttonStyle(.plain)
.frame(width: 28, height: 28)
}
if let status = status {
VStack(alignment: .leading, spacing: 4) {
HStack {
Circle()
.fill(status.isOnline ? Color.green : Color.gray)
.frame(width: 8, height: 8)
Text(status.label.isEmpty ? "No status" : status.label)
.font(.body)
}
if status.isInvisible {
Text("Invisible")
.font(.caption)
.foregroundColor(.secondary)
}
if status.isNotDisturb {
Text("Do Not Disturb")
.font(.caption)
.foregroundColor(.secondary)
}
if let clearedAt = status.clearedAt {
Text("Clears: \(clearedAt.formatted(date: .abbreviated, time: .shortened))")
.font(.caption)
.foregroundColor(.secondary)
}
}
} else {
Text("No status set")
.font(.body)
.foregroundColor(.secondary)
}
}
// Level and Progress
VStack(alignment: .leading, spacing: 8) {
Text("Level \(user.profile.level)")
.font(.title3)
.bold()
ProgressView(value: user.profile.levelingProgress)
.progressViewStyle(LinearProgressViewStyle())
.frame(height: 8)
Text("Experience: \(user.profile.experience)")
.font(.caption)
.foregroundColor(.secondary)
}
// Bio
if let bio = user.profile.bio, !bio.isEmpty {
Text(bio)
.font(.body)
.multilineTextAlignment(.center)
.foregroundColor(.secondary)
.frame(alignment: .leading)
} else {
Text("No bio available")
.font(.body)
.foregroundColor(.secondary)
.frame(alignment: .leading)
}
// Member since
Text("Joined at \(user.createdAt.formatted(.dateTime.month(.abbreviated).year()))")
.font(.caption)
.foregroundColor(.secondary)
.frame(alignment: .leading)
}
.padding()
// Load images when user data is available
.task(id: user.profile.picture?.id) {
if let serverUrl = appState.serverUrl, let pictureId = user.profile.picture?.id, let imageUrl = getAttachmentUrl(for: pictureId, serverUrl: serverUrl), let token = appState.token {
await profileImageLoader.loadImage(from: imageUrl, token: token)
}
}
.task(id: user.profile.background?.id) {
if let serverUrl = appState.serverUrl, let backgroundId = user.profile.background?.id, let imageUrl = getAttachmentUrl(for: backgroundId, serverUrl: serverUrl), let token = appState.token {
await bannerImageLoader.loadImage(from: imageUrl, token: token)
}
}
} else {
Text("No account data")
.padding()
}
}
.navigationTitle("Account")
.confirmationDialog("Clear Status", isPresented: $showingClearConfirmation) {
Button("Clear Status", role: .destructive) {
Task {
await clearStatus()
}
}
Button("Cancel", role: .cancel) {}
} message: {
Text("Are you sure you want to clear your status? This action cannot be undone.")
}
.onAppear {
Task.detached {
await loadUserProfile()
}
}
}
private func loadUserProfile() async {
guard let token = appState.token, let serverUrl = appState.serverUrl else {
error = NSError(domain: "AccountView", code: 1, userInfo: [NSLocalizedDescriptionKey: "Authentication not available"])
return
}
isLoading = true
error = nil
do {
user = try await networkService.fetchUserProfile(token: token, serverUrl: serverUrl)
status = try await networkService.fetchAccountStatus(token: token, serverUrl: serverUrl)
} catch {
self.error = error
}
isLoading = false
}
private func clearStatus() async {
guard let token = appState.token, let serverUrl = appState.serverUrl else {
error = NSError(domain: "AccountView", code: 1, userInfo: [NSLocalizedDescriptionKey: "Authentication not available"])
return
}
do {
try await networkService.clearStatus(token: token, serverUrl: serverUrl)
// Refresh status after clearing
status = try await networkService.fetchAccountStatus(token: token, serverUrl: serverUrl)
} catch {
self.error = error
}
}
}
#Preview {
AccountView()
.environmentObject(AppState())
}

View File

@@ -0,0 +1,86 @@
//
// ActivityListView.swift
// WatchRunner Watch App
//
// Created by LittleSheep on 2025/10/29.
//
import SwiftUI
// MARK: - Views
struct ActivityListView: View {
@StateObject private var viewModel: ActivityViewModel
@EnvironmentObject var appState: AppState
init(filter: String, mockActivities: [SnActivity]? = nil) {
_viewModel = StateObject(wrappedValue: ActivityViewModel(filter: filter, mockActivities: mockActivities))
}
var body: some View {
Group {
if viewModel.isLoading {
ProgressView()
} else if let errorMessage = viewModel.errorMessage {
VStack {
Text("Error fetching data")
.font(.headline)
Text(errorMessage)
.font(.caption)
.lineLimit(nil)
}
.padding()
} else if viewModel.activities.isEmpty {
Text("No activities found.")
} else {
List {
ForEach(viewModel.activities) { activity in
switch activity.type {
case "posts.new", "posts.new.replies":
if case .post(let post) = activity.data {
NavigationLink(
destination: PostDetailView(post: post).environmentObject(appState)
) {
PostRowView(post: post)
}
}
case "discovery":
if case .discovery(let discoveryData) = activity.data {
DiscoveryView(discoveryData: discoveryData)
}
default:
Text("Unknown activity type: \(activity.type)")
}
}
if viewModel.hasMore {
if viewModel.isLoadingMore {
HStack {
Spacer()
ProgressView()
Spacer()
}
} else {
Button("Load More") {
Task {
if let token = appState.token, let serverUrl = appState.serverUrl {
await viewModel.loadMoreActivities(token: token, serverUrl: serverUrl)
}
}
}
.frame(maxWidth: .infinity)
}
}
}
}
}
.onAppear {
if appState.isReady, let token = appState.token, let serverUrl = appState.serverUrl {
Task.detached {
await viewModel.fetchActivities(token: token, serverUrl: serverUrl)
}
}
}
.navigationTitle(viewModel.filter)
.navigationBarTitleDisplayMode(.inline)
}
}

View File

@@ -0,0 +1,62 @@
//
// AppInfoHeader.swift
// Runner
//
// Created by LittleSheep on 2025/10/30.
//
import Combine
import SwiftUI
struct AppInfoHeaderView : View {
@EnvironmentObject var appState: AppState // Access AppState
@State private var webSocketConnectionState: WebSocketState = .disconnected // New state for WebSocket status
@State private var cancellables = Set<AnyCancellable>() // For managing subscriptions
var body: some View {
VStack(alignment: .leading) {
HStack(spacing: 12) {
Image("Logo")
.resizable()
.frame(width: 40, height: 40)
VStack(alignment: .leading) {
Text("Solian").font(.headline)
Text("for Apple Watch").font(.system(size: 11))
// Display WebSocket connection status
Text(webSocketStatusMessage)
.font(.system(size: 10))
.foregroundColor(.secondary)
}
}
}
.onAppear {
setupWebSocketListeners()
}
.onDisappear {
cancellables.forEach { $0.cancel() }
cancellables.removeAll()
}
}
private var webSocketStatusMessage: String {
switch webSocketConnectionState {
case .connected: return "Connected"
case .connecting: return "Connecting..."
case .disconnected: return "Disconnected"
case .serverDown: return "Server Down"
case .duplicateDevice: return "Duplicate Device"
case .error(let msg): return "Error: \(msg)"
}
}
private func setupWebSocketListeners() {
appState.networkService.stateStream
.receive(on: DispatchQueue.main)
.sink { state in
webSocketConnectionState = state
}
.store(in: &cancellables)
}
}

View File

@@ -0,0 +1,109 @@
//
// AttachmentImageView.swift
// WatchRunner Watch App
//
// Created by LittleSheep on 2025/10/29.
//
import SwiftUI
import AVKit
import AVFoundation
struct AttachmentView: View {
let attachment: SnCloudFile
@EnvironmentObject var appState: AppState
@StateObject private var imageLoader = ImageLoader()
var body: some View {
Group {
if let mimeType = attachment.mimeType {
if mimeType.starts(with: "image") {
if let serverUrl = appState.serverUrl, let imageUrl = getAttachmentUrl(for: attachment.id, serverUrl: serverUrl) {
NavigationLink(
destination: ImageViewer(imageUrl: imageUrl).environmentObject(appState)
) {
if imageLoader.isLoading {
ProgressView()
} else if let image = imageLoader.image {
image
.resizable()
.aspectRatio(contentMode: .fit)
.frame(maxWidth: .infinity)
.cornerRadius(8)
} else if let errorMessage = imageLoader.errorMessage {
Text("Failed to load attachment: \(errorMessage)")
.font(.caption)
.foregroundColor(.red)
.cornerRadius(8)
} else {
Text("File: \(attachment.id)")
.cornerRadius(8)
}
}
.buttonStyle(PlainButtonStyle())
} else {
Text("Image URL not available.")
}
} else if mimeType.starts(with: "video") {
if let serverUrl = appState.serverUrl, let videoUrl = getAttachmentUrl(for: attachment.id, serverUrl: serverUrl) {
NavigationLink(destination: VideoPlayerView(videoUrl: videoUrl)) {
if imageLoader.isLoading {
ProgressView()
} else if let image = imageLoader.image {
ZStack {
image
.resizable()
.aspectRatio(contentMode: .fit)
.frame(maxWidth: .infinity)
.cornerRadius(8)
Image(systemName: "play.circle.fill")
.resizable()
.scaledToFit()
.frame(width: 36, height: 36)
.foregroundColor(.white)
.shadow(color: .black.opacity(0.6), radius: 4, x: 0, y: 2)
}
} else if imageLoader.errorMessage != nil {
Image(systemName: "play.rectangle.fill")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(maxWidth: .infinity)
.foregroundColor(.gray)
.cornerRadius(8)
} else {
ProgressView()
.cornerRadius(8)
}
}
.buttonStyle(PlainButtonStyle())
} else {
Text("Video URL not available.")
}
} else if mimeType.starts(with: "audio") {
if let serverUrl = appState.serverUrl, let audioUrl = getAttachmentUrl(for: attachment.id, serverUrl: serverUrl) {
AudioPlayerView(audioUrl: audioUrl)
} else {
Text("Cannot play audio: URL not available.")
}
} else {
Text("Unsupported media type: \(mimeType)")
}
} else {
Text("File: \(attachment.id) (No MIME type)")
}
}
.task(id: attachment.id) {
if let serverUrl = appState.serverUrl, let attachmentUrl = getAttachmentUrl(for: attachment.id, serverUrl: serverUrl), let token = appState.token {
if attachment.mimeType?.starts(with: "image") == true {
await imageLoader.loadImage(from: attachmentUrl, token: token)
}
if attachment.mimeType?.starts(with: "video") == true {
let thumbnailUrl = attachmentUrl
.appending(queryItems: [URLQueryItem(name: "thumbnail", value: "true")]) // Construct thumbnail URL
await imageLoader.loadImage(from: thumbnailUrl, token: token)
}
}
}
}
}

View File

@@ -0,0 +1,47 @@
//
// AudioPlayerView.swift
// WatchRunner Watch App
//
// Created by LittleSheep on 2025/10/29.
//
import SwiftUI
import AVFoundation
struct AudioPlayerView: View {
let audioUrl: URL
@State private var player: AVPlayer?
@State private var isPlaying: Bool = false
var body: some View {
VStack {
if player != nil {
Button(action: togglePlayPause) {
Image(systemName: isPlaying ? "pause.circle.fill" : "play.circle.fill")
.font(.largeTitle)
}
.buttonStyle(.plain)
} else {
Text("Loading audio...")
}
}
.onAppear {
player = AVPlayer(url: audioUrl)
}
.onDisappear {
player?.pause()
player = nil
}
}
private func togglePlayPause() {
guard let player = player else { return }
if isPlaying {
player.pause()
} else {
player.play()
}
isPlaying.toggle()
}
}

View File

@@ -0,0 +1,785 @@
//
// ChatView.swift
// WatchRunner Watch App
//
// Created by LittleSheep on 2025/10/30.
//
import SwiftUI
struct ChatView: View {
@EnvironmentObject var appState: AppState
@State private var selectedTab = 0
@State private var chatRooms: [SnChatRoom] = []
@State private var chatInvites: [SnChatMember] = []
@State private var isLoading = false
@State private var error: Error?
@State private var showingInvites = false
private let tabs = ["All", "Direct", "Group"]
var body: some View {
TabView(selection: $selectedTab) {
ForEach(0..<tabs.count, id: \.self) { index in
VStack {
if isLoading {
ProgressView()
} else if error != nil {
VStack {
Text("Error loading chats")
.font(.caption)
Button("Retry") {
Task {
await loadChatRooms()
}
}
.font(.caption2)
}
} else {
ChatRoomListView(
chatRooms: filteredChatRooms(for: index),
selectedTab: index
)
}
}
.tabItem {
Text(tabs[index])
}
.tag(index)
}
}
.tabViewStyle(.page)
.navigationTitle("Chat")
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button {
showingInvites = true
} label: {
ZStack {
Image(systemName: "envelope")
if !chatInvites.isEmpty {
Circle()
.fill(Color.red)
.frame(width: 8, height: 8)
.offset(x: 8, y: -8)
}
}
}
}
}
.sheet(isPresented: $showingInvites) {
ChatInvitesView(invites: $chatInvites, appState: appState)
}
.onAppear {
Task.detached {
await loadChatRooms()
await loadChatInvites()
}
}
}
private func filteredChatRooms(for tabIndex: Int) -> [SnChatRoom] {
switch tabIndex {
case 0: // All
return chatRooms
case 1: // Direct
return chatRooms.filter { $0.type == 1 }
case 2: // Group
return chatRooms.filter { $0.type != 1 }
default:
return chatRooms
}
}
private func loadChatRooms() async {
guard let token = appState.token, let serverUrl = appState.serverUrl else { return }
isLoading = true
error = nil
do {
let response = try await appState.networkService.fetchChatRooms(token: token, serverUrl: serverUrl)
chatRooms = response.rooms
} catch {
self.error = error
}
isLoading = false
}
private func loadChatInvites() async {
guard let token = appState.token, let serverUrl = appState.serverUrl else { return }
do {
let response = try await appState.networkService.fetchChatInvites(token: token, serverUrl: serverUrl)
chatInvites = response.invites
} catch {
// Handle error silently for invites
}
}
}
struct ChatRoomListView: View {
let chatRooms: [SnChatRoom]
let selectedTab: Int
var body: some View {
if chatRooms.isEmpty {
VStack {
Image(systemName: "message")
.font(.largeTitle)
.foregroundColor(.secondary)
Text("No chats yet")
.font(.caption)
.foregroundColor(.secondary)
}
} else {
List(chatRooms) { room in
ChatRoomListItem(room: room)
}
.listStyle(.plain)
}
}
}
struct ChatRoomListItem: View {
let room: SnChatRoom
@EnvironmentObject var appState: AppState
@StateObject private var avatarLoader = ImageLoader()
private var displayName: String {
if room.type == 1, let members = room.members, !members.isEmpty {
// For direct messages, show the other member's name
return members[0].account.nick
} else {
// For group chats, show room name or fallback
return room.name ?? "Group Chat"
}
}
private var subtitle: String {
if room.type == 1, let members = room.members, members.count > 1 {
// For direct messages, show member usernames
return members.map { "@\($0.account.name)" }.joined(separator: ", ")
} else if let description = room.description {
// For group chats with description
return description
} else {
// Fallback
return ""
}
}
private var avatarPictureId: String? {
if room.type == 1, let members = room.members, !members.isEmpty {
// For direct messages, use the other member's avatar
return members[0].account.profile.picture?.id
} else {
// For group chats, use room picture
return room.picture?.id
}
}
var body: some View {
NavigationLink(
destination: ChatRoomView(room: room)
.environmentObject(appState)
) {
HStack {
// Avatar using ImageLoader pattern
Group {
if avatarLoader.isLoading {
ProgressView()
.frame(width: 32, height: 32)
} else if let image = avatarLoader.image {
image
.resizable()
.frame(width: 32, height: 32)
.clipShape(Circle())
} else if avatarLoader.errorMessage != nil {
// Error state - show fallback
Circle()
.fill(Color.gray.opacity(0.3))
.frame(width: 32, height: 32)
.overlay(
Text(displayName.prefix(1).uppercased())
.font(.system(size: 12, weight: .medium))
.foregroundColor(.primary)
)
} else {
// No image available - show initial
Circle()
.fill(Color.gray.opacity(0.3))
.frame(width: 32, height: 32)
.overlay(
Text(displayName.prefix(1).uppercased())
.font(.system(size: 12, weight: .medium))
.foregroundColor(.primary)
)
}
}
.task(id: avatarPictureId) {
if let serverUrl = appState.serverUrl,
let pictureId = avatarPictureId,
let imageUrl = getAttachmentUrl(for: pictureId, serverUrl: serverUrl),
let token = appState.token {
await avatarLoader.loadImage(from: imageUrl, token: token)
}
}
VStack(alignment: .leading, spacing: 2) {
Text(displayName)
.font(.system(size: 14, weight: .medium))
.lineLimit(1)
if !subtitle.isEmpty {
Text(subtitle)
.font(.system(size: 12))
.foregroundColor(.secondary)
.lineLimit(1)
}
}
Spacer()
// Unread count badge placeholder
// In a full implementation, this would show unread count
}
.padding(.vertical, 4)
}
}
}
import Combine
import SwiftUI
struct ChatRoomView: View {
let room: SnChatRoom
@EnvironmentObject var appState: AppState
@State private var messages: [SnChatMessage] = []
@State private var isLoading = false
@State private var error: Error?
@State private var wsState: WebSocketState = .disconnected // New state for WebSocket status
@State private var hasLoadedMessages = false // Track if messages have been loaded
@State private var messageText = "" // Text input for sending messages
@State private var isSending = false // Track sending state
@State private var isInputHidden = false // Track if input should be hidden during scrolling
@State private var scrollTimer: Timer? // Timer to show input after scrolling stops
@State private var cancellables = Set<AnyCancellable>() // For managing subscriptions
var body: some View {
VStack {
// Display WebSocket connection status
if (wsState != .connected)
{
Text(webSocketStatusMessage)
.font(.caption2)
.foregroundColor(.secondary)
.padding(.vertical, 2)
.animation(.easeInOut, value: wsState) // Animate status changes
.transition(.opacity)
}
if isLoading {
ProgressView()
} else if error != nil {
VStack {
Text("Error loading messages")
.font(.caption)
Button("Retry") {
Task {
await loadMessages()
}
}
.font(.caption2)
}
} else if messages.isEmpty {
VStack {
Image(systemName: "bubble.left")
.font(.largeTitle)
.foregroundColor(.secondary)
Text("No messages yet")
.font(.caption)
.foregroundColor(.secondary)
}
} else {
ScrollViewReader { scrollView in
ScrollView {
LazyVStack(alignment: .leading, spacing: 8) {
ForEach(messages) { message in
ChatMessageItem(message: message)
}
}
.padding(.horizontal)
.padding(.vertical, 8)
.padding(.bottom, 8)
}
.onAppear {
// Scroll to bottom when messages load
if let lastMessage = messages.last {
scrollView.scrollTo(lastMessage.id, anchor: .bottom)
}
}
.onChange(of: messages.count) { _, _ in
// Scroll to bottom when new messages arrive
if let lastMessage = messages.last {
withAnimation {
scrollView.scrollTo(lastMessage.id, anchor: .bottom)
}
}
}
.onScrollPhaseChange { _, phase in
switch phase {
case .interacting:
if !isInputHidden {
withAnimation(.easeOut(duration: 0.2)) {
isInputHidden = true
}
}
case .idle:
withAnimation(.easeIn(duration: 0.3)) {
isInputHidden = false
}
default: break
}
}
}
}
// Message input area
if !isInputHidden {
HStack(spacing: 8) {
TextField("Send message...", text: $messageText)
.font(.system(size: 14))
.disabled(isSending)
.frame(height: 40)
Button {
Task {
await sendMessage()
}
} label: {
if isSending {
ProgressView()
.frame(width: 20, height: 20)
} else {
Image(systemName: "arrow.up.circle.fill")
.resizable()
.frame(width: 20, height: 20)
}
}
.labelStyle(.iconOnly)
.buttonStyle(.automatic)
.disabled(messageText.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty || isSending)
.frame(width: 40, height: 40)
}
.padding(.horizontal)
.padding(.top, 8)
.transition(.move(edge: .bottom).combined(with: .opacity))
}
}
.navigationTitle(room.name ?? "Chat")
.task {
await loadMessages()
}
.onAppear {
setupWebSocketListeners()
}
.onDisappear {
cancellables.forEach { $0.cancel() }
cancellables.removeAll()
scrollTimer?.invalidate()
scrollTimer = nil
}
}
private var webSocketStatusMessage: String {
switch wsState {
case .connected: return "Connected"
case .connecting: return "Connecting..."
case .disconnected: return "Disconnected"
case .serverDown: return "Server Down"
case .duplicateDevice: return "Duplicate Device"
case .error(let msg): return "Error: \(msg)"
}
}
private func loadMessages() async {
// Prevent reloading if already loaded
guard !hasLoadedMessages else { return }
guard let token = appState.token, let serverUrl = appState.serverUrl else {
isLoading = false
return
}
isLoading = true
error = nil
do {
let messages = try await appState.networkService.fetchChatMessages(
chatRoomId: room.id,
token: token,
serverUrl: serverUrl
)
// Sort with newest messages first (for flipped list, newest will appear at bottom)
self.messages = messages.sorted { $0.createdAt < $1.createdAt }
hasLoadedMessages = true
} catch {
print("[watchOS] Error loading messages: \(error.localizedDescription)")
self.error = error
}
isLoading = false
}
private func sendMessage() async {
let content = messageText.trimmingCharacters(in: .whitespacesAndNewlines)
guard !content.isEmpty,
let token = appState.token,
let serverUrl = appState.serverUrl else { return }
isSending = true
do {
// Generate a nonce for the message
let nonce = UUID().uuidString
// Prepare the request data
let messageData: [String: Any] = [
"content": content,
"attachments_id": [], // Empty for now, can be extended for attachments
"meta": [:],
"nonce": nonce
]
// Create the URL
guard let url = URL(string: "\(serverUrl)/sphere/chat/\(room.id)/messages") else {
throw URLError(.badURL)
}
// Create the request
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = try JSONSerialization.data(withJSONObject: messageData, options: [])
// Send the request
let (data, response) = try await URLSession.shared.data(for: request)
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
throw URLError(.badServerResponse)
}
// Parse the response to get the sent message
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
decoder.keyDecodingStrategy = .convertFromSnakeCase
let sentMessage = try decoder.decode(SnChatMessage.self, from: data)
// Add the message to the local list
messages.append(sentMessage)
// Clear the input
messageText = ""
} catch {
print("[watchOS] Error sending message: \(error.localizedDescription)")
// Could show an error alert here
}
isSending = false
}
private func sendReadReceipt() {
let data: [String: Any] = ["chat_room_id": room.id]
let packet: [String: Any] = ["type": "messages.read", "data": data, "endpoint": "sphere"]
if let jsonData = try? JSONSerialization.data(withJSONObject: packet, options: []),
let jsonString = String(data: jsonData, encoding: .utf8) {
appState.networkService.sendWebSocketMessage(message: jsonString)
}
}
private func setupWebSocketListeners() {
// Listen for WebSocket packets (new messages)
appState.networkService.packetStream
.receive(on: DispatchQueue.main) // Ensure UI updates on main thread
.sink(receiveCompletion: { completion in
if case .failure(let err) = completion {
print("[ChatRoomView] WebSocket packet stream error: \(err.localizedDescription)")
}
}, receiveValue: { packet in
if ["messages.new", "messages.update", "messages.delete"].contains(packet.type),
let messageData = packet.data {
do {
let jsonData = try JSONSerialization.data(withJSONObject: messageData, options: [])
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
decoder.keyDecodingStrategy = .convertFromSnakeCase
let message = try decoder.decode(SnChatMessage.self, from: jsonData)
if message.chatRoomId == room.id {
switch packet.type {
case "messages.new":
if message.type.hasPrefix("call") {
// TODO: Handle ongoing call
}
if !messages.contains(where: { $0.id == message.id }) {
messages.append(message)
}
sendReadReceipt()
case "messages.update":
if let index = messages.firstIndex(where: { $0.id == message.id }) {
messages[index] = message
}
case "messages.delete":
messages.removeAll(where: { $0.id == message.id })
default:
break
}
}
} catch {
print("[ChatRoomView] Error decoding message from websocket: \(error.localizedDescription)")
}
}
})
.store(in: &cancellables)
// Listen for WebSocket connection state changes
appState.networkService.stateStream
.receive(on: DispatchQueue.main) // Ensure UI updates on main thread
.sink { state in
wsState = state
}
.store(in: &cancellables)
}
}
struct ChatMessageItem: View {
let message: SnChatMessage
@EnvironmentObject var appState: AppState
@StateObject private var avatarLoader = ImageLoader()
private var avatarPictureId: String? {
message.sender.account.profile.picture?.id
}
var body: some View {
HStack(alignment: .top, spacing: 8) {
// Avatar
Group {
if avatarLoader.isLoading {
ProgressView()
.frame(width: 24, height: 24)
} else if let image = avatarLoader.image {
image
.resizable()
.frame(width: 24, height: 24)
.clipShape(Circle())
} else {
Circle()
.fill(Color.gray.opacity(0.3))
.frame(width: 24, height: 24)
.overlay(
Text(message.sender.account.nick.prefix(1).uppercased())
.font(.system(size: 10, weight: .medium))
.foregroundColor(.primary)
)
}
}
.task(id: avatarPictureId) {
if let serverUrl = appState.serverUrl,
let pictureId = avatarPictureId,
let imageUrl = getAttachmentUrl(for: pictureId, serverUrl: serverUrl),
let token = appState.token {
await avatarLoader.loadImage(from: imageUrl, token: token)
}
}
VStack(alignment: .leading, spacing: 4) {
HStack {
Text(message.sender.account.nick)
.font(.system(size: 12, weight: .medium))
Spacer()
Text(message.createdAt, style: .time)
.font(.system(size: 10))
.foregroundColor(.secondary)
}
if let content = message.content, !content.isEmpty {
Text(content)
.font(.system(size: 14))
.lineLimit(nil)
.fixedSize(horizontal: false, vertical: true)
}
if !message.attachments.isEmpty {
AttachmentView(attachment: message.attachments[0])
if message.attachments.count > 1 {
HStack(spacing: 8) {
Image(systemName: "paperclip.circle.fill")
.frame(width: 12, height: 12)
.foregroundStyle(.gray)
Text("\(message.attachments.count - 1)+ attachments")
.font(.footnote)
.foregroundStyle(.gray)
}
}
}
}
}
.padding(.vertical, 4)
}
}
struct ChatInvitesView: View {
@Binding var invites: [SnChatMember]
let appState: AppState
@Environment(\.dismiss) private var dismiss
@State private var isLoading = false
var body: some View {
NavigationView {
VStack {
if invites.isEmpty {
VStack {
Image(systemName: "envelope.open")
.font(.largeTitle)
.foregroundColor(.secondary)
Text("No invites")
.font(.caption)
.foregroundColor(.secondary)
}
} else {
List(invites) { invite in
ChatInviteItem(invite: invite, appState: appState, invites: $invites)
}
.listStyle(.plain)
}
}
.navigationTitle("Invites")
.navigationBarTitleDisplayMode(.inline)
}
}
}
struct ChatInviteItem: View {
let invite: SnChatMember
let appState: AppState
@Binding var invites: [SnChatMember]
@State private var isAccepting = false
@State private var isDeclining = false
var body: some View {
VStack(alignment: .leading, spacing: 8) {
HStack {
Circle()
.fill(Color.gray.opacity(0.3))
.frame(width: 24, height: 24)
.overlay(
Text((invite.chatRoom?.name ?? "C").prefix(1).uppercased())
.font(.system(size: 10, weight: .medium))
.foregroundColor(.primary)
)
VStack(alignment: .leading, spacing: 2) {
Text(invite.chatRoom?.name ?? "Unknown Chat")
.font(.system(size: 14, weight: .medium))
.lineLimit(1)
HStack(spacing: 4) {
Text(invite.role == 100 ? "Owner" : invite.role >= 50 ? "Moderator" : "Member")
.font(.system(size: 12))
.foregroundColor(.secondary)
if invite.chatRoom?.type == 1 {
Text("Direct")
.font(.system(size: 12))
.foregroundColor(.blue)
.padding(.horizontal, 4)
.padding(.vertical, 2)
.background(Color.blue.opacity(0.1))
.cornerRadius(4)
}
}
}
Spacer()
}
HStack(spacing: 8) {
Button {
Task {
await acceptInvite()
}
} label: {
if isAccepting {
ProgressView()
.frame(width: 20, height: 20)
} else {
Image(systemName: "checkmark")
.frame(width: 20, height: 20)
}
}
.disabled(isAccepting || isDeclining)
Button {
Task {
await declineInvite()
}
} label: {
if isDeclining {
ProgressView()
.frame(width: 20, height: 20)
} else {
Image(systemName: "xmark")
.frame(width: 20, height: 20)
}
}
.disabled(isAccepting || isDeclining)
}
}
.padding(.vertical, 8)
}
private func acceptInvite() async {
guard let token = appState.token,
let serverUrl = appState.serverUrl,
let chatRoomId = invite.chatRoom?.id else { return }
isAccepting = true
do {
try await appState.networkService.acceptChatInvite(chatRoomId: chatRoomId, token: token, serverUrl: serverUrl)
// Remove from invites list
invites.removeAll { $0.id == invite.id }
} catch {
// Handle error - could show alert
print("Failed to accept invite: \(error)")
}
isAccepting = false
}
private func declineInvite() async {
guard let token = appState.token,
let serverUrl = appState.serverUrl,
let chatRoomId = invite.chatRoom?.id else { return }
isDeclining = true
do {
try await appState.networkService.declineChatInvite(chatRoomId: chatRoomId, token: token, serverUrl: serverUrl)
// Remove from invites list
invites.removeAll { $0.id == invite.id }
} catch {
// Handle error - could show alert
print("Failed to decline invite: \(error)")
}
isDeclining = false
}
}

View File

@@ -0,0 +1,53 @@
//
// ComposePostView.swift
// WatchRunner Watch App
//
// Created by LittleSheep on 2025/10/29.
//
import SwiftUI
struct ComposePostView: View {
@StateObject private var viewModel = ComposePostViewModel()
@EnvironmentObject var appState: AppState
@Environment(\.dismiss) private var dismiss
var body: some View {
NavigationStack {
Form {
TextField("Title", text: $viewModel.title)
TextField("Content", text: $viewModel.content)
}
.navigationTitle("New Post")
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("Cancel", systemImage: "xmark") {
dismiss()
}
.labelStyle(.iconOnly)
}
ToolbarItem(placement: .confirmationAction) {
Button("Post", systemImage: "square.and.arrow.up") {
Task {
if let token = appState.token, let serverUrl = appState.serverUrl {
await viewModel.createPost(token: token, serverUrl: serverUrl)
}
}
}
.labelStyle(.iconOnly)
.disabled(viewModel.isPosting)
}
}
.onChange(of: viewModel.didPost) {
if viewModel.didPost {
dismiss()
}
}
.alert("Error", isPresented: .constant(viewModel.errorMessage != nil), actions: {
Button("OK") { viewModel.errorMessage = nil }
}, message: {
Text(viewModel.errorMessage ?? "")
})
}
}
}

View File

@@ -0,0 +1,110 @@
//
// DiscoveryViews.swift
// WatchRunner Watch App
//
// Created by LittleSheep on 2025/10/29.
//
import SwiftUI
struct DiscoveryView: View {
let discoveryData: DiscoveryData
var body: some View {
NavigationLink(destination: DiscoveryDetailView(discoveryData: discoveryData)) {
VStack(alignment: .leading) {
Text("Discovery")
.font(.headline)
Text("\(discoveryData.items.count) new items to discover")
.font(.subheadline)
.foregroundColor(.secondary)
}
}
}
}
struct DiscoveryDetailView: View {
let discoveryData: DiscoveryData
var body: some View {
List(discoveryData.items) { item in
NavigationLink(destination: destinationView(for: item)) {
itemView(for: item)
}
}
.navigationTitle("Discovery")
}
@ViewBuilder
private func itemView(for item: DiscoveryItem) -> some View {
VStack(alignment: .leading) {
switch item.data {
case .realm(let realm):
Text("Realm").font(.headline)
Text(realm.name).foregroundColor(.secondary)
case .publisher(let publisher):
Text("Publisher").font(.headline)
Text(publisher.name).foregroundColor(.secondary)
case .article(let article):
Text("Article").font(.headline)
Text(article.title).foregroundColor(.secondary)
case .unknown:
Text("Unknown item")
}
}
}
@ViewBuilder
private func destinationView(for item: DiscoveryItem) -> some View {
switch item.data {
case .realm(let realm):
RealmDetailView(realm: realm)
case .publisher(let publisher):
PublisherDetailView(publisher: publisher)
case .article(let article):
ArticleDetailView(article: article)
case .unknown:
Text("Detail view not available")
}
}
}
struct RealmDetailView: View {
let realm: SnRealm
var body: some View {
VStack(alignment: .leading, spacing: 8) {
Text(realm.name).font(.headline)
if let description = realm.description {
Text(description).font(.body)
}
}
.navigationTitle("Realm")
}
}
struct PublisherDetailView: View {
let publisher: SnPublisher
var body: some View {
VStack(alignment: .leading, spacing: 8) {
Text(publisher.name).font(.headline)
if let description = publisher.description {
Text(description).font(.body)
}
}
.navigationTitle("Publisher")
}
}
struct ArticleDetailView: View {
let article: SnWebArticle
var body: some View {
VStack(alignment: .leading, spacing: 8) {
Text(article.title).font(.headline)
Text(article.url).font(.caption).foregroundColor(.secondary)
}
.navigationTitle("Article")
}
}

View File

@@ -0,0 +1,67 @@
//
// ExploreView.swift
// WatchRunner Watch App
//
// Created by LittleSheep on 2025/10/29.
//
import SwiftUI
// The main view with the TabView for filtering.
struct ExploreView: View {
@EnvironmentObject private var appState: AppState
@State private var isComposing = false
@State private var selectedTab: String = "Explore"
var body: some View {
NavigationStack {
if appState.isReady {
TabView(selection: $selectedTab) {
ActivityListView(filter: "Explore")
.tag("Explore")
.tabItem {
Label("Explore", systemImage: "safari")
}
.labelStyle(.titleOnly)
ActivityListView(filter: "Subscriptions")
.tag("Subscriptions")
.tabItem {
Label("Subscriptions", systemImage: "star")
}
.labelStyle(.titleOnly)
ActivityListView(filter: "Friends")
.tag("Friends")
.tabItem {
Label("Friends", systemImage: "person.2")
}
.labelStyle(.titleOnly)
}
.navigationTitle(selectedTab)
.toolbar {
ToolbarItem(placement: .primaryAction) {
Button(action: { isComposing = true }) {
Label("Compose", systemImage: "plus")
}
}
}
} else {
VStack {
ProgressView { Text("Syncing...") }
Button("Retry") {
appState.requestData()
}
}
}
}
.sheet(isPresented: $isComposing) {
ComposePostView()
}
.alert("Error", isPresented: .constant(appState.errorMessage != nil), actions: {
Button("OK") { appState.errorMessage = nil }
}, message: {
Text(appState.errorMessage ?? "")
})
}
}

View File

@@ -0,0 +1,34 @@
import SwiftUI
struct ImageViewer: View {
let imageUrl: URL
@EnvironmentObject var appState: AppState
@StateObject private var imageLoader = ImageLoader()
var body: some View {
Group {
if imageLoader.isLoading {
ProgressView()
} else if let image = imageLoader.image {
image
.resizable()
.aspectRatio(contentMode: .fit)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.scaledToFit()
} else if let errorMessage = imageLoader.errorMessage {
Text("Failed to load image: \(errorMessage)")
.font(.caption)
.foregroundColor(.red)
} else {
Text("Failed to load image.")
}
}
.task(id: imageUrl) {
if let token = appState.token {
await imageLoader.loadImage(from: imageUrl, token: token)
}
}
.navigationTitle("Image")
.navigationBarTitleDisplayMode(.inline)
}
}

View File

@@ -0,0 +1,198 @@
//
// NotificationView.swift
// WatchRunner Watch App
//
// Created by LittleSheep on 2025/10/29.
//
import SwiftUI
import Combine
@MainActor
class NotificationViewModel: ObservableObject {
@Published var notifications = [SnNotification]()
@Published var isLoading = false
@Published var isLoadingMore = false
@Published var errorMessage: String?
@Published var hasMore = false
private let networkService = NetworkService()
private var hasFetched = false
private var offset = 0
private let pageSize = 20
func fetchNotifications(token: String, serverUrl: String) async {
if hasFetched { return }
guard !isLoading else { return }
isLoading = true
errorMessage = nil
hasFetched = true
offset = 0
do {
let response = try await networkService.fetchNotifications(offset: offset, take: pageSize, token: token, serverUrl: serverUrl)
self.notifications = response.notifications
self.hasMore = response.hasMore
offset += response.notifications.count
} catch {
self.errorMessage = error.localizedDescription
print("[watchOS] fetchNotifications failed with error: \(error)")
hasFetched = false
}
isLoading = false
}
func loadMoreNotifications(token: String, serverUrl: String) async {
guard !isLoadingMore && hasMore else { return }
isLoadingMore = true
do {
let response = try await networkService.fetchNotifications(offset: offset, take: pageSize, token: token, serverUrl: serverUrl)
self.notifications.append(contentsOf: response.notifications)
self.hasMore = response.hasMore
offset += response.notifications.count
} catch {
self.errorMessage = error.localizedDescription
print("[watchOS] loadMoreNotifications failed with error: \(error)")
}
isLoadingMore = false
}
}
struct NotificationView: View {
@EnvironmentObject var appState: AppState
@StateObject private var viewModel = NotificationViewModel()
var body: some View {
Group {
if viewModel.isLoading {
ProgressView()
} else if let errorMessage = viewModel.errorMessage {
VStack {
Text("Error")
.font(.headline)
Text(errorMessage)
.font(.caption)
Button("Retry") {
Task {
if let token = appState.token, let serverUrl = appState.serverUrl {
await viewModel.fetchNotifications(token: token, serverUrl: serverUrl)
}
}
}
}
.padding()
} else if viewModel.notifications.isEmpty {
Text("No notifications")
} else {
List {
ForEach(viewModel.notifications) { notification in
NavigationLink(destination: NotificationDetailView(notification: notification)) {
VStack(alignment: .leading, spacing: 4) {
HStack {
Text(notification.title)
.font(.headline)
Spacer()
if notification.viewedAt == nil {
Circle()
.fill(Color.blue)
.frame(width: 8, height: 8)
}
}
if !notification.subtitle.isEmpty {
Text(notification.subtitle)
.font(.subheadline)
.foregroundColor(.secondary)
}
if notification.content.count > 100 {
Text(notification.content.prefix(100) + "...")
.font(.caption)
.foregroundColor(.gray)
.lineLimit(2)
} else {
Text(notification.content)
.font(.caption)
.foregroundColor(.gray)
.lineLimit(2)
}
Text(notification.createdAt, style: .relative)
.font(.caption2)
.foregroundColor(.gray)
}
.padding(.vertical, 8)
}
}
if viewModel.hasMore {
if viewModel.isLoadingMore {
HStack {
Spacer()
ProgressView()
Spacer()
}
} else {
Button("Load More") {
Task {
if let token = appState.token, let serverUrl = appState.serverUrl {
await viewModel.loadMoreNotifications(token: token, serverUrl: serverUrl)
}
}
}
.frame(maxWidth: .infinity)
}
}
}
}
}
.onAppear {
if appState.isReady, let token = appState.token, let serverUrl = appState.serverUrl {
Task.detached {
await viewModel.fetchNotifications(token: token, serverUrl: serverUrl)
}
}
}
.navigationTitle("Notifications")
.navigationBarTitleDisplayMode(.inline)
}
}
struct NotificationDetailView: View {
let notification: SnNotification
var body: some View {
ScrollView {
VStack(alignment: .leading, spacing: 16) {
Text(notification.title)
.font(.headline)
if !notification.subtitle.isEmpty {
Text(notification.subtitle)
.font(.subheadline)
.foregroundColor(.secondary)
}
Text(notification.content)
.font(.body)
HStack {
Text(notification.createdAt, style: .date)
Text("·")
Text(notification.createdAt, style: .time)
}
.font(.caption)
.foregroundColor(.gray)
if notification.viewedAt == nil {
Text("Unread")
.font(.caption)
.foregroundColor(.blue)
}
}
.padding()
}
.navigationTitle("Notification")
.navigationBarTitleDisplayMode(.inline)
}
}

View File

@@ -0,0 +1,151 @@
//
// PostViews.swift
// WatchRunner Watch App
//
// Created by LittleSheep on 2025/10/29.
//
import SwiftUI
struct PostRowView: View {
let post: SnPost
@EnvironmentObject var appState: AppState
@StateObject private var imageLoader = ImageLoader() // Instantiate ImageLoader
var body: some View {
VStack(alignment: .leading, spacing: 4) {
HStack {
if imageLoader.isLoading {
ProgressView()
.frame(width: 24, height: 24)
} else if let image = imageLoader.image {
image
.resizable()
.frame(width: 24, height: 24)
.clipShape(Circle())
} else if let errorMessage = imageLoader.errorMessage {
Text("Failed: \(errorMessage)")
.font(.caption)
.foregroundColor(.red)
.frame(width: 24, height: 24)
} else {
// Placeholder if no image and not loading
Image(systemName: "person.circle.fill")
.resizable()
.frame(width: 24, height: 24)
.clipShape(Circle())
.foregroundColor(.gray)
}
Text(post.publisher.nick ?? post.publisher.name)
.font(.subheadline)
.bold()
}
.task(id: post.publisher.picture?.id) { // Use task(id:) to reload image when pictureId changes
if let serverUrl = appState.serverUrl, let pictureId = post.publisher.picture?.id, let imageUrl = getAttachmentUrl(for: pictureId, serverUrl: serverUrl), let token = appState.token {
await imageLoader.loadImage(from: imageUrl, token: token)
}
}
if let title = post.title, !title.isEmpty {
Text(title)
.font(.headline)
}
if let content = post.content, !content.isEmpty {
Text(content)
.font(.body)
}
if !post.attachments.isEmpty {
AttachmentView(attachment: post.attachments[0])
if post.attachments.count > 1 {
HStack(spacing: 8) {
Image(systemName: "paperclip.circle.fill")
.frame(width: 12, height: 12)
.foregroundStyle(.gray)
Text("\(post.attachments.count - 1)+ attachments")
.font(.footnote)
.foregroundStyle(.gray)
}
}
}
}.padding(.vertical)
}
}
struct PostDetailView: View {
let post: SnPost
@EnvironmentObject var appState: AppState
@StateObject private var publisherImageLoader = ImageLoader() // Instantiate ImageLoader for publisher avatar
var body: some View {
ScrollView {
VStack(alignment: .leading, spacing: 8) {
HStack {
if publisherImageLoader.isLoading {
ProgressView()
.frame(width: 32, height: 32)
} else if let image = publisherImageLoader.image {
image
.resizable()
.frame(width: 32, height: 32)
.clipShape(Circle())
} else if let errorMessage = publisherImageLoader.errorMessage {
Text("Failed: \(errorMessage)")
.font(.caption)
.foregroundColor(.red)
.frame(width: 32, height: 32)
} else {
Image(systemName: "person.circle.fill")
.resizable()
.frame(width: 32, height: 32)
.clipShape(Circle())
.foregroundColor(.gray)
}
Text("@\(post.publisher.name)")
.font(.headline)
}
// Use task(id:) to reload image when pictureId changes
.task(id: post.publisher.picture?.id) {
if let serverUrl = appState.serverUrl, let pictureId = post.publisher.picture?.id, let imageUrl = getAttachmentUrl(for: pictureId, serverUrl: serverUrl), let token = appState.token {
await publisherImageLoader.loadImage(from: imageUrl, token: token)
}
}
if let title = post.title, !title.isEmpty {
Text(title)
.font(.title2)
.bold()
}
if let content = post.content, !content.isEmpty {
Text(content)
.font(.body)
}
if !post.attachments.isEmpty {
Text("Attachments").font(.headline)
ForEach(post.attachments) { attachment in
AttachmentView(attachment: attachment)
}
}
if !post.tags.isEmpty {
Text("Tags").font(.headline)
FlowLayout(alignment: .leading, spacing: 4) {
ForEach(post.tags) { tag in
Text("#\(tag.name ?? tag.slug)")
.font(.caption)
.padding(.horizontal, 8)
.padding(.vertical, 3)
.background(Capsule().fill(Color.accentColor.opacity(0.2)))
.cornerRadius(5)
}
}
}
}
.padding()
}
.navigationTitle("Post")
}
}

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