Compare commits

...

17 Commits

Author SHA1 Message Date
Eric Bailey d8682d4a5f Get rid of ad-hoc status checks 2024-05-07 11:27:25 -05:00
Eric Bailey 3baaa94f0e Check for screen focus in app state listener 2024-05-07 10:31:37 -05:00
Eric Bailey e38d398de0 Update history handling
(cherry picked from commit b82b552ba4040adf7ead2377541132a386964ff8)
2024-05-07 10:29:12 -05:00
Eric Bailey b04ba05063 Clean up polling, make sync
(cherry picked from commit 7f75cd04c3bf81c94662785748698640a84bef51)
2024-05-07 10:29:08 -05:00
Eric Bailey b5cb5b65e6 Guard fetchConvo
(cherry picked from commit 8385579d31500bb4bfb60afeecdc1eb3ddd7e747)
2024-05-07 10:29:03 -05:00
Eric Bailey 9ca9feb518 Remove poll events 2024-05-06 22:32:41 -05:00
Eric Bailey 7a2e2a5d5f Reducer-esque 2024-05-06 22:24:38 -05:00
Eric Bailey 39bd62f1a8 Checkpoint: confusing but working state machine 2024-05-06 18:14:09 -05:00
Eric Bailey 2048b3f76e Make init sync also 2024-05-06 18:07:02 -05:00
Eric Bailey ba15e24519 Make state transitions sync 2024-05-06 17:49:49 -05:00
Eric Bailey 402ed23ca4 Add debug and temp guard 2024-05-06 17:34:13 -05:00
Eric Bailey a7b876670d Handle init/resume/suspend/background and polling 2024-05-06 17:18:43 -05:00
Eric Bailey 0b6ace990e [Clipclops] All my clops gone (#3850)
* Handle two common errors, provide more clarity around error states

* Handle failed polling

* Remove unused error type

* format
2024-05-06 15:35:05 -05:00
Ivan Beà 2a1dbd2756 Update catalan messages.po (#3764)
Translate new lines, messages are coming! 
You can check it @jordimas @darccio @ferranrego @MiquelAdell @mvdan @johnnydement @Mejans @surfdude29
2024-05-06 13:27:08 -07:00
Minseo Lee a9feba4efd Update Korean localization (#3809)
* Update messages.po

* Update messages.po

* Update messages.po
2024-05-06 13:26:32 -07:00
Takayuki KUSANO 5cfe5d4f58 Update Japanese translations (#3810)
* Update Japanese translations

* Updated Japanese translation

* Updated Japanese translation

* Update Japanese translations

* Updated based on @Hima-Zinn's suggestion
2024-05-06 13:25:59 -07:00
Frudrax Cheng c90443b37a Update zh-TW translations (#3873)
* 修正及改進翻譯

* Epic zh-TW rebrand

* small fix

* remove reundant "設定" for "偏好"

* legacy for "殘存"

* Update Translator

---------

Co-authored-by: Kuwa Lee <kuwalee1069@gmail.com>
2024-05-06 13:25:07 -07:00
10 changed files with 1599 additions and 948 deletions
+10 -10
View File
@@ -777,11 +777,11 @@ msgstr "Canvia el teu correu"
#: src/Navigation.tsx:302
msgid "Chat"
msgstr ""
msgstr "Xat"
#: src/screens/Messages/Conversation/index.tsx:26
msgid "Chat with {chatId}"
msgstr ""
msgstr "Xateja amb {chatId}"
#: src/screens/Deactivated.tsx:79
#: src/screens/Deactivated.tsx:83
@@ -2858,18 +2858,18 @@ msgstr "Missatge del servidor: {0}"
#: src/screens/Messages/List/index.tsx:34
msgid "Message settings"
msgstr ""
msgstr "Configuració dels missatges"
#: src/Navigation.tsx:517
#: src/screens/Messages/List/index.tsx:103
#: src/view/shell/bottom-bar/BottomBar.tsx:261
#: src/view/shell/desktop/LeftNav.tsx:360
msgid "Messages"
msgstr ""
msgstr "Missatges"
#: src/Navigation.tsx:307
msgid "Messaging settings"
msgstr ""
msgstr "Configuració dels missatges"
#: src/lib/moderation/useReportOptions.ts:45
msgid "Misleading Account"
@@ -3569,7 +3569,7 @@ msgstr "Obre la web enllaçada"
#: src/screens/Messages/List/index.tsx:35
msgid "Opens the message settings page"
msgstr ""
msgstr "Obre la pàgina de configuració dels missatges"
#: src/view/screens/Settings/index.tsx:788
#: src/view/screens/Settings/index.tsx:798
@@ -4044,7 +4044,7 @@ msgstr "Elimina la paraula silenciada de la teva llista"
#: src/view/com/util/post-embeds/QuoteEmbed.tsx:208
msgid "Remove quote"
msgstr ""
msgstr "Elimina la citació"
#: src/view/com/modals/Repost.tsx:48
msgid "Remove repost"
@@ -4081,7 +4081,7 @@ msgstr "Elimina la miniatura per defecte de {0}"
#: src/view/com/util/post-embeds/QuoteEmbed.tsx:209
msgid "Removes quoted post"
msgstr ""
msgstr "Elimina la publicació amb la citació"
#: src/view/screens/Profile.tsx:194
msgid "Replies"
@@ -4413,7 +4413,7 @@ msgstr "Cerca per \"{query}\""
#: src/view/screens/Search/Search.tsx:839
msgid "Search for \"{searchText}\""
msgstr ""
msgstr "Cerca per \"{searchText}\""
#: src/components/TagMenu/index.tsx:145
msgid "Search for all posts by @{authorHandle} with tag {displayTag}"
@@ -6261,7 +6261,7 @@ msgstr "No tens llistes."
#: src/screens/Messages/List/index.tsx:92
msgid "You have no messages yet. Start a conversation with someone!"
msgstr ""
msgstr "Encara no tens missatges. Comença una conversa amb algú!"
#: src/view/screens/ModerationBlockedAccounts.tsx:137
msgid "You have not blocked any accounts yet. To block an account, go to their profile and select \"Block account\" from the menu on their account."
+190 -15
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: ja\n"
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2024-04-27 16:07+0900\n"
"PO-Revision-Date: 2024-05-06 15:47+0900\n"
"Last-Translator: tkusano\n"
"Language-Team: Hima-Zinn, tkusano, dolciss, oboenikui, noritada, middlingphys, hibiki, reindex-ot, haoyayoi, vyv03354\n"
"Plural-Forms: \n"
@@ -247,6 +247,10 @@ msgstr "以前のメールアドレス{0}にメールが送信されました。
msgid "An error occured"
msgstr "エラーが発生しました"
#: src/components/dms/MessageMenu.tsx:132
msgid "An error occurred while trying to delete the message. Please try again."
msgstr "メッセージ削除中にエラーが発生しました。再実行してみてください。"
#: src/lib/moderation/useReportOptions.ts:26
msgid "An issue not included in these options"
msgstr "ほかの選択肢にはあてはまらない問題"
@@ -324,6 +328,14 @@ msgstr "背景"
msgid "Are you sure you want to delete the app password \"{name}\"?"
msgstr "アプリパスワード「{name}」を本当に削除しますか?"
#: src/components/dms/MessageMenu.tsx:90
msgid "Are you sure you want to delete this message? The message will be deleted for you, but not for other participants."
msgstr "このメッセージを本当に削除しますか?このメッセージはあなたからは削除したように見えますが、他の参加者からは削除されません。"
#: src/components/dms/ConvoMenu.tsx:166
msgid "Are you sure you want to leave this conversation? Your messages will be deleted for you, but not for other participants."
msgstr "この会話から退出しますか?あなたのメッセージはあなたからは削除したように見えますが、他の参加者からは削除されません。"
#: src/view/com/feeds/FeedSourceCard.tsx:280
msgid "Are you sure you want to remove {0} from your feeds?"
msgstr "あなたのフィードから{0}を削除してもよろしいですか?"
@@ -389,6 +401,11 @@ msgstr "生年月日:"
msgid "Block"
msgstr "ブロック"
#: src/components/dms/ConvoMenu.tsx:129
#: src/components/dms/ConvoMenu.tsx:133
msgid "Block account"
msgstr "アカウントをブロック"
#: src/view/com/profile/ProfileMenu.tsx:300
#: src/view/com/profile/ProfileMenu.tsx:307
msgid "Block Account"
@@ -617,9 +634,17 @@ msgstr "メールアドレスを変更"
msgid "Chat"
msgstr "チャット"
#: src/screens/Messages/Conversation/index.tsx:26
msgid "Chat with {chatId}"
msgstr "{chatId}とのチャット"
#: src/components/dms/ConvoMenu.tsx:55
msgid "Chat muted"
msgstr "チャットをミュートしました"
#: src/components/dms/ConvoMenu.tsx:87
msgid "Chat settings"
msgstr "チャットの設定"
#: src/components/dms/ConvoMenu.tsx:65
msgid "Chat unmuted"
msgstr "チャットのミュートを解除しました"
#: src/screens/Deactivated.tsx:79
#: src/screens/Deactivated.tsx:83
@@ -966,6 +991,11 @@ msgstr "リストへのリンクをコピー"
msgid "Copy link to post"
msgstr "投稿へのリンクをコピー"
#: src/components/dms/MessageMenu.tsx:89
#: src/components/dms/MessageMenu.tsx:91
msgid "Copy message text"
msgstr "メッセージのテキストをコピー"
#: src/view/com/util/forms/PostDropdownBtn.tsx:230
#: src/view/com/util/forms/PostDropdownBtn.tsx:232
msgid "Copy post text"
@@ -976,6 +1006,10 @@ msgstr "投稿のテキストをコピー"
msgid "Copyright Policy"
msgstr "著作権ポリシー"
#: src/components/dms/ConvoMenu.tsx:79
msgid "Could not leave chat"
msgstr "チャットからの退出に失敗しました"
#: src/view/screens/ProfileFeed.tsx:103
msgid "Could not load feed"
msgstr "フィードの読み込みに失敗しました"
@@ -984,6 +1018,18 @@ msgstr "フィードの読み込みに失敗しました"
msgid "Could not load list"
msgstr "リストの読み込みに失敗しました"
#: src/components/dms/NewChat.tsx:225
msgid "Could not load profiles. Please try again later."
msgstr "プロフィールの読み込みに失敗しました。時間をおいてもう一度お試しください。"
#: src/components/dms/ConvoMenu.tsx:58
msgid "Could not mute chat"
msgstr "チャットのミュートに失敗しました"
#: src/components/dms/ConvoMenu.tsx:68
msgid "Could not unmute chat"
msgstr "チャットのミュートの解除に失敗しました"
#: src/view/com/auth/SplashScreen.tsx:57
#: src/view/com/auth/SplashScreen.web.tsx:101
msgid "Create a new account"
@@ -1088,10 +1134,23 @@ msgstr "アプリパスワードを削除"
msgid "Delete app password?"
msgstr "アプリパスワードを削除しますか?"
#: src/components/dms/MessageMenu.tsx:101
msgid "Delete for me"
msgstr "自分宛を削除"
#: src/view/screens/ProfileList.tsx:417
msgid "Delete List"
msgstr "リストを削除"
#: src/components/dms/MessageMenu.tsx:68
#: src/components/dms/MessageMenu.tsx:88
msgid "Delete message"
msgstr "メッセージを削除"
#: src/components/dms/MessageMenu.tsx:99
msgid "Delete message for me"
msgstr "メッセージの宛先から自分を削除"
#: src/view/com/modals/DeleteAccount.tsx:223
msgid "Delete my account"
msgstr "マイアカウントを削除"
@@ -1584,6 +1643,10 @@ msgstr "アプリパスワードの作成に失敗しました。"
msgid "Failed to create the list. Check your internet connection and try again."
msgstr "リストの作成に失敗しました。インターネットへの接続を確認の上、もう一度お試しください。"
#: src/components/dms/MessageMenu.tsx:130
msgid "Failed to delete message"
msgstr "メッセージの削除に失敗しました"
#: src/view/com/util/forms/PostDropdownBtn.tsx:131
msgid "Failed to delete post, please try again"
msgstr "投稿の削除に失敗しました。もう一度お試しください。"
@@ -1592,6 +1655,10 @@ msgstr "投稿の削除に失敗しました。もう一度お試しください
msgid "Failed to load GIFs"
msgstr "GIFの読み込みに失敗しました"
#: src/screens/Messages/Conversation/MessageListError.tsx:21
msgid "Failed to load past messages."
msgstr "過去のメッセージの読み込みに失敗しました。"
#: src/view/com/lightbox/Lightbox.tsx:83
msgid "Failed to save image: {0}"
msgstr "画像の保存に失敗しました:{0}"
@@ -1853,16 +1920,19 @@ msgstr "ホームへ"
msgid "Go Home"
msgstr "ホームへ"
#: src/view/screens/Search/Search.tsx:827
#: src/view/shell/desktop/Search.tsx:263
#~ msgid "Go to @{queryMaybeHandle}"
#~ msgstr "@{queryMaybeHandle}へ"
#: src/screens/Login/ForgotPasswordForm.tsx:172
#: src/view/com/modals/ChangePassword.tsx:169
msgid "Go to next"
msgstr "次へ"
#: src/components/dms/ConvoMenu.tsx:109
msgid "Go to profile"
msgstr "プロフィールへ"
#: src/components/dms/ConvoMenu.tsx:106
msgid "Go to user's profile"
msgstr "ユーザーのプロフィールへ移動"
#: src/lib/moderation/useGlobalLabelStrings.ts:46
msgid "Graphic Media"
msgstr "生々しいメディア"
@@ -2218,6 +2288,16 @@ msgstr "Blueskyで公開されている内容はこちらを参照してくだ
msgid "Learn more."
msgstr "詳細。"
#: src/components/dms/ConvoMenu.tsx:168
msgid "Leave"
msgstr "退出"
#: src/components/dms/ConvoMenu.tsx:151
#: src/components/dms/ConvoMenu.tsx:154
#: src/components/dms/ConvoMenu.tsx:164
msgid "Leave conversation"
msgstr "会話を退出"
#: src/view/com/modals/lang-settings/ContentLanguagesSettings.tsx:82
msgid "Leave them all unchecked to see any language."
msgstr "どの言語も表示するには、すべてのチェックを外したままにします。"
@@ -2411,10 +2491,18 @@ msgstr "メンションされたユーザー"
msgid "Menu"
msgstr "メニュー"
#: src/screens/Messages/List/index.tsx:209
msgid "Message deleted"
msgstr "メッセージは削除されました"
#: src/view/com/posts/FeedErrorMessage.tsx:192
msgid "Message from server: {0}"
msgstr "サーバーからのメッセージ:{0}"
#: src/screens/Messages/Conversation/MessageInput.tsx:74
msgid "Message input field"
msgstr "メッセージを入力するフィールド"
#: src/screens/Messages/List/index.tsx:34
msgid "Message settings"
msgstr "メッセージの設定"
@@ -2543,6 +2631,11 @@ msgstr "テキストとタグをミュート"
msgid "Mute list"
msgstr "リストをミュート"
#: src/components/dms/ConvoMenu.tsx:114
#: src/components/dms/ConvoMenu.tsx:120
msgid "Mute notifications"
msgstr "通知をミュート"
#: src/view/screens/ProfileList.tsx:621
msgid "Mute these accounts?"
msgstr "これらのアカウントをミュートしますか?"
@@ -2665,6 +2758,12 @@ msgstr "新規"
msgid "New"
msgstr "新規"
#: src/components/dms/NewChat.tsx:59
#: src/screens/Messages/List/index.tsx:346
#: src/screens/Messages/List/index.tsx:354
msgid "New chat"
msgstr "新しいチャット"
#: src/view/com/modals/CreateOrEditList.tsx:255
msgid "New Moderation List"
msgstr "新しいモデレーションリスト"
@@ -2755,6 +2854,11 @@ msgstr "{0}のフォローを解除しました"
msgid "No longer than 253 characters"
msgstr "253文字まで"
#: src/screens/Messages/List/index.tsx:138
#: src/screens/Messages/List/index.tsx:198
msgid "No messages yet"
msgstr "メッセージはありません"
#: src/view/com/notifications/Feed.tsx:109
msgid "No notifications yet!"
msgstr "お知らせはありません!"
@@ -2780,7 +2884,11 @@ msgstr "「{query}」の検索結果はありません"
#: src/components/dialogs/GifSelect.tsx:204
msgid "No search results found for \"{search}\"."
msgstr "「{search}」の検索結果はありません"
msgstr "「{search}」の検索結果はありません"
#: src/components/dms/NewChat.tsx:224
msgid "No search results found for \"{searchText}\"."
msgstr "「{searchText}」の検索結果はありません。"
#: src/components/dialogs/EmbedConsent.tsx:105
#: src/components/dialogs/EmbedConsent.tsx:112
@@ -2834,6 +2942,10 @@ msgstr "注記:Blueskyはオープンでパブリックなネットワーク
msgid "Notifications"
msgstr "通知"
#: src/screens/Messages/Conversation/MessageItem.tsx:116
msgid "Now"
msgstr "今"
#: src/view/com/modals/SelfLabel.tsx:103
msgid "Nudity"
msgstr "ヌード"
@@ -3218,6 +3330,10 @@ msgstr "パスワードも入力してください:"
msgid "Please explain why you think this label was incorrectly applied by {0}"
msgstr "{0}によって貼られたこのラベルが誤って適用されたと思われる理由を説明してください"
#: src/lib/hooks/useAccountSwitcher.ts:43
msgid "Please sign in as @{0}"
msgstr "@{0}としてサインインしてください"
#: src/view/com/modals/VerifyEmail.tsx:110
msgid "Please Verify Your Email"
msgstr "メールアドレスを確認してください"
@@ -3316,6 +3432,11 @@ msgstr "ホスティングプロバイダーを変える"
msgid "Press to retry"
msgstr "再実行する"
#: src/screens/Messages/Conversation/MessagesList.tsx:41
#: src/screens/Messages/Conversation/MessagesList.tsx:47
msgid "Press to Retry"
msgstr "再実行"
#: src/view/com/lightbox/Lightbox.web.tsx:150
msgid "Previous image"
msgstr "前の画像"
@@ -3520,6 +3641,15 @@ msgctxt "description"
msgid "Reply to <0><1/></0>"
msgstr "<0><1/></0>に返信"
#: src/components/dms/MessageMenu.tsx:78
msgid "Report"
msgstr "報告"
#: src/components/dms/ConvoMenu.tsx:140
#: src/components/dms/ConvoMenu.tsx:144
msgid "Report account"
msgstr "アカウントを報告"
#: src/view/com/profile/ProfileMenu.tsx:319
#: src/view/com/profile/ProfileMenu.tsx:322
msgid "Report Account"
@@ -3538,6 +3668,10 @@ msgstr "フィードを報告"
msgid "Report List"
msgstr "リストを報告"
#: src/components/dms/MessageMenu.tsx:76
msgid "Report message"
msgstr "メッセージを報告"
#: src/view/com/util/forms/PostDropdownBtn.tsx:316
#: src/view/com/util/forms/PostDropdownBtn.tsx:318
msgid "Report post"
@@ -3678,6 +3812,10 @@ msgstr "エラーになった最後のアクションをやり直す"
msgid "Retry"
msgstr "再試行"
#: src/screens/Messages/Conversation/MessageListError.tsx:48
msgid "Retry."
msgstr "再試行。"
#: src/components/Error.tsx:95
#: src/view/screens/ProfileList.tsx:919
msgid "Return to previous page"
@@ -3795,6 +3933,10 @@ msgstr "{displayTag}のすべての投稿を検索(@{authorHandle}のみ)"
msgid "Search for all posts with tag {displayTag}"
msgstr "{displayTag}のすべての投稿を検索(すべてのユーザー)"
#: src/components/dms/NewChat.tsx:226
msgid "Search for someone to start a conversation with."
msgstr "会話を始める相手を検索。"
#: src/view/com/auth/LoggedOut.tsx:105
#: src/view/com/auth/LoggedOut.tsx:106
#: src/view/com/modals/ListAddRemoveUsers.tsx:70
@@ -3805,6 +3947,10 @@ msgstr "ユーザーを検索"
msgid "Search GIFs"
msgstr "GIFを検索"
#: src/components/dms/NewChat.tsx:182
msgid "Search profiles"
msgstr "プロフィールを検索"
#: src/components/dialogs/GifSelect.tsx:159
msgid "Search Tenor"
msgstr "Tenorを検索"
@@ -3937,6 +4083,10 @@ msgstr "メールを送信"
msgid "Send feedback"
msgstr "フィードバックを送信"
#: src/screens/Messages/Conversation/MessageInput.tsx:94
msgid "Send message"
msgstr "メッセージを送信"
#: src/components/ReportDialog/SubmitView.tsx:215
#: src/components/ReportDialog/SubmitView.tsx:219
msgid "Send report"
@@ -4309,8 +4459,12 @@ msgstr "スポーツ"
msgid "Square"
msgstr "正方形"
#: src/view/screens/Settings/index.tsx:862
msgid "Status page"
#: src/components/dms/NewChat.tsx:177
msgid "Start a new chat"
msgstr "新しいチャットを開始"
#: src/view/screens/Settings/index.tsx:896
msgid "Status Page"
msgstr "ステータスページ"
#: src/screens/Signup/index.tsx:145
@@ -4799,6 +4953,10 @@ msgstr "再試行"
msgid "Two-factor authentication"
msgstr "2要素認証"
#: src/screens/Messages/Conversation/MessageInput.tsx:75
msgid "Type your message here"
msgstr "ここにメッセージを入力する"
#: src/view/com/modals/ChangeHandle.tsx:429
msgid "Type:"
msgstr "タイプ:"
@@ -4893,6 +5051,10 @@ msgstr "アカウントのミュートを解除"
msgid "Unmute all {displayTag} posts"
msgstr "{displayTag}のすべての投稿のミュートを解除"
#: src/components/dms/ConvoMenu.tsx:118
msgid "Unmute notifications"
msgstr "通知のミュートを解除"
#: src/view/com/util/forms/PostDropdownBtn.tsx:273
#: src/view/com/util/forms/PostDropdownBtn.tsx:278
msgid "Unmute thread"
@@ -5093,9 +5255,9 @@ msgstr "新しいメールアドレスを確認"
msgid "Verify Your Email"
msgstr "メールアドレスを確認"
#: src/view/screens/Settings/index.tsx:852
msgid "Version {0}"
msgstr "バージョン {0}"
#: src/view/screens/Settings/index.tsx:868
msgid "Version {appVersion} {bundleInfo}"
msgstr "バージョン {appVersion} {bundleInfo}"
#: src/screens/Onboarding/index.tsx:42
msgid "Video Games"
@@ -5279,6 +5441,11 @@ msgstr "なぜこのユーザーをレビューする必要がありますか?
msgid "Wide"
msgstr "ワイド"
#: src/screens/Messages/Conversation/MessageInput.tsx:76
#: src/screens/Messages/Conversation/MessageInput.web.tsx:71
msgid "Write a message"
msgstr "メッセージを書く"
#: src/view/com/composer/Composer.tsx:468
msgid "Write post"
msgstr "投稿を書く"
@@ -5302,6 +5469,10 @@ msgstr "ライター"
msgid "Yes"
msgstr "はい"
#: src/screens/Messages/Conversation/MessageItem.tsx:130
msgid "Yesterday, {time}"
msgstr "昨日、{time}"
#: src/screens/Deactivated.tsx:137
msgid "You are in line."
msgstr "あなたは並んでいます。"
@@ -5435,6 +5606,10 @@ msgstr "これ以降、このスレッドに関する通知を受け取ること
msgid "You will receive an email with a \"reset code.\" Enter that code here, then enter your new password."
msgstr "「リセットコード」が記載されたメールが届きます。ここにコードを入力し、新しいパスワードを入力します。"
#: src/screens/Messages/List/index.tsx:202
msgid "You: {0}"
msgstr "あなた: {0}"
#: src/screens/Onboarding/StepModeration/index.tsx:60
msgid "You're in control"
msgstr "あなたがコントロールしています"
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -3,7 +3,7 @@ import {View} from 'react-native'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {ConvoError, ConvoItem} from '#/state/messages/convo'
import {ConvoItem, ConvoItemError} from '#/state/messages/convo'
import {atoms as a, useTheme} from '#/alf'
import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
import {InlineLinkText} from '#/components/Link'
@@ -18,7 +18,13 @@ export function MessageListError({
const {_} = useLingui()
const message = React.useMemo(() => {
return {
[ConvoError.HistoryFailed]: _(msg`Failed to load past messages.`),
[ConvoItemError.Network]: _(
msg`There was an issue connecting to the chat.`,
),
[ConvoItemError.HistoryFailed]: _(msg`Failed to load past messages.`),
[ConvoItemError.PollFailed]: _(
msg`This chat was disconnected due to a network error.`,
),
}[item.code]
}, [_, item.code])
@@ -229,7 +229,7 @@ export function MessagesList() {
<ScrollProvider onScroll={onScroll} onMomentumEnd={onMomentumEnd}>
<List
ref={flatListRef}
data={chat.status === ConvoStatus.Ready ? chat.items : undefined}
data={chat.items}
renderItem={renderItem}
keyExtractor={keyExtractor}
disableVirtualization={true}
@@ -248,11 +248,7 @@ export function MessagesList() {
onScrollToIndexFailed={onScrollToIndexFailed}
scrollEventThrottle={100}
ListHeaderComponent={
<MaybeLoader
isLoading={
chat.status === ConvoStatus.Ready && chat.isFetchingHistory
}
/>
<MaybeLoader isLoading={chat.isFetchingHistory} />
}
/>
</ScrollProvider>
+26 -14
View File
@@ -14,11 +14,11 @@ import {BACK_HITSLOP} from 'lib/constants'
import {isWeb} from 'platform/detection'
import {ChatProvider, useChat} from 'state/messages'
import {ConvoStatus} from 'state/messages/convo'
import {useSession} from 'state/session'
import {PreviewableUserAvatar} from 'view/com/util/UserAvatar'
import {CenteredView} from 'view/com/util/Views'
import {MessagesList} from '#/screens/Messages/Conversation/MessagesList'
import {atoms as a, useBreakpoints, useTheme} from '#/alf'
import {Button, ButtonText} from '#/components/Button'
import {ConvoMenu} from '#/components/dms/ConvoMenu'
import {ListMaybePlaceholder} from '#/components/Lists'
import {Text} from '#/components/Typography'
@@ -43,28 +43,40 @@ export function MessagesConversationScreen({route}: Props) {
function Inner() {
const chat = useChat()
const {currentAccount} = useSession()
const myDid = currentAccount?.did
const otherProfile = React.useMemo(() => {
if (chat.status !== ConvoStatus.Ready) return
return chat.convo.members.find(m => m.did !== myDid)
}, [chat, myDid])
if (
chat.status === ConvoStatus.Uninitialized ||
chat.status === ConvoStatus.Initializing
) {
return <ListMaybePlaceholder isLoading />
}
// TODO whenever we have error messages, we should use them in here -hailey
if (chat.status !== ConvoStatus.Ready || !otherProfile) {
if (chat.status === ConvoStatus.Error) {
// TODO
return (
<ListMaybePlaceholder
isLoading={true}
isError={chat.status === ConvoStatus.Error}
/>
<View>
<CenteredView style={{flex: 1}} sideBorders>
<Text>Something went wrong</Text>
<Button
label="Retry"
onPress={() => {
chat.error.retry()
}}>
<ButtonText>Retry</ButtonText>
</Button>
</CenteredView>
</View>
)
}
/*
* Any other chat states (atm) are "ready" states
*/
return (
<KeyboardProvider>
<CenteredView style={{flex: 1}} sideBorders>
<Header profile={otherProfile} />
<Header profile={chat.recipients[0]} />
<MessagesList />
</CenteredView>
</KeyboardProvider>
+10 -6
View File
@@ -1,15 +1,19 @@
import {describe, it} from '@jest/globals'
describe(`#/state/messages/convo`, () => {
describe(`status states`, () => {
describe(`init`, () => {
it.todo(`fails if sender and recipients aren't found`)
it.todo(`cannot re-initialize from a non-unintialized state`)
it.todo(`can re-initialize from a failed state`)
})
describe(`destroy`, () => {
it.todo(`cannot be interacted with when destroyed`)
it.todo(`polling is stopped when destroyed`)
it.todo(`events are cleaned up when destroyed`)
})
describe(`resume`, () => {
it.todo(`restores previous state if resume fails`)
})
describe(`suspend`, () => {
it.todo(`cannot be interacted with when suspended`)
it.todo(`polling is stopped when suspended`)
})
describe(`read states`, () => {
+480 -142
View File
@@ -2,6 +2,7 @@ import {AppBskyActorDefs} from '@atproto/api'
import {
BskyAgent,
ChatBskyConvoDefs,
ChatBskyConvoGetLog,
ChatBskyConvoSendMessage,
} from '@atproto-labs/api'
import {nanoid} from 'nanoid/non-secure'
@@ -18,17 +19,58 @@ export type ConvoParams = {
export enum ConvoStatus {
Uninitialized = 'uninitialized',
Initializing = 'initializing',
Resuming = 'resuming',
Ready = 'ready',
Error = 'error',
Backgrounded = 'backgrounded',
Suspended = 'suspended',
}
export enum ConvoError {
export enum ConvoItemError {
HistoryFailed = 'historyFailed',
PollFailed = 'pollFailed',
Network = 'network',
}
export enum ConvoErrorCode {
InitFailed = 'initFailed',
}
export type ConvoError = {
code: ConvoErrorCode
exception?: Error
retry: () => void
}
export enum ConvoDispatchEvent {
Init = 'init',
Ready = 'ready',
Resume = 'resume',
Background = 'background',
Suspend = 'suspend',
Error = 'error',
}
export type ConvoDispatch =
| {
event: ConvoDispatchEvent.Init
}
| {
event: ConvoDispatchEvent.Ready
}
| {
event: ConvoDispatchEvent.Resume
}
| {
event: ConvoDispatchEvent.Background
}
| {
event: ConvoDispatchEvent.Suspend
}
| {
event: ConvoDispatchEvent.Error
payload: ConvoError
}
export type ConvoItem =
| {
type: 'message' | 'pending-message'
@@ -56,14 +98,9 @@ export type ConvoItem =
| {
type: 'error-recoverable'
key: string
code: ConvoError
code: ConvoItemError
retry: () => void
}
| {
type: 'error-fatal'
code: ConvoError
key: string
}
export type ConvoState =
| {
@@ -71,6 +108,8 @@ export type ConvoState =
items: []
convo: undefined
error: undefined
sender: undefined
recipients: undefined
isFetchingHistory: false
deleteMessage: undefined
sendMessage: undefined
@@ -81,6 +120,8 @@ export type ConvoState =
items: []
convo: undefined
error: undefined
sender: undefined
recipients: undefined
isFetchingHistory: boolean
deleteMessage: undefined
sendMessage: undefined
@@ -91,6 +132,8 @@ export type ConvoState =
items: ConvoItem[]
convo: ChatBskyConvoDefs.ConvoView
error: undefined
sender: AppBskyActorDefs.ProfileViewBasic
recipients: AppBskyActorDefs.ProfileViewBasic[]
isFetchingHistory: boolean
deleteMessage: (messageId: string) => Promise<void>
sendMessage: (
@@ -103,6 +146,8 @@ export type ConvoState =
items: ConvoItem[]
convo: ChatBskyConvoDefs.ConvoView
error: undefined
sender: AppBskyActorDefs.ProfileViewBasic
recipients: AppBskyActorDefs.ProfileViewBasic[]
isFetchingHistory: boolean
deleteMessage: (messageId: string) => Promise<void>
sendMessage: (
@@ -115,18 +160,8 @@ export type ConvoState =
items: ConvoItem[]
convo: ChatBskyConvoDefs.ConvoView
error: undefined
isFetchingHistory: boolean
deleteMessage: (messageId: string) => Promise<void>
sendMessage: (
message: ChatBskyConvoSendMessage.InputSchema['message'],
) => Promise<void>
fetchMessageHistory: () => Promise<void>
}
| {
status: ConvoStatus.Resuming
items: ConvoItem[]
convo: ChatBskyConvoDefs.ConvoView
error: undefined
sender: AppBskyActorDefs.ProfileViewBasic
recipients: AppBskyActorDefs.ProfileViewBasic[]
isFetchingHistory: boolean
deleteMessage: (messageId: string) => Promise<void>
sendMessage: (
@@ -139,15 +174,20 @@ export type ConvoState =
items: []
convo: undefined
error: any
sender: undefined
recipients: undefined
isFetchingHistory: false
deleteMessage: undefined
sendMessage: undefined
fetchMessageHistory: undefined
}
const ACTIVE_POLL_INTERVAL = 2e3
const ACTIVE_POLL_INTERVAL = 1e3
const BACKGROUND_POLL_INTERVAL = 10e3
// TODO temporary
let DEBUG_ACTIVE_CHAT: string | undefined
export function isConvoItemMessage(
item: ConvoItem,
): item is ConvoItem & {type: 'message'} {
@@ -160,12 +200,20 @@ export function isConvoItemMessage(
}
export class Convo {
private id: string
private agent: BskyAgent
private __tempFromUserDid: string
private pollInterval = ACTIVE_POLL_INTERVAL
private status: ConvoStatus = ConvoStatus.Uninitialized
private error: any
private pollInterval = ACTIVE_POLL_INTERVAL
private error:
| {
code: ConvoErrorCode
exception?: Error
retry: () => void
}
| undefined
private historyCursor: string | undefined | null = undefined
private isFetchingHistory = false
private eventsCursor: string | undefined = undefined
@@ -186,15 +234,18 @@ export class Convo {
private footerItems: Map<string, ConvoItem> = new Map()
private headerItems: Map<string, ConvoItem> = new Map()
private pendingEventIngestion: Promise<void> | undefined
private isProcessingPendingMessages = false
private pendingPoll: Promise<void> | undefined
private nextPoll: NodeJS.Timeout | undefined
convoId: string
convo: ChatBskyConvoDefs.ConvoView | undefined
sender: AppBskyActorDefs.ProfileViewBasic | undefined
recipients: AppBskyActorDefs.ProfileViewBasic[] | undefined = undefined
snapshot: ConvoState | undefined
constructor(params: ConvoParams) {
this.id = nanoid(3)
this.convoId = params.convoId
this.agent = params.agent
this.__tempFromUserDid = params.__tempFromUserDid
@@ -204,6 +255,14 @@ export class Convo {
this.sendMessage = this.sendMessage.bind(this)
this.deleteMessage = this.deleteMessage.bind(this)
this.fetchMessageHistory = this.fetchMessageHistory.bind(this)
if (DEBUG_ACTIVE_CHAT) {
logger.error(`Convo: another chat was already active`, {
convoId: this.convoId,
})
} else {
DEBUG_ACTIVE_CHAT = this.convoId
}
}
private commit() {
@@ -226,7 +285,7 @@ export class Convo {
getSnapshot(): ConvoState {
if (!this.snapshot) this.snapshot = this.generateSnapshot()
logger.debug('Convo: snapshotted', {}, logger.DebugContext.convo)
// logger.debug('Convo: snapshotted', {}, logger.DebugContext.convo)
return this.snapshot
}
@@ -238,6 +297,8 @@ export class Convo {
items: [],
convo: undefined,
error: undefined,
sender: undefined,
recipients: undefined,
isFetchingHistory: this.isFetchingHistory,
deleteMessage: undefined,
sendMessage: undefined,
@@ -246,13 +307,14 @@ export class Convo {
}
case ConvoStatus.Suspended:
case ConvoStatus.Backgrounded:
case ConvoStatus.Resuming:
case ConvoStatus.Ready: {
return {
status: this.status,
items: this.getItems(),
convo: this.convo!,
error: undefined,
sender: this.sender!,
recipients: this.recipients!,
isFetchingHistory: this.isFetchingHistory,
deleteMessage: this.deleteMessage,
sendMessage: this.sendMessage,
@@ -265,6 +327,8 @@ export class Convo {
items: [],
convo: undefined,
error: this.error,
sender: undefined,
recipients: undefined,
isFetchingHistory: false,
deleteMessage: undefined,
sendMessage: undefined,
@@ -277,6 +341,8 @@ export class Convo {
items: [],
convo: undefined,
error: undefined,
sender: undefined,
recipients: undefined,
isFetchingHistory: false,
deleteMessage: undefined,
sendMessage: undefined,
@@ -286,87 +352,310 @@ export class Convo {
}
}
async init() {
logger.debug('Convo: init', {}, logger.DebugContext.convo)
dispatch(action: ConvoDispatch) {
const prevStatus = this.status
if (this.status === ConvoStatus.Uninitialized) {
try {
this.status = ConvoStatus.Initializing
this.commit()
await this.refreshConvo()
this.status = ConvoStatus.Ready
this.commit()
await this.fetchMessageHistory()
this.pollEvents()
} catch (e) {
this.error = e
this.status = ConvoStatus.Error
this.commit()
switch (this.status) {
case ConvoStatus.Uninitialized: {
switch (action.event) {
case ConvoDispatchEvent.Init: {
this.status = ConvoStatus.Initializing
this.setup()
break
}
}
break
}
} else {
logger.warn(`Convo: cannot init from ${this.status}`)
}
}
async resume() {
logger.debug('Convo: resume', {}, logger.DebugContext.convo)
if (
this.status === ConvoStatus.Suspended ||
this.status === ConvoStatus.Backgrounded
) {
try {
this.status = ConvoStatus.Resuming
this.commit()
await this.refreshConvo()
this.status = ConvoStatus.Ready
this.commit()
await this.fetchMessageHistory()
this.pollInterval = ACTIVE_POLL_INTERVAL
this.pollEvents()
} catch (e) {
// TODO handle errors in one place
this.error = e
this.status = ConvoStatus.Error
this.commit()
case ConvoStatus.Initializing: {
switch (action.event) {
case ConvoDispatchEvent.Ready: {
this.status = ConvoStatus.Ready
this.pollInterval = ACTIVE_POLL_INTERVAL
this.fetchMessageHistory().then(() => {
this.restartPoll()
})
break
}
case ConvoDispatchEvent.Background: {
this.status = ConvoStatus.Backgrounded
this.pollInterval = BACKGROUND_POLL_INTERVAL
this.fetchMessageHistory().then(() => {
this.restartPoll()
})
break
}
case ConvoDispatchEvent.Suspend: {
this.status = ConvoStatus.Suspended
break
}
case ConvoDispatchEvent.Error: {
this.status = ConvoStatus.Error
this.error = action.payload
break
}
}
break
}
} else {
logger.warn(`Convo: cannot resume from ${this.status}`)
case ConvoStatus.Ready: {
switch (action.event) {
case ConvoDispatchEvent.Resume: {
this.refreshConvo()
this.restartPoll()
break
}
case ConvoDispatchEvent.Background: {
this.status = ConvoStatus.Backgrounded
this.pollInterval = BACKGROUND_POLL_INTERVAL
this.restartPoll()
break
}
case ConvoDispatchEvent.Suspend: {
this.status = ConvoStatus.Suspended
this.cancelNextPoll()
break
}
case ConvoDispatchEvent.Error: {
this.status = ConvoStatus.Error
this.error = action.payload
this.cancelNextPoll()
break
}
}
break
}
case ConvoStatus.Backgrounded: {
switch (action.event) {
case ConvoDispatchEvent.Resume: {
this.status = ConvoStatus.Ready
this.pollInterval = ACTIVE_POLL_INTERVAL
this.refreshConvo()
// TODO truncate history if needed
this.restartPoll()
break
}
case ConvoDispatchEvent.Suspend: {
this.status = ConvoStatus.Suspended
this.cancelNextPoll()
break
}
case ConvoDispatchEvent.Error: {
this.status = ConvoStatus.Error
this.error = action.payload
this.cancelNextPoll()
break
}
}
break
}
case ConvoStatus.Suspended: {
switch (action.event) {
case ConvoDispatchEvent.Init: {
this.status = ConvoStatus.Ready
this.pollInterval = ACTIVE_POLL_INTERVAL
this.refreshConvo()
// TODO truncate history if needed
this.restartPoll()
break
}
case ConvoDispatchEvent.Resume: {
this.status = ConvoStatus.Ready
this.pollInterval = ACTIVE_POLL_INTERVAL
this.refreshConvo()
this.restartPoll()
break
}
case ConvoDispatchEvent.Error: {
this.status = ConvoStatus.Error
this.error = action.payload
break
}
}
break
}
case ConvoStatus.Error: {
switch (action.event) {
case ConvoDispatchEvent.Init: {
this.reset()
break
}
case ConvoDispatchEvent.Resume: {
this.reset()
break
}
case ConvoDispatchEvent.Suspend: {
this.status = ConvoStatus.Suspended
break
}
case ConvoDispatchEvent.Error: {
this.status = ConvoStatus.Error
this.error = action.payload
break
}
}
break
}
default:
break
}
}
async background() {
logger.debug('Convo: backgrounded', {}, logger.DebugContext.convo)
this.status = ConvoStatus.Backgrounded
this.pollInterval = BACKGROUND_POLL_INTERVAL
logger.debug(
`Convo: dispatch '${action.event}'`,
{
id: this.id,
prev: prevStatus,
next: this.status,
},
logger.DebugContext.convo,
)
this.commit()
}
async suspend() {
logger.debug('Convo: suspended', {}, logger.DebugContext.convo)
this.status = ConvoStatus.Suspended
this.commit()
private reset() {
this.convo = undefined
this.sender = undefined
this.recipients = undefined
this.snapshot = undefined
this.status = ConvoStatus.Uninitialized
this.error = undefined
this.historyCursor = undefined
this.eventsCursor = undefined
this.pastMessages = new Map()
this.newMessages = new Map()
this.pendingMessages = new Map()
this.deletedMessages = new Set()
this.footerItems = new Map()
this.headerItems = new Map()
this.dispatch({event: ConvoDispatchEvent.Init})
}
private async setup() {
try {
const {convo, sender, recipients} = await this.fetchConvo()
this.convo = convo
this.sender = sender
this.recipients = recipients
/*
* Some validation prior to `Ready` status
*/
if (!this.convo) {
throw new Error('Convo: could not find convo')
}
if (!this.sender) {
throw new Error('Convo: could not find sender in convo')
}
if (!this.recipients) {
throw new Error('Convo: could not find recipients in convo')
}
// await new Promise(y => setTimeout(y, 2000))
// throw new Error('UNCOMMENT TO TEST INIT FAILURE')
this.dispatch({event: ConvoDispatchEvent.Ready})
} catch (e: any) {
logger.error('Convo: setup() failed')
this.dispatch({
event: ConvoDispatchEvent.Error,
payload: {
exception: e,
code: ConvoErrorCode.InitFailed,
retry: () => {
this.reset()
},
},
})
}
}
init() {
this.dispatch({event: ConvoDispatchEvent.Init})
}
resume() {
this.dispatch({event: ConvoDispatchEvent.Resume})
}
background() {
this.dispatch({event: ConvoDispatchEvent.Background})
}
suspend() {
this.dispatch({event: ConvoDispatchEvent.Suspend})
DEBUG_ACTIVE_CHAT = undefined
}
private pendingFetchConvo:
| Promise<{
convo: ChatBskyConvoDefs.ConvoView
sender: AppBskyActorDefs.ProfileViewBasic | undefined
recipients: AppBskyActorDefs.ProfileViewBasic[]
}>
| undefined
async fetchConvo() {
if (this.pendingFetchConvo) return this.pendingFetchConvo
this.pendingFetchConvo = new Promise<{
convo: ChatBskyConvoDefs.ConvoView
sender: AppBskyActorDefs.ProfileViewBasic | undefined
recipients: AppBskyActorDefs.ProfileViewBasic[]
}>(async (resolve, reject) => {
try {
const response = await this.agent.api.chat.bsky.convo.getConvo(
{
convoId: this.convoId,
},
{
headers: {
Authorization: this.__tempFromUserDid,
},
},
)
const convo = response.data.convo
resolve({
convo,
sender: convo.members.find(m => m.did === this.__tempFromUserDid),
recipients: convo.members.filter(
m => m.did !== this.__tempFromUserDid,
),
})
} catch (e) {
reject(e)
} finally {
this.pendingFetchConvo = undefined
}
})
return this.pendingFetchConvo
}
async refreshConvo() {
const response = await this.agent.api.chat.bsky.convo.getConvo(
{
convoId: this.convoId,
},
{
headers: {
Authorization: this.__tempFromUserDid,
try {
const {convo, sender, recipients} = await this.fetchConvo()
// throw new Error('UNCOMMENT TO TEST REFRESH FAILURE')
this.convo = convo || this.convo
this.sender = sender || this.sender
this.recipients = recipients || this.recipients
} catch (e: any) {
logger.error(`Convo: failed to refresh convo`)
this.footerItems.set(ConvoItemError.Network, {
type: 'error-recoverable',
key: ConvoItemError.Network,
code: ConvoItemError.Network,
retry: () => {
this.footerItems.delete(ConvoItemError.Network)
this.resume()
},
},
)
this.convo = response.data.convo
this.sender = this.convo.members.find(m => m.did === this.__tempFromUserDid)
})
this.commit()
}
}
async fetchMessageHistory() {
@@ -386,7 +675,7 @@ export class Convo {
* If we've rendered a retry state for history fetching, exit. Upon retry,
* this will be removed and we'll try again.
*/
if (this.headerItems.has(ConvoError.HistoryFailed)) return
if (this.headerItems.has(ConvoItemError.HistoryFailed)) return
try {
this.isFetchingHistory = true
@@ -435,12 +724,12 @@ export class Convo {
} catch (e: any) {
logger.error('Convo: failed to fetch message history')
this.headerItems.set(ConvoError.HistoryFailed, {
this.headerItems.set(ConvoItemError.HistoryFailed, {
type: 'error-recoverable',
key: ConvoError.HistoryFailed,
code: ConvoError.HistoryFailed,
key: ConvoItemError.HistoryFailed,
code: ConvoItemError.HistoryFailed,
retry: () => {
this.headerItems.delete(ConvoError.HistoryFailed)
this.headerItems.delete(ConvoItemError.HistoryFailed)
this.fetchMessageHistory()
},
})
@@ -450,85 +739,134 @@ export class Convo {
}
}
private async pollEvents() {
if (
this.status === ConvoStatus.Ready ||
this.status === ConvoStatus.Backgrounded
) {
if (this.pendingEventIngestion) return
private restartPoll() {
this.cancelNextPoll()
this.pollLatestEvents()
}
setTimeout(async () => {
this.pendingEventIngestion = this.ingestLatestEvents()
await this.pendingEventIngestion
this.pendingEventIngestion = undefined
this.pollEvents()
private cancelNextPoll() {
if (this.nextPoll) clearTimeout(this.nextPoll)
}
private pollLatestEvents() {
/*
* Uncomment to view poll events
*/
logger.debug('Convo: poll events', {id: this.id}, logger.DebugContext.convo)
try {
this.fetchLatestEvents().then(({events}) => {
this.applyLatestEvents(events)
})
this.nextPoll = setTimeout(() => {
this.pollLatestEvents()
}, this.pollInterval)
} catch (e: any) {
logger.error('Convo: poll events failed')
this.cancelNextPoll()
this.footerItems.set(ConvoItemError.PollFailed, {
type: 'error-recoverable',
key: ConvoItemError.PollFailed,
code: ConvoItemError.PollFailed,
retry: () => {
this.footerItems.delete(ConvoItemError.PollFailed)
this.commit()
this.pollLatestEvents()
},
})
this.commit()
}
}
async ingestLatestEvents() {
const response = await this.agent.api.chat.bsky.convo.getLog(
{
cursor: this.eventsCursor,
},
{
headers: {
Authorization: this.__tempFromUserDid,
},
},
)
const {logs} = response.data
private pendingFetchLatestEvents:
| Promise<{
events: ChatBskyConvoGetLog.OutputSchema['logs']
}>
| undefined
async fetchLatestEvents() {
if (this.pendingFetchLatestEvents) return this.pendingFetchLatestEvents
this.pendingFetchLatestEvents = new Promise<{
events: ChatBskyConvoGetLog.OutputSchema['logs']
}>(async (resolve, reject) => {
try {
// throw new Error('UNCOMMENT TO TEST POLL FAILURE')
const response = await this.agent.api.chat.bsky.convo.getLog(
{
cursor: this.eventsCursor,
},
{
headers: {
Authorization: this.__tempFromUserDid,
},
},
)
const {logs} = response.data
resolve({events: logs})
} catch (e) {
reject(e)
} finally {
this.pendingFetchLatestEvents = undefined
}
})
return this.pendingFetchLatestEvents
}
private applyLatestEvents(events: ChatBskyConvoGetLog.OutputSchema['logs']) {
let needsCommit = false
for (const log of logs) {
for (const ev of events) {
/*
* If there's a rev, we should handle it. If there's not a rev, we don't
* know what it is.
*/
if (typeof log.rev === 'string') {
if (typeof ev.rev === 'string') {
/*
* We only care about new events
*/
if (log.rev > (this.eventsCursor = this.eventsCursor || log.rev)) {
if (ev.rev > (this.eventsCursor = this.eventsCursor || ev.rev)) {
/*
* Update rev regardless of if it's a log type we care about or not
* Update rev regardless of if it's a ev type we care about or not
*/
this.eventsCursor = log.rev
this.eventsCursor = ev.rev
/*
* This is VERY important. We don't want to insert any messages from
* your other chats.
*/
if (log.convoId !== this.convoId) continue
if (ev.convoId !== this.convoId) continue
if (
ChatBskyConvoDefs.isLogCreateMessage(log) &&
ChatBskyConvoDefs.isMessageView(log.message)
ChatBskyConvoDefs.isLogCreateMessage(ev) &&
ChatBskyConvoDefs.isMessageView(ev.message)
) {
if (this.newMessages.has(log.message.id)) {
// Trust the log as the source of truth on ordering
this.newMessages.delete(log.message.id)
if (this.newMessages.has(ev.message.id)) {
// Trust the ev as the source of truth on ordering
this.newMessages.delete(ev.message.id)
}
this.newMessages.set(log.message.id, log.message)
this.newMessages.set(ev.message.id, ev.message)
needsCommit = true
} else if (
ChatBskyConvoDefs.isLogDeleteMessage(log) &&
ChatBskyConvoDefs.isDeletedMessageView(log.message)
ChatBskyConvoDefs.isLogDeleteMessage(ev) &&
ChatBskyConvoDefs.isDeletedMessageView(ev.message)
) {
/*
* Update if we have this in state. If we don't, don't worry about it.
*/
if (this.pastMessages.has(log.message.id)) {
if (this.pastMessages.has(ev.message.id)) {
/*
* For now, we remove deleted messages from the thread, if we receive one.
*
* To support them, it'd look something like this:
* this.pastMessages.set(log.message.id, log.message)
* this.pastMessages.set(ev.message.id, ev.message)
*/
this.pastMessages.delete(log.message.id)
this.newMessages.delete(log.message.id)
this.deletedMessages.delete(log.message.id)
this.pastMessages.delete(ev.message.id)
this.newMessages.delete(ev.message.id)
this.deletedMessages.delete(ev.message.id)
needsCommit = true
}
}
+21 -1
View File
@@ -1,6 +1,7 @@
import React, {useContext, useState, useSyncExternalStore} from 'react'
import {AppState} from 'react-native'
import {BskyAgent} from '@atproto-labs/api'
import {useFocusEffect} from '@react-navigation/native'
import {useFocusEffect, useIsFocused} from '@react-navigation/native'
import {Convo, ConvoParams, ConvoState} from '#/state/messages/convo'
import {useAgent} from '#/state/session'
@@ -20,6 +21,7 @@ export function ChatProvider({
children,
convoId,
}: Pick<ConvoParams, 'convoId'> & {children: React.ReactNode}) {
const isScreenFocused = useIsFocused()
const {serviceUrl} = useDmServiceUrlStorage()
const {getAgent} = useAgent()
const [convo] = useState(
@@ -44,5 +46,23 @@ export function ChatProvider({
}, [convo]),
)
React.useEffect(() => {
const handleAppStateChange = (nextAppState: string) => {
if (isScreenFocused) {
if (nextAppState === 'active') {
convo.resume()
} else {
convo.background()
}
}
}
const sub = AppState.addEventListener('change', handleAppStateChange)
return () => {
sub.remove()
}
}, [convo, isScreenFocused])
return <ChatContext.Provider value={service}>{children}</ChatContext.Provider>
}