From 4292ffe66fdfa12308537c3e06eca59f771a9349 Mon Sep 17 00:00:00 2001 From: fanyu Date: Fri, 9 Jun 2023 10:09:14 +0800 Subject: [PATCH 01/28] Add localizable strings --- Mixin/Resources/en.lproj/Localizable.strings | 17 +++++++++++++++++ Mixin/Resources/es.lproj/Localizable.strings | 17 +++++++++++++++++ Mixin/Resources/ja.lproj/Localizable.strings | 17 +++++++++++++++++ Mixin/Resources/ru.lproj/Localizable.strings | 17 +++++++++++++++++ .../Resources/zh-Hans.lproj/Localizable.strings | 17 +++++++++++++++++ .../Resources/zh-Hant.lproj/Localizable.strings | 17 +++++++++++++++++ 6 files changed, 102 insertions(+) diff --git a/Mixin/Resources/en.lproj/Localizable.strings b/Mixin/Resources/en.lproj/Localizable.strings index 2b435608ab..6bd40b7bce 100644 --- a/Mixin/Resources/en.lproj/Localizable.strings +++ b/Mixin/Resources/en.lproj/Localizable.strings @@ -55,7 +55,9 @@ "alert_key_group_transcript_message" = "%@ sent a transcript"; "alert_key_group_video_message" = "%@ sent a video"; "all" = "All"; +"all_chats" = "All chats"; "all_conversations" = "All Conversations"; +"all_dates" = "All dates"; "all_photos" = "All Photos"; "all_signer_failure" = "All node failure, perhaps PIN does not match what was set when it was last terminated unexpectedly"; "all_transactions" = "All Transactions"; @@ -179,6 +181,8 @@ "chat_text_size" = "Chat Text Size"; "chat_waiting" = "Waiting for %1$@ to get online and establish an encrypted session. %2$@."; "chats" = "CHATS"; +"chats_count_one" = "1 chat"; +"chats_count" = "%d chats"; "choose_network" = "Choose Network"; "choose_network_tip" = "Please ensure the network you choose to deposit that Mixin supports. Otherwise, your assets may be lost."; "choose_photo" = "Choose Photo"; @@ -299,6 +303,8 @@ "deposit_tip_eos" = "This address supports all base on EOS tokens."; "deposit_tip_eth" = "This address supports all ERC-20 tokens, such as ETH, XIN, etc."; "deposit_tip_trx" = "This address supports all TRC-10 and TRC-20 tokens."; +"deselect_all" = "Deselect All"; +"designated_time_frame" = "Designated time frame"; "desktop_on_hint" = "You have your desktop logged in"; "desktop_upgrade" = "Please upgrade Mixin Messenger Desktop to the latest version."; "detect_qr_tip" = "Detected a Mixin QR code, tap to recognize"; @@ -454,6 +460,8 @@ "invite_to_group_via_link" = "Invite to Group via Link"; "invited_by_stranger" = "The inviter is not in your contacts"; "invites_you_to_a_voice_call" = "invites you to a voice call"; +"items_selected_one" = "1 Item Selected"; +"items_selected_count" = "%d Items Selected"; "join_group" = "Join Group"; "joined_in" = "Joined in %@"; "keep_running_foreground" = "Please do not close the screen and keep Mixin running in the foreground"; @@ -465,6 +473,10 @@ "large_amount_confirmation_with_symbol" = "Large Amount Confirmation(%@)"; "last_active_time" = "Last active %@"; "last_backup_hint" = "Last backup on %@, total size %@."; +"last_month" = "Last month"; +"last_month_count" = "Last %d months"; +"last_year" = "Last year"; +"last_year_count" = "Last %d years"; "later" = "Later"; "learn_more" = "Learn More"; "light" = "Light"; @@ -512,6 +524,7 @@ "mixin_messenger_desktop" = "Mixin Messenger Desktop"; "mixin_server_encounters_errors" = "Mixin server encounters errors"; "mobile_contacts" = "Mobile Contacts"; +"month" = "Month"; "monthly" = "Monthly"; "more" = "More"; "move_and_scale" = "Move and Scale"; @@ -675,6 +688,7 @@ "receive_money" = "Receive Money"; "receiver" = "Receiver"; "receivers" = "Receivers"; +"recent" = "Recent"; "recent_chats" = "CHATS"; "recent_searches" = "Recent searches"; "refresh" = "Refresh"; @@ -749,6 +763,7 @@ "security" = "Security"; "select" = "Select"; "select_a_country_or_region" = "Select a Country or Region"; +"select_all" = "Select All"; "select_emergency_contact" = "Select Emergency Contact"; "select_more_photos" = "Select More Photos"; "selected_count" = "%@ Selected"; @@ -823,6 +838,7 @@ "show" = "Show"; "show_asset" = "Show asset"; "show_in_chat" = "Show in chat"; +"show_selected" = "Show Selected(%d)"; "sign_in" = "Sign in"; "sign_with_emergency_contact" = "Sign in with emergency contact"; "sign_with_phone_number" = "Sign in with phone number"; @@ -1007,6 +1023,7 @@ "withdrawal_network_fee" = "Network fee: "; "withdrawal_no_memo" = "No Memo"; "withdrawal_to" = "Withdraw to %@"; +"year" = "Year"; "you" = "You"; "you_deleted_this_message" = "You deleted this message"; "you_have_a_new_message" = "You have a new message"; diff --git a/Mixin/Resources/es.lproj/Localizable.strings b/Mixin/Resources/es.lproj/Localizable.strings index b69dddb4e6..116aea3a97 100644 --- a/Mixin/Resources/es.lproj/Localizable.strings +++ b/Mixin/Resources/es.lproj/Localizable.strings @@ -55,7 +55,9 @@ "alert_key_group_transcript_message" = "%@ ha enviado una transcripción"; "alert_key_group_video_message" = "%@ ha enviado un vídeo"; "all" = "Todo"; +"all_chats" = "All chats"; "all_conversations" = "Todas las conversaciones"; +"all_dates" = "All dates"; "all_photos" = "Todas las fotos"; "all_signer_failure" = "Falla de todos los nodos, tal vez el PIN no coincida con lo que se configuró la última vez que finalizó inesperadamente"; "all_transactions" = "Todas las transacciones"; @@ -179,6 +181,8 @@ "chat_text_size" = "Tamaño del texto del chat"; "chat_waiting" = "Esperando a que %1$@ se conecte y establezca una sesión cifrada. %2$@."; "chats" = "CHATS"; +"chats_count_one" = "1 chat"; +"chats_count" = "%d chats"; "choose_network" = "Elige Red"; "choose_network_tip" = "Asegúrate de que la red que elijas para depositar sea compatible con Mixin. De lo contrario, tus bienes pueden perderse."; "choose_photo" = "Escoge una foto"; @@ -299,6 +303,8 @@ "deposit_tip_eos" = "Esta dirección es compatible con todos los tokens basados en EOS."; "deposit_tip_eth" = "Esta dirección admite todos los tokens ERC-20, como ETH, XIN, etc."; "deposit_tip_trx" = "Esta dirección admite todos los tokens TRC-10 y TRC-20."; +"deselect_all" = "Deselect All"; +"designated_time_frame" = "Designated time frame"; "desktop_on_hint" = "Tienes tu escritorio conectado"; "desktop_upgrade" = "Actualiza Mixin Messenger Desktop a la última versión."; "detect_qr_tip" = "Se ha detectado un código QR de Mixin, toca para reconocer"; @@ -454,6 +460,8 @@ "invite_to_group_via_link" = "Invitar al grupo a través de un enlace"; "invited_by_stranger" = "El anfitrión no está en tus contactos."; "invites_you_to_a_voice_call" = "te invita a una llamada de voz"; +"items_selected_one" = "1 Item Selected"; +"items_selected_count" = "%d Items Selected"; "join_group" = "Únete al grupo"; "joined_in" = "Se ha unido en %@"; "keep_running_foreground" = "Please do not close the screen and keep Mixin running in the foreground"; @@ -465,6 +473,10 @@ "large_amount_confirmation_with_symbol" = "Confirmación de gran cantidad(%@)"; "last_active_time" = "Último Activo %@"; "last_backup_hint" = "Última copia de seguridad en %@, tamaño total %@."; +"last_month" = "Last month"; +"last_month_count" = "Last %d months"; +"last_year" = "Last year"; +"last_year_count" = "Last %d years"; "later" = "Más tarde"; "learn_more" = "Obtener más información"; "light" = "Claro"; @@ -512,6 +524,7 @@ "mixin_messenger_desktop" = "Mixin Messenger de Escritorio"; "mixin_server_encounters_errors" = "El servidor Mixin encuentra errores"; "mobile_contacts" = "Contactos móviles"; +"month" = "Month"; "monthly" = "Mensual"; "more" = "Más"; "move_and_scale" = "Mover y escalar"; @@ -675,6 +688,7 @@ "receive_money" = "Recibir dinero"; "receiver" = "Receptor"; "receivers" = "Receptores"; +"recent" = "Recent"; "recent_chats" = "CHATS"; "recent_searches" = "Búsquedas recientes"; "refresh" = "Actualizar"; @@ -749,6 +763,7 @@ "security" = "Seguridad"; "select" = "Seleccionar"; "select_a_country_or_region" = "Seleccionar un país o región"; +"select_all" = "Select All"; "select_emergency_contact" = "Seleccionar contacto de emergencia"; "select_more_photos" = "Seleccionar más fotos"; "selected_count" = "%@ Seleccionado"; @@ -823,6 +838,7 @@ "show" = "Espectáculo"; "show_asset" = "Mostrar activo"; "show_in_chat" = "Mostrar en el chat"; +"show_selected" = "Show Selected(%d)"; "sign_in" = "Iniciar sesión"; "sign_with_emergency_contact" = "Iniciar sesión con contacto de emergencia"; "sign_with_phone_number" = "Iniciar sesión con número de teléfono"; @@ -1007,6 +1023,7 @@ "withdrawal_network_fee" = "Tarifa de red: "; "withdrawal_no_memo" = "Sin memorando"; "withdrawal_to" = "Retirar a %@"; +"year" = "Year"; "you" = "Tú"; "you_deleted_this_message" = "Has borrado este mensaje"; "you_have_a_new_message" = "Tienes un nuevo mensaje"; diff --git a/Mixin/Resources/ja.lproj/Localizable.strings b/Mixin/Resources/ja.lproj/Localizable.strings index cd48d98549..1fad77e7b2 100644 --- a/Mixin/Resources/ja.lproj/Localizable.strings +++ b/Mixin/Resources/ja.lproj/Localizable.strings @@ -55,7 +55,9 @@ "alert_key_group_transcript_message" = "%@がメッセージ履歴を共有しました"; "alert_key_group_video_message" = "%@が動画を送信しました"; "all" = "すべて"; +"all_chats" = "All chats"; "all_conversations" = "すべてのチャットルーム"; +"all_dates" = "All dates"; "all_photos" = "全ての画像"; "all_signer_failure" = "All node failure, perhaps PIN does not match what was set when it was last terminated unexpectedly"; "all_transactions" = "もらった・あげたコイン💰"; @@ -179,6 +181,8 @@ "chat_text_size" = "Chat Text Size"; "chat_waiting" = "オンラインで暗号化されたやりとりを開始するまであと%1$@。%2$@"; "chats" = "チャット"; +"chats_count_one" = "1 chat"; +"chats_count" = "%d chats"; "choose_network" = "Choose Network"; "choose_network_tip" = "Please ensure the network you choose to deposit that Mixin supports. Otherwise, your assets may be lost."; "choose_photo" = "画像を選ぶ"; @@ -299,6 +303,8 @@ "deposit_tip_eos" = "このアドレスはEOSベースの全てのトークンをサポートしています"; "deposit_tip_eth" = "このアドレスはETHやXINなど全てのERC-20トークンをサポートしています"; "deposit_tip_trx" = "このアドレスは全てのTRC-10/TRC-20トークンをサポートしています"; +"deselect_all" = "Deselect All"; +"designated_time_frame" = "Designated time frame"; "desktop_on_hint" = "デスクトップにログインしています"; "desktop_upgrade" = "デスクトップ版Mixinを最新バージョンにアップデートしてください"; "detect_qr_tip" = "Mixin QRコードを検出しました、タップしてアクセスします"; @@ -454,6 +460,8 @@ "invite_to_group_via_link" = "リンクを使って招待する"; "invited_by_stranger" = "招待者はあなたの連絡先に存在しません"; "invites_you_to_a_voice_call" = "グループ通話に招待されました"; +"items_selected_one" = "1 Item Selected"; +"items_selected_count" = "%d Items Selected"; "join_group" = "グループに参加"; "joined_in" = "%@からMixinを利用しています"; "keep_running_foreground" = "Please do not close the screen and keep Mixin running in the foreground"; @@ -465,6 +473,10 @@ "large_amount_confirmation_with_symbol" = "送金時に通知を行う金額の設定(%@)"; "last_active_time" = "直近のアクティビティ%@"; "last_backup_hint" = "最後に行ったバックアップ %@, 合計サイズ %@."; +"last_month" = "Last month"; +"last_month_count" = "Last %d months"; +"last_year" = "Last year"; +"last_year_count" = "Last %d years"; "later" = "後で"; "learn_more" = "こちら"; "light" = "ダーク"; @@ -512,6 +524,7 @@ "mixin_messenger_desktop" = "Mixin デスクトップ"; "mixin_server_encounters_errors" = "Mixinのサーバーにエラーが発生しています"; "mobile_contacts" = "連絡先"; +"month" = "Month"; "monthly" = "月"; "more" = "もっとみる"; "move_and_scale" = "移動と拡大縮小"; @@ -675,6 +688,7 @@ "receive_money" = "仮想通貨を受け取る"; "receiver" = "受取人"; "receivers" = "受取人"; +"recent" = "Recent"; "recent_chats" = "チャット"; "recent_searches" = "最近の検索"; "refresh" = "更新"; @@ -749,6 +763,7 @@ "security" = "セキュリティ"; "select" = "選択"; "select_a_country_or_region" = "国と地域を選択"; +"select_all" = "Select All"; "select_emergency_contact" = "緊急連絡先を選択"; "select_more_photos" = "さらに選択"; "selected_count" = "%@を選択しています。"; @@ -823,6 +838,7 @@ "show" = "表示"; "show_asset" = "資産を表示する"; "show_in_chat" = "チャット内で表示"; +"show_selected" = "Show Selected(%d)"; "sign_in" = "ログイン"; "sign_with_emergency_contact" = "緊急連絡先でログイン"; "sign_with_phone_number" = "電話番号でログイン"; @@ -1007,6 +1023,7 @@ "withdrawal_network_fee" = "ネットワーク手数料:"; "withdrawal_no_memo" = "メモなし"; "withdrawal_to" = "%@へ出金"; +"year" = "Year"; "you" = "自分"; "you_deleted_this_message" = "このメッセージを削除しました。"; "you_have_a_new_message" = "新しいメッセージがあります"; diff --git a/Mixin/Resources/ru.lproj/Localizable.strings b/Mixin/Resources/ru.lproj/Localizable.strings index 8e718abc42..a38984e10f 100644 --- a/Mixin/Resources/ru.lproj/Localizable.strings +++ b/Mixin/Resources/ru.lproj/Localizable.strings @@ -55,7 +55,9 @@ "alert_key_group_transcript_message" = "%@ отправил стенограмму"; "alert_key_group_video_message" = "%@ отправил видео"; "all" = "Все"; +"all_chats" = "All chats"; "all_conversations" = "Все разговоры"; +"all_dates" = "All dates"; "all_photos" = "Все фотографии"; "all_signer_failure" = "All node failure, perhaps PIN does not match what was set when it was last terminated unexpectedly"; "all_transactions" = "Все транзакции"; @@ -179,6 +181,8 @@ "chat_text_size" = "Chat Text Size"; "chat_waiting" = "Ожидание, пока %1$@ подключится к сети и установит зашифрованный сеанс. %2$@."; "chats" = "ЧАТЫ"; +"chats_count_one" = "1 chat"; +"chats_count" = "%d chats"; "choose_network" = "Choose Network"; "choose_network_tip" = "Please ensure the network you choose to deposit that Mixin supports. Otherwise, your assets may be lost."; "choose_photo" = "Выбрать фото"; @@ -299,6 +303,8 @@ "deposit_tip_eos" = "Этот адрес поддерживает всю базу токенов EOS."; "deposit_tip_eth" = "Этот адрес поддерживает все токены ERC-20, такие как ETH, XIN и т. д."; "deposit_tip_trx" = "Этот адрес поддерживает все токены TRC-10 и TRC-20."; +"deselect_all" = "Deselect All"; +"designated_time_frame" = "Designated time frame"; "desktop_on_hint" = "Вы вошли в свой рабочий стол"; "desktop_upgrade" = "Пожалуйста, обновите Mixin Messenger Desktop до последней версии."; "detect_qr_tip" = "Обнаружен QR-код Mixin, нажмите, чтобы распознать"; @@ -454,6 +460,8 @@ "invite_to_group_via_link" = "Пригласить в группу по ссылке"; "invited_by_stranger" = "Приглашающего нет в ваших контактах"; "invites_you_to_a_voice_call" = "приглашает вас на голосовой вызов"; +"items_selected_one" = "1 Item Selected"; +"items_selected_count" = "%d Items Selected"; "join_group" = "Присоединиться к группе"; "joined_in" = "Присоединился к %@"; "keep_running_foreground" = "Please do not close the screen and keep Mixin running in the foreground"; @@ -465,6 +473,10 @@ "large_amount_confirmation_with_symbol" = "Подтверждение крупной суммы(%@)"; "last_active_time" = "Последнее посещение %@"; "last_backup_hint" = "Последняя резервная копия %@, общий размер %@."; +"last_month" = "Last month"; +"last_month_count" = "Last %d months"; +"last_year" = "Last year"; +"last_year_count" = "Last %d years"; "later" = "Потом"; "learn_more" = "Узнать больше"; "light" = "Светлое"; @@ -512,6 +524,7 @@ "mixin_messenger_desktop" = "Рабочий стол Mixin Messenger"; "mixin_server_encounters_errors" = "Сервер Mixin обнаруживает ошибки"; "mobile_contacts" = "Мобильные контакты"; +"month" = "Month"; "monthly" = "Ежемесячно"; "more" = "Больше"; "move_and_scale" = "Cдвиг и масштаб"; @@ -675,6 +688,7 @@ "receive_money" = "Получить деньги"; "receiver" = "Получатель"; "receivers" = "Получатели"; +"recent" = "Recent"; "recent_chats" = "ЧАТЫ"; "recent_searches" = "Недавние поиски"; "refresh" = "Обновить"; @@ -749,6 +763,7 @@ "security" = "Безопасность"; "select" = "Выбрать"; "select_a_country_or_region" = "Выберите страну или регион"; +"select_all" = "Select All"; "select_emergency_contact" = "Выберите экстренный контакт"; "select_more_photos" = "Выберите больше фотографий"; "selected_count" = "%@ Выбрано"; @@ -823,6 +838,7 @@ "show" = "Показать"; "show_asset" = "Показать актив"; "show_in_chat" = "Показать в чате"; +"show_selected" = "Show Selected(%d)"; "sign_in" = "Войти"; "sign_with_emergency_contact" = "Войти через контакт для экстренных случаев"; "sign_with_phone_number" = "Войти через номер телефона"; @@ -1007,6 +1023,7 @@ "withdrawal_network_fee" = "Комиссия за сеть: "; "withdrawal_no_memo" = "Нет памятки"; "withdrawal_to" = "Вывод на %@"; +"year" = "Year"; "you" = "Вы"; "you_deleted_this_message" = "Вы удалили это сообщение"; "you_have_a_new_message" = "У вас новое сообщение"; diff --git a/Mixin/Resources/zh-Hans.lproj/Localizable.strings b/Mixin/Resources/zh-Hans.lproj/Localizable.strings index bb95de0fd3..760a8d6b87 100644 --- a/Mixin/Resources/zh-Hans.lproj/Localizable.strings +++ b/Mixin/Resources/zh-Hans.lproj/Localizable.strings @@ -55,7 +55,9 @@ "alert_key_group_transcript_message" = "%@分享一个聊天记录"; "alert_key_group_video_message" = "%@发送一个视频"; "all" = "全部"; +"all_chats" = "所有聊天"; "all_conversations" = "所有会话"; +"all_dates" = "所有日期"; "all_photos" = "所有照片"; "all_signer_failure" = "所有节点失败,也许 PIN 跟上次意外退出时设置的不一致"; "all_transactions" = "所有交易记录"; @@ -179,6 +181,8 @@ "chat_text_size" = "聊天字体大小"; "chat_waiting" = "等待%1$@上线后建立加密会话。%2$@。"; "chats" = "会话"; +"chats_count_one" = "1 个聊天"; +"chats_count" = "%d 个聊天"; "choose_network" = "选择充值网络"; "choose_network_tip" = "请选择 Mixin 支持的网络进行充值,否则您的充值将不会到账。"; "choose_photo" = "选择照片"; @@ -299,6 +303,8 @@ "deposit_tip_eos" = "支持所有基于 EOS 发行的代币。"; "deposit_tip_eth" = "支持所有符合 ERC-20 标准的代币,例如 ETH、XIN 等。"; "deposit_tip_trx" = "支持 TRX 和所有符合 TRC-10、TRC-20 标准的代币。"; +"deselect_all" = "取消所有选择"; +"designated_time_frame" = "指定时间段"; "desktop_on_hint" = "桌面版已登入。"; "desktop_upgrade" = "请升级 Mixin Messenger 桌面端至最新版!"; "detect_qr_tip" = "检测到一个 Mixin 二维码,点击识别"; @@ -454,6 +460,8 @@ "invite_to_group_via_link" = "群邀请链接"; "invited_by_stranger" = "邀请人不是你的联系人"; "invites_you_to_a_voice_call" = "发起了语音通话"; +"items_selected_one" = "已选择 1 个"; +"items_selected_count" = "已选择 %d 个"; "join_group" = "加入群组"; "joined_in" = "%@ 加入"; "keep_running_foreground" = "不要关闭屏幕并保持 Mixin 在前台运行"; @@ -465,6 +473,10 @@ "large_amount_confirmation_with_symbol" = "大额转账确认(%@)"; "last_active_time" = "最后登入于 %@"; "last_backup_hint" = "上次备份是 %@,占用空间 %@。"; +"last_month" = "最近一个月"; +"last_month_count" = "最近 %d 个月"; +"last_year" = "最近一年"; +"last_year_count" = "最近 %d 年"; "later" = "稍后"; "learn_more" = "了解更多"; "light" = "浅色"; @@ -512,6 +524,7 @@ "mixin_messenger_desktop" = "Mixin Messenger 桌面"; "mixin_server_encounters_errors" = "服务器出错,请稍后重试"; "mobile_contacts" = "通讯录"; +"month" = "月"; "monthly" = "每月"; "more" = "更多"; "move_and_scale" = "移动和缩放"; @@ -675,6 +688,7 @@ "receive_money" = "我的收款码"; "receiver" = "至"; "receivers" = "交易接收人"; +"recent" = "最近"; "recent_chats" = "最近聊天"; "recent_searches" = "最近搜索"; "refresh" = "刷新"; @@ -749,6 +763,7 @@ "security" = "安全"; "select" = "选择"; "select_a_country_or_region" = "选择一个国家或地区"; +"select_all" = "选择所有"; "select_emergency_contact" = "选择紧急联系人"; "select_more_photos" = "选择更多照片"; "selected_count" = "选择了 %@ 个消息"; @@ -823,6 +838,7 @@ "show" = "显示"; "show_asset" = "显示资产"; "show_in_chat" = "在聊天中展示"; +"show_selected" = "显示选择(%d)"; "sign_in" = "登录"; "sign_with_emergency_contact" = "通过紧急联系人登录"; "sign_with_phone_number" = "通过手机号登录"; @@ -1007,6 +1023,7 @@ "withdrawal_network_fee" = "网络手续费:"; "withdrawal_no_memo" = "点击不使用 Memo(备注)"; "withdrawal_to" = "提现到 %@"; +"year" = "年"; "you" = "你"; "you_deleted_this_message" = "你撤回了一条消息"; "you_have_a_new_message" = "你收到了一条消息"; diff --git a/Mixin/Resources/zh-Hant.lproj/Localizable.strings b/Mixin/Resources/zh-Hant.lproj/Localizable.strings index f7f9d2463b..42293cbbd7 100644 --- a/Mixin/Resources/zh-Hant.lproj/Localizable.strings +++ b/Mixin/Resources/zh-Hant.lproj/Localizable.strings @@ -56,7 +56,9 @@ "alert_key_group_video_message" = "%@傳送一個影片"; "all" = "全部"; "all_conversations" = "所有會話"; +"all_chats" = "所有聊天"; "all_photos" = "所有照片"; +"all_dates" = "所有日期"; "all_signer_failure" = "所有節點失敗,也許 PIN 跟上次意外退出時設定的不一致"; "all_transactions" = "所有交易記錄"; "allow" = "允許"; @@ -179,6 +181,8 @@ "chat_text_size" = "聊天字型大小"; "chat_waiting" = "等待%1$@上線後建立加密會話。%2$@。"; "chats" = "會話"; +"chats_count_one" = "1 個聊天"; +"chats_count" = "%d 個聊天"; "choose_network" = "選擇充值網路"; "choose_network_tip" = "請選擇 Mixin 支援的網路進行充值,否則您的充值將不會到賬。"; "choose_photo" = "選擇照片"; @@ -299,6 +303,8 @@ "deposit_tip_eos" = "支援所有基於 EOS 發行的代幣。"; "deposit_tip_eth" = "支援所有符合 ERC-20 標準的代幣,例如 ETH、XIN 等。"; "deposit_tip_trx" = "支援 TRX 和所有符合 TRC-10、TRC-20 標準的代幣。"; +"deselect_all" = "取消所有選擇"; +"designated_time_frame" = "指定時間段"; "desktop_on_hint" = "桌面版已登入。"; "desktop_upgrade" = "請升級 Mixin Messenger 桌面端至最新版!"; "detect_qr_tip" = "檢測到一個 Mixin 二維碼,點選識別"; @@ -454,6 +460,8 @@ "invite_to_group_via_link" = "群邀請連結"; "invited_by_stranger" = "邀請人不是你的聯絡人"; "invites_you_to_a_voice_call" = "發起了語音通話"; +"items_selected_one" = "已選擇 1 個"; +"items_selected_count" = "已選擇 %d 個"; "join_group" = "加入群組"; "joined_in" = "%@ 加入"; "keep_running_foreground" = "不要關閉螢幕並保持 Mixin 在前臺執行"; @@ -465,6 +473,10 @@ "large_amount_confirmation_with_symbol" = "大額轉賬確認(%@)"; "last_active_time" = "最後登入於 %@"; "last_backup_hint" = "上次備份是 %@,佔用空間 %@。"; +"last_month" = "最近一個月"; +"last_month_count" = "最近 %d 個月"; +"last_year" = "最近一年"; +"last_year_count" = "最近 %d 年"; "later" = "稍後"; "learn_more" = "瞭解更多"; "light" = "淺色"; @@ -512,6 +524,7 @@ "mixin_messenger_desktop" = "Mixin Messenger 桌面"; "mixin_server_encounters_errors" = "伺服器出錯,請稍後重試"; "mobile_contacts" = "通訊錄"; +"month" = "月"; "monthly" = "每月"; "more" = "更多"; "move_and_scale" = "移動和縮放"; @@ -675,6 +688,7 @@ "receive_money" = "我的收款碼"; "receiver" = "至"; "receivers" = "交易接收人"; +"recent" = "最近"; "recent_chats" = "最近聊天"; "recent_searches" = "最近搜尋"; "refresh" = "重新整理"; @@ -749,6 +763,7 @@ "security" = "安全"; "select" = "選擇"; "select_a_country_or_region" = "選擇一個國家或地區"; +"select_all" = "選擇所有"; "select_emergency_contact" = "選擇緊急聯絡人"; "select_more_photos" = "選擇更多照片"; "selected_count" = "選擇了 %@ 個訊息"; @@ -823,6 +838,7 @@ "show" = "顯示"; "show_asset" = "顯示資產"; "show_in_chat" = "在聊天中展示"; +"show_selected" = "顯示選擇(%d)"; "sign_in" = "登入"; "sign_with_emergency_contact" = "透過緊急聯絡人登入"; "sign_with_phone_number" = "透過手機號登入"; @@ -1007,6 +1023,7 @@ "withdrawal_network_fee" = "網路手續費:"; "withdrawal_no_memo" = "點選不使用 Memo(備註)"; "withdrawal_to" = "提現到 %@"; +"year" = "年"; "you" = "你"; "you_deleted_this_message" = "你撤回了一條訊息"; "you_have_a_new_message" = "你收到了一條訊息"; From bec310529158bcbbe5c227e456ffa2797c321105 Mon Sep 17 00:00:00 2001 From: fanyu Date: Fri, 9 Jun 2023 10:52:16 +0800 Subject: [PATCH 02/28] Add data range selection interface --- Mixin.xcodeproj/project.pbxproj | 24 ++ .../Controllers/Common/Views/PeerView.xib | 70 +++++- ...rConversationSelectionViewController.swift | 212 ++++++++++++++++++ ...eTransferDateSelectionViewController.swift | 109 +++++++++ .../DeviceTransfer/DeviceTransferRange.swift | 51 +++++ .../TransferToDesktopViewController.swift | 139 ++++++++---- .../TransferToPhoneViewController.swift | 49 +++- .../View/DeviceTransferDateSelectionView.xib | 188 ++++++++++++++++ ...ceTransferSelectedConversationWindow.swift | 83 +++++++ ...viceTransferSelectedConversationWindow.xib | 88 ++++++++ 10 files changed, 951 insertions(+), 62 deletions(-) create mode 100644 Mixin/UserInterface/Controllers/DeviceTransfer/DeviceTransferConversationSelectionViewController.swift create mode 100644 Mixin/UserInterface/Controllers/DeviceTransfer/DeviceTransferDateSelectionViewController.swift create mode 100644 Mixin/UserInterface/Controllers/DeviceTransfer/DeviceTransferRange.swift create mode 100644 Mixin/UserInterface/Controllers/DeviceTransfer/View/DeviceTransferDateSelectionView.xib create mode 100644 Mixin/UserInterface/Windows/DeviceTransferSelectedConversationWindow.swift create mode 100644 Mixin/UserInterface/Windows/DeviceTransferSelectedConversationWindow.xib diff --git a/Mixin.xcodeproj/project.pbxproj b/Mixin.xcodeproj/project.pbxproj index 14b76c8486..d83302ce37 100644 --- a/Mixin.xcodeproj/project.pbxproj +++ b/Mixin.xcodeproj/project.pbxproj @@ -623,6 +623,9 @@ 7C7579DF29DC61890002DA0B /* TransferToPhoneQRCodeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C7579DD29DC61890002DA0B /* TransferToPhoneQRCodeViewController.swift */; }; 7C7579E029DC61890002DA0B /* TransferToPhoneQRCodeView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 7C7579DE29DC61890002DA0B /* TransferToPhoneQRCodeView.xib */; }; 7C7635B826A13461006101DB /* HomeAppsConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C7635B726A13461006101DB /* HomeAppsConstants.swift */; }; + 7C7DBAD62A2F3464008D4B0E /* DeviceTransferDateSelectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C7DBAD52A2F3464008D4B0E /* DeviceTransferDateSelectionViewController.swift */; }; + 7C7DBAD82A2F34CC008D4B0E /* DeviceTransferConversationSelectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C7DBAD72A2F34CC008D4B0E /* DeviceTransferConversationSelectionViewController.swift */; }; + 7C7DBADA2A2F391C008D4B0E /* DeviceTransferRange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C7DBAD92A2F391C008D4B0E /* DeviceTransferRange.swift */; }; 7C8CC5A4280D347A00F7CBDF /* PreviewWallpaperViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C8CC5A2280D347A00F7CBDF /* PreviewWallpaperViewController.swift */; }; 7C8CC5A7280D40E900F7CBDF /* PreviewWallpaperCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C8CC5A6280D40E900F7CBDF /* PreviewWallpaperCell.swift */; }; 7C8FA78D27687D1500855AFD /* DeleteAccountSettingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C8FA78C27687D1500855AFD /* DeleteAccountSettingViewController.swift */; }; @@ -658,11 +661,14 @@ 7CDBA58E28F7B6CB00AC3777 /* TransferSearchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CDBA58D28F7B6CB00AC3777 /* TransferSearchViewController.swift */; }; 7CDF316C29890FB200421808 /* ConversationFontSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CDF316B29890FB200421808 /* ConversationFontSet.swift */; }; 7CDF316E29891B1200421808 /* PresentationFontSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CDF316D29891B1200421808 /* PresentationFontSize.swift */; }; + 7CDFFF6F2A30760500E0870E /* DeviceTransferSelectedConversationWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CDFFF6E2A30760500E0870E /* DeviceTransferSelectedConversationWindow.swift */; }; + 7CDFFF712A30761900E0870E /* DeviceTransferSelectedConversationWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 7CDFFF702A30761900E0870E /* DeviceTransferSelectedConversationWindow.xib */; }; 7CE2DC9A28587DE100AF00AE /* Colors.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7B8BB58F234F36C000991ACB /* Colors.xcassets */; }; 7CE2DE102858B52000AF00AE /* WallpaperImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CE2DE0F2858B52000AF00AE /* WallpaperImageView.swift */; }; 7CE3A25C2771A8AB006BE765 /* DeleteAccountVerifyCodeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CE3A25B2771A8AB006BE765 /* DeleteAccountVerifyCodeViewController.swift */; }; 7CE5E7A8269BDA29000B7904 /* HomeAppsPinTipsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CE5E7A6269BDA29000B7904 /* HomeAppsPinTipsViewController.swift */; }; 7CE5E7A9269BDA29000B7904 /* HomeAppsPinTipsView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 7CE5E7A7269BDA29000B7904 /* HomeAppsPinTipsView.xib */; }; + 7CE78FB12A30883200FEB942 /* DeviceTransferDateSelectionView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 7CE78FB02A30883200FEB942 /* DeviceTransferDateSelectionView.xib */; }; 7CEB735429DB24F3006FB5B2 /* RestoreChatViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CEB735229DB24F3006FB5B2 /* RestoreChatViewController.swift */; }; 7CEB735529DB24F3006FB5B2 /* RestoreChatView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 7CEB735329DB24F3006FB5B2 /* RestoreChatView.xib */; }; 7CEB735829DB272F006FB5B2 /* RestoreChatTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CEB735629DB272F006FB5B2 /* RestoreChatTableViewCell.swift */; }; @@ -1696,6 +1702,9 @@ 7C7579DD29DC61890002DA0B /* TransferToPhoneQRCodeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransferToPhoneQRCodeViewController.swift; sourceTree = ""; }; 7C7579DE29DC61890002DA0B /* TransferToPhoneQRCodeView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TransferToPhoneQRCodeView.xib; sourceTree = ""; }; 7C7635B726A13461006101DB /* HomeAppsConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeAppsConstants.swift; sourceTree = ""; }; + 7C7DBAD52A2F3464008D4B0E /* DeviceTransferDateSelectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceTransferDateSelectionViewController.swift; sourceTree = ""; }; + 7C7DBAD72A2F34CC008D4B0E /* DeviceTransferConversationSelectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceTransferConversationSelectionViewController.swift; sourceTree = ""; }; + 7C7DBAD92A2F391C008D4B0E /* DeviceTransferRange.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceTransferRange.swift; sourceTree = ""; }; 7C8CC5A2280D347A00F7CBDF /* PreviewWallpaperViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewWallpaperViewController.swift; sourceTree = ""; }; 7C8CC5A6280D40E900F7CBDF /* PreviewWallpaperCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewWallpaperCell.swift; sourceTree = ""; }; 7C8FA78C27687D1500855AFD /* DeleteAccountSettingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeleteAccountSettingViewController.swift; sourceTree = ""; }; @@ -1732,10 +1741,13 @@ 7CDBA58D28F7B6CB00AC3777 /* TransferSearchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransferSearchViewController.swift; sourceTree = ""; }; 7CDF316B29890FB200421808 /* ConversationFontSet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationFontSet.swift; sourceTree = ""; }; 7CDF316D29891B1200421808 /* PresentationFontSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PresentationFontSize.swift; sourceTree = ""; }; + 7CDFFF6E2A30760500E0870E /* DeviceTransferSelectedConversationWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceTransferSelectedConversationWindow.swift; sourceTree = ""; }; + 7CDFFF702A30761900E0870E /* DeviceTransferSelectedConversationWindow.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = DeviceTransferSelectedConversationWindow.xib; sourceTree = ""; }; 7CE2DE0F2858B52000AF00AE /* WallpaperImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WallpaperImageView.swift; sourceTree = ""; }; 7CE3A25B2771A8AB006BE765 /* DeleteAccountVerifyCodeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeleteAccountVerifyCodeViewController.swift; sourceTree = ""; }; 7CE5E7A6269BDA29000B7904 /* HomeAppsPinTipsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeAppsPinTipsViewController.swift; sourceTree = ""; }; 7CE5E7A7269BDA29000B7904 /* HomeAppsPinTipsView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = HomeAppsPinTipsView.xib; sourceTree = ""; }; + 7CE78FB02A30883200FEB942 /* DeviceTransferDateSelectionView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = DeviceTransferDateSelectionView.xib; sourceTree = ""; }; 7CEB735229DB24F3006FB5B2 /* RestoreChatViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RestoreChatViewController.swift; sourceTree = ""; }; 7CEB735329DB24F3006FB5B2 /* RestoreChatView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = RestoreChatView.xib; sourceTree = ""; }; 7CEB735629DB272F006FB5B2 /* RestoreChatTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RestoreChatTableViewCell.swift; sourceTree = ""; }; @@ -2669,6 +2681,7 @@ 7CEB735329DB24F3006FB5B2 /* RestoreChatView.xib */, 7CEB735629DB272F006FB5B2 /* RestoreChatTableViewCell.swift */, 7CEB735729DB272F006FB5B2 /* RestoreChatTableViewCell.xib */, + 7CE78FB02A30883200FEB942 /* DeviceTransferDateSelectionView.xib */, ); path = View; sourceTree = ""; @@ -2786,6 +2799,9 @@ 7C7579DD29DC61890002DA0B /* TransferToPhoneQRCodeViewController.swift */, 7C7579DB29DC60960002DA0B /* TransferToPhoneViewController.swift */, 7C7579D529DC573F0002DA0B /* DeviceTransferProgressViewController.swift */, + 7C7DBAD72A2F34CC008D4B0E /* DeviceTransferConversationSelectionViewController.swift */, + 7C7DBAD52A2F3464008D4B0E /* DeviceTransferDateSelectionViewController.swift */, + 7C7DBAD92A2F391C008D4B0E /* DeviceTransferRange.swift */, ); path = DeviceTransfer; sourceTree = ""; @@ -3173,6 +3189,8 @@ 7C0FAAC827E07A0A008D4021 /* ExpiredMessageTimePickerWindow.xib */, 7C47352828571CC900ECD293 /* AccessPhoneContactHintWindow.swift */, 7C47352A28571D0300ECD293 /* AccessPhoneContactHintWindow.xib */, + 7CDFFF6E2A30760500E0870E /* DeviceTransferSelectedConversationWindow.swift */, + 7CDFFF702A30761900E0870E /* DeviceTransferSelectedConversationWindow.xib */, ); path = Windows; sourceTree = ""; @@ -4177,6 +4195,7 @@ 7C7579E029DC61890002DA0B /* TransferToPhoneQRCodeView.xib in Resources */, 94E8913925C019F000F1E5D4 /* Pods-Mixin-acknowledgements.plist in Resources */, 7BD3880024DC66F900A3035C /* Localizable.strings in Resources */, + 7CDFFF712A30761900E0870E /* DeviceTransferSelectedConversationWindow.xib in Resources */, 94B7B6DC26B43562000B0AC5 /* SilentNotificationMessagePreviewView.xib in Resources */, 7C5DFE32284F3071008733FC /* UserCenterTableHeaderView.xib in Resources */, 7C8FA78F2768822800855AFD /* DeleteAccountTableHeaderView.xib in Resources */, @@ -4297,6 +4316,7 @@ 948E6CB628AF95CC00DFF5DF /* TIPIntroView.xib in Resources */, 9BB351691FB1A94100EDDD2C /* ConversationDateHeaderView.xib in Resources */, 7B84358B20D8F88200237242 /* QuotePreviewView.xib in Resources */, + 7CE78FB12A30883200FEB942 /* DeviceTransferDateSelectionView.xib in Resources */, 7B51DDAE223A3818008ACDBB /* MobileNumberView.xib in Resources */, 7C53049B28FE753400567CF6 /* AuthorizationScopeGroupCell.xib in Resources */, E05F1B9E23BB546F00A2569D /* AppReceptionView.xib in Resources */, @@ -5105,6 +5125,7 @@ 7B0BE46421A59AE400B91A1E /* RefreshTopAssetsJob.swift in Sources */, 7BC1466F230D12B70060FE19 /* CurrencyCell.swift in Sources */, 7CB0955B26CF4DDC0049F4C7 /* PinMessageCell.swift in Sources */, + 7C7DBAD82A2F34CC008D4B0E /* DeviceTransferConversationSelectionViewController.swift in Sources */, 7B1D61132512773F00BD6B15 /* ExternalSharingConfirmationViewController.swift in Sources */, 7B9D825422F1BFC40099381E /* LargeModernNetworkOperationButton.swift in Sources */, 941CAE00276A3F17008F42D6 /* WebPImageDecoderInternal.swift in Sources */, @@ -5139,6 +5160,7 @@ 7B57186E24D9375300682D86 /* MixinAPIError+Description.swift in Sources */, E0320FA523CC3D1600A651D4 /* MessageTagLabel.swift in Sources */, 7B333A8623406AD000FDA848 /* GalleryTransitionFromMessageCellView.swift in Sources */, + 7C7DBADA2A2F391C008D4B0E /* DeviceTransferRange.swift in Sources */, 7B54F95922B243EA00908A9D /* EmergencyContactVerifyPinViewController.swift in Sources */, 7B68F78B2191741300B79978 /* BiometryType.swift in Sources */, 7BD0532820A41F6A00C36F69 /* RangeExtension.swift in Sources */, @@ -5354,6 +5376,7 @@ 7B5BABE31FB57B0300341FE6 /* ImagePickerController.swift in Sources */, 7B1ABAE021186BF2009FAA6C /* StickerInputViewController.swift in Sources */, 7CEB736029DBC639006FB5B2 /* DeviceTransferUser.swift in Sources */, + 7C7DBAD62A2F3464008D4B0E /* DeviceTransferDateSelectionViewController.swift in Sources */, 7BA9D9EE226F114700255943 /* HomeSearchViewController.swift in Sources */, DFAD89A1241B6D6400836EDD /* JobService.swift in Sources */, 94E1D44129E906AB00511267 /* DeviceTransferHeader.swift in Sources */, @@ -5362,6 +5385,7 @@ 7BA1768E244ACE2E007D50FD /* PickerCell.swift in Sources */, 7B81BF2522893F5D00266A77 /* GroupParticipantsViewController.swift in Sources */, 7B2C8B2D2524D26900347AC3 /* ClipSwitcherViewController.swift in Sources */, + 7CDFFF6F2A30760500E0870E /* DeviceTransferSelectedConversationWindow.swift in Sources */, 7B34A19722C0B59200665F41 /* MixinNavigationAnimating.swift in Sources */, DFE1F9811FAB40A800537D43 /* CornerButton.swift in Sources */, 7C359DCE26A6C15A001D3AE4 /* StickerStorePreviewCell.swift in Sources */, diff --git a/Mixin/UserInterface/Controllers/Common/Views/PeerView.xib b/Mixin/UserInterface/Controllers/Common/Views/PeerView.xib index 087fa6ba9a..d868574dc7 100644 --- a/Mixin/UserInterface/Controllers/Common/Views/PeerView.xib +++ b/Mixin/UserInterface/Controllers/Common/Views/PeerView.xib @@ -1,9 +1,9 @@ - + - + @@ -11,11 +11,16 @@ + + + + + @@ -26,42 +31,93 @@ - + - + - + + + + + + + - + + + - + + + diff --git a/Mixin/UserInterface/Controllers/DeviceTransfer/DeviceTransferConversationSelectionViewController.swift b/Mixin/UserInterface/Controllers/DeviceTransfer/DeviceTransferConversationSelectionViewController.swift new file mode 100644 index 0000000000..79188ed76f --- /dev/null +++ b/Mixin/UserInterface/Controllers/DeviceTransfer/DeviceTransferConversationSelectionViewController.swift @@ -0,0 +1,212 @@ +import UIKit +import AVFoundation +import MixinServices + +class DeviceTransferConversationSelectionViewController: PeerViewController { + + @IBOutlet weak var actionView: UIView! + @IBOutlet weak var operationAllButton: UIButton! + @IBOutlet weak var showSelectedButton: UIButton! + + @IBOutlet weak var showActionViewConstraint: NSLayoutConstraint! + @IBOutlet weak var hideActionViewConstraint: NSLayoutConstraint! + + private var selections = [MessageReceiver]() { + didSet { + container?.rightButton.isEnabled = selections.count > 0 + let title = !models.isEmpty && models.count == selections.count + ? R.string.localizable.deselect_all() + : R.string.localizable.select_all() + operationAllButton.setTitle(title, for: .normal) + let color = selections.isEmpty ? R.color.text_accessory() : R.color.theme() + showSelectedButton.setTitleColor(color, for: .normal) + showSelectedButton.setTitle(R.string.localizable.show_selected(selections.count), for: .normal) + showSelectedButton.isEnabled = !selections.isEmpty + } + } + + private var range = DeviceTransferRange.Conversation.all + private var rangeChanged: ((DeviceTransferRange.Conversation) -> Void)? + + class func instance(range: DeviceTransferRange.Conversation, rangeChanged: @escaping ((DeviceTransferRange.Conversation) -> Void)) -> UIViewController { + let controller = DeviceTransferConversationSelectionViewController() + controller.range = range + controller.rangeChanged = rangeChanged + return ContainerViewController.instance(viewController: controller, title: R.string.localizable.conversations()) + } + + override func viewDidLoad() { + super.viewDidLoad() + actionView.isHidden = false + showActionViewConstraint.priority = .defaultHigh + hideActionViewConstraint.priority = .defaultLow + tableView.allowsMultipleSelection = true + } + + override func initData() { + initDataOperation.addExecutionBlock { [weak self] in + guard let self else { + return + } + let conversations = ConversationDAO.shared.conversationList() + .compactMap(MessageReceiver.init) + let selections: [MessageReceiver] + switch range { + case .all: + selections = conversations + case .designated(let conversationIDs): + selections = conversations.filter { conversationIDs.contains($0.conversationId) } + } + DispatchQueue.main.sync { + self.models = conversations + self.selections = selections + self.tableView.reloadData() + self.reloadTableViewSelections() + } + } + queue.addOperation(initDataOperation) + } + + override func search(keyword: String) { + queue.operations + .filter({ $0 != initDataOperation }) + .forEach({ $0.cancel() }) + let op = BlockOperation() + let receivers = self.models + op.addExecutionBlock { [unowned op, weak self] in + guard self != nil, !op.isCancelled else { + return + } + let uniqueReceivers = Set(receivers.compactMap({ $0 })) + let searchResults = uniqueReceivers + .filter { $0.matches(lowercasedKeyword: keyword) } + .map { MessageReceiverSearchResult(receiver: $0, keyword: keyword) } + DispatchQueue.main.sync { + guard let weakSelf = self, !op.isCancelled else { + return + } + weakSelf.searchingKeyword = keyword + weakSelf.searchResults = searchResults + weakSelf.tableView.reloadData() + weakSelf.reloadTableViewSelections() + } + } + queue.addOperation(op) + } + + override func configure(cell: CheckmarkPeerCell, at indexPath: IndexPath) { + if isSearching { + cell.render(result: searchResults[indexPath.row]) + } else { + cell.render(receiver: models[indexPath.row]) + } + } + + override func reloadTableViewSelections() { + super.reloadTableViewSelections() + if isSearching { + for (index, result) in searchResults.enumerated() { + guard selections.contains(result.receiver) else { + continue + } + let indexPath = IndexPath(row: index, section: 0) + tableView.selectRow(at: indexPath, animated: false, scrollPosition: .none) + } + } else { + updateSelectedRows() + } + } + + // MARK: - UITableViewDataSource + override func numberOfSections(in tableView: UITableView) -> Int { + 1 + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return isSearching ? searchResults.count : models.count + } + + // MARK: - UITableViewDelegate + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + selections.append(messageReceiver(at: indexPath)) + if !isSearching { + updateSelectedRows() + } + } + + override func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) { + let receiver = messageReceiver(at: indexPath) + if let index = selections.firstIndex(of: receiver) { + selections.remove(at: index) + } + if !isSearching { + if let row = models.firstIndex(where: { $0.conversationId == receiver.conversationId }) { + tableView.deselectRow(at: IndexPath(row: row, section: 0), animated: false) + } + } + } + + @IBAction func operationAllAction(_ sender: Any) { + if selections.count == models.count { + selections.removeAll() + for index in 0.. String? { + R.string.localizable.save() + } + + func barRightButtonTappedAction() { + if selections.count == models.count { + rangeChanged?(.all) + } else { + rangeChanged?(.designated(selections.map(\.conversationId))) + } + navigationController?.popViewController(animated: true) + } + +} + +extension DeviceTransferConversationSelectionViewController { + + private func messageReceiver(at indexPath: IndexPath) -> MessageReceiver { + if isSearching { + return searchResults[indexPath.row].receiver + } else { + return models[indexPath.row] + } + } + + private func updateSelectedRows() { + assert(!isSearching) + for (row, receiver) in models.enumerated() where selections.contains(receiver) { + tableView.selectRow(at: IndexPath(row: row, section: 0), animated: false, scrollPosition: .none) + } + } + +} diff --git a/Mixin/UserInterface/Controllers/DeviceTransfer/DeviceTransferDateSelectionViewController.swift b/Mixin/UserInterface/Controllers/DeviceTransfer/DeviceTransferDateSelectionViewController.swift new file mode 100644 index 0000000000..bc5f88c04b --- /dev/null +++ b/Mixin/UserInterface/Controllers/DeviceTransfer/DeviceTransferDateSelectionViewController.swift @@ -0,0 +1,109 @@ +import UIKit + +class DeviceTransferDateSelectionViewController: UIViewController { + + @IBOutlet weak var allDateCheckmark: UIImageView! + @IBOutlet weak var lastDateCheckmark: UIImageView! + @IBOutlet weak var textField: UITextField! + @IBOutlet weak var segmentedControl: UISegmentedControl! + + private var range = DeviceTransferRange.Date.all + private var rangeChanged: ((DeviceTransferRange.Date) -> Void)? + + convenience init() { + self.init(nib: R.nib.deviceTransferDateSelectionView) + } + + override func viewDidLoad() { + super.viewDidLoad() + segmentedControl.setTitle(R.string.localizable.month(), forSegmentAt: 0) + segmentedControl.setTitle(R.string.localizable.year(), forSegmentAt: 1) + switch range { + case .all: + updateAllDateSelection() + case .lastMonths(let months): + updateLastDateSelection() + textField.text = "\(months)" + segmentedControl.selectedSegmentIndex = 0 + case .lastYears(let years): + updateLastDateSelection() + textField.text = "\(years)" + segmentedControl.selectedSegmentIndex = 1 + } + } + + class func instance(range: DeviceTransferRange.Date, rangeChanged: @escaping ((DeviceTransferRange.Date) -> Void)) -> UIViewController { + let controller = DeviceTransferDateSelectionViewController() + controller.range = range + controller.rangeChanged = rangeChanged + return ContainerViewController.instance(viewController: controller, title: R.string.localizable.date()) + } + + @IBAction func selectAllDateAction(_ sender: Any) { + updateAllDateSelection() + } + + @IBAction func selectLastDateAction(_ sender: Any) { + updateLastDateSelection() + } + + @IBAction func dateUnitChangedAction(_ sender: Any) { + updateDateRange() + } + + @IBAction func textChangedAction(_ sender: Any) { + updateDateRange() + } + + @IBAction func editingBeginAction(_ sender: Any) { + updateLastDateSelection() + } + +} + +extension DeviceTransferDateSelectionViewController: ContainerViewControllerDelegate { + + func textBarRightButton() -> String? { + R.string.localizable.save() + } + + func barRightButtonTappedAction() { + rangeChanged?(range) + navigationController?.popViewController(animated: true) + } + +} + +extension DeviceTransferDateSelectionViewController { + + private func updateAllDateSelection() { + allDateCheckmark.isHidden = false + lastDateCheckmark.isHidden = true + segmentedControl.isEnabled = false + range = .all + container?.rightButton.isEnabled = true + textField.resignFirstResponder() + } + + private func updateLastDateSelection() { + allDateCheckmark.isHidden = true + lastDateCheckmark.isHidden = false + segmentedControl.isEnabled = true + textField.becomeFirstResponder() + updateDateRange() + } + + private func updateDateRange() { + guard let count = textField.text?.intValue, count > 0 else { + container?.rightButton.isEnabled = false + return + } + container?.rightButton.isEnabled = true + if segmentedControl.selectedSegmentIndex == 0 { + range = .lastMonths(count) + } else { + range = .lastYears(count) + } + } + +} diff --git a/Mixin/UserInterface/Controllers/DeviceTransfer/DeviceTransferRange.swift b/Mixin/UserInterface/Controllers/DeviceTransfer/DeviceTransferRange.swift new file mode 100644 index 0000000000..4d389f555a --- /dev/null +++ b/Mixin/UserInterface/Controllers/DeviceTransfer/DeviceTransferRange.swift @@ -0,0 +1,51 @@ +import Foundation + +struct DeviceTransferRange { + + enum Conversation { + case all + case designated(Array) + + var title: String { + switch self { + case .all: + return R.string.localizable.all_chats() + case .designated(let conversations): + if conversations.count == 1 { + return R.string.localizable.chats_count_one() + } else { + return R.string.localizable.chats_count(conversations.count) + } + } + } + } + + enum Date { + case all + case lastMonths(Int) + case lastYears(Int) + + var title: String { + switch self { + case .all: + return R.string.localizable.all_dates() + case .lastMonths(let count): + if count == 1 { + return R.string.localizable.last_month() + } else { + return R.string.localizable.last_month_count(count) + } + case .lastYears(let count): + if count == 1 { + return R.string.localizable.last_year() + } else { + return R.string.localizable.last_year_count(count) + } + } + } + } + + var conversation: Conversation + var date: Date + +} diff --git a/Mixin/UserInterface/Controllers/DeviceTransfer/TransferToDesktopViewController.swift b/Mixin/UserInterface/Controllers/DeviceTransfer/TransferToDesktopViewController.swift index ac2901649e..99f533e0c6 100644 --- a/Mixin/UserInterface/Controllers/DeviceTransfer/TransferToDesktopViewController.swift +++ b/Mixin/UserInterface/Controllers/DeviceTransfer/TransferToDesktopViewController.swift @@ -4,12 +4,21 @@ import MixinServices class TransferToDesktopViewController: DeviceTransferSettingViewController { - private let section = SettingsRadioSection(rows: [ - SettingsRow(title: R.string.localizable.transfer_now(), titleStyle: .highlighted) + private lazy var actionSection = SettingsRadioSection(rows: [ + SettingsRow(title: R.string.localizable.transfer_now(), titleStyle: .highlighted), + ]) + private lazy var conversationRangeRow = SettingsRow(title: R.string.localizable.conversations(), + subtitle: DeviceTransferRange.Conversation.all.title, + accessory: .disclosure) + private lazy var dateRangeRow = SettingsRow(title: R.string.localizable.date(), + subtitle: DeviceTransferRange.Date.all.title, + accessory: .disclosure) + private lazy var dataSource = SettingsDataSource(sections: [ + actionSection, + SettingsRadioSection(rows: [conversationRangeRow, dateRangeRow]) ]) - private lazy var dataSource = SettingsDataSource(sections: [section]) - + private var range = DeviceTransferRange(conversation: .all, date: .all) private var observers: Set = [] private var server: DeviceTransferServer? @@ -33,48 +42,20 @@ extension TransferToDesktopViewController: UITableViewDelegate { func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) - if AppGroupUserDefaults.Account.isDesktopLoggedIn { - guard ReachabilityManger.shared.isReachableOnEthernetOrWiFi else { - Logger.general.info(category: "TransferToDesktop", message: "Network is not reachable") - alert(R.string.localizable.devices_on_same_network()) - return - } - guard WebSocketService.shared.isRealConnected else { - Logger.general.info(category: "TransferToDesktop", message: "WebSocket is not connected") - alert(R.string.localizable.unable_connect_to_desktop()) - return + switch indexPath.section { + case 0: + prepareTransfer() + default: + let controller: UIViewController + switch indexPath.row { + case 0: + controller = DeviceTransferConversationSelectionViewController.instance(range: range.conversation, + rangeChanged: updateCoversationRangeRow(conversationRange:)) + default: + controller = DeviceTransferDateSelectionViewController.instance(range: range.date, + rangeChanged: updateDateRangeRow(dateRange:)) } - tableView.isUserInteractionEnabled = false - let section = SettingsRadioSection(footer: R.string.localizable.open_desktop_to_confirm(), - rows: [SettingsRow(title: R.string.localizable.waiting(), titleStyle: .normal)]) - section.setAccessory(.busy, forRowAt: indexPath.row) - dataSource.replaceSection(at: indexPath.section, with: section, animation: .automatic) - let server = DeviceTransferServer() - server.$state - .receive(on: DispatchQueue.main) - .sink { [weak self] state in - self?.server(server, didChangeToState: state) - } - .store(in: &observers) - server.$lastConnectionRejectedReason - .sink { [weak self] reason in - if let self, let reason { - self.server(server, didRejectConnection: reason) - } - } - .store(in: &observers) - self.server = server - server.startListening() { [weak self] error in - guard let self else { - return - } - Logger.general.info(category: "TransferToDesktop", message: "Failed to start listening: \(error)") - self.alert(R.string.localizable.connection_establishment_failed()) { _ in - self.navigationController?.popViewController(animated: true) - } - } - } else { - alert(R.string.localizable.login_desktop_first()) + navigationController?.pushViewController(controller, animated: true) } } @@ -94,7 +75,7 @@ extension TransferToDesktopViewController { } Logger.general.info(category: "TransferToDesktop", message: "Command: \(command))") tableView.isUserInteractionEnabled = true - dataSource.replaceSection(at: 0, with: section, animation: .automatic) + dataSource.replaceSection(at: 0, with: actionSection, animation: .automatic) server?.stopListening() } @@ -127,14 +108,14 @@ extension TransferToDesktopViewController { Logger.general.info(category: "TransferToDesktop", message: "Send push command: \(success)") if !success, let self { self.alert(R.string.localizable.unable_connect_to_desktop()) - self.dataSource.replaceSection(at: 0, with: self.section, animation: .automatic) + self.dataSource.replaceSection(at: 0, with: self.actionSection, animation: .automatic) self.tableView.isUserInteractionEnabled = true } } case .transfer: observers.forEach({ $0.cancel() }) tableView.isUserInteractionEnabled = true - dataSource.replaceSection(at: 0, with: section, animation: .automatic) + dataSource.replaceSection(at: 0, with: actionSection, animation: .automatic) let progress = DeviceTransferProgressViewController(connection: .server(server, .desktop)) navigationController?.pushViewController(progress, animated: true) case let .closed(reason): @@ -151,7 +132,7 @@ extension TransferToDesktopViewController { private func server(_ server: DeviceTransferServer, didRejectConnection reason: DeviceTransferServer.ConnectionRejectedReason) { tableView.isUserInteractionEnabled = true - dataSource.replaceSection(at: 0, with: section, animation: .automatic) + dataSource.replaceSection(at: 0, with: actionSection, animation: .automatic) let title: String switch reason { case .mismatchedUser: @@ -167,3 +148,63 @@ extension TransferToDesktopViewController { } } + +extension TransferToDesktopViewController { + + private func prepareTransfer() { + if AppGroupUserDefaults.Account.isDesktopLoggedIn { + guard ReachabilityManger.shared.isReachableOnEthernetOrWiFi else { + Logger.general.info(category: "TransferToDesktop", message: "Network is not reachable") + alert(R.string.localizable.devices_on_same_network()) + return + } + guard WebSocketService.shared.isRealConnected else { + Logger.general.info(category: "TransferToDesktop", message: "WebSocket is not connected") + alert(R.string.localizable.unable_connect_to_desktop()) + return + } + tableView.isUserInteractionEnabled = false + let section = SettingsRadioSection(footer: R.string.localizable.open_desktop_to_confirm(), + rows: [SettingsRow(title: R.string.localizable.waiting(), titleStyle: .normal)]) + section.setAccessory(.busy, forRowAt: 0) + dataSource.replaceSection(at: 0, with: section, animation: .automatic) + let server = DeviceTransferServer() + server.$state + .receive(on: DispatchQueue.main) + .sink { [weak self] state in + self?.server(server, didChangeToState: state) + } + .store(in: &observers) + server.$lastConnectionRejectedReason + .sink { [weak self] reason in + if let self, let reason { + self.server(server, didRejectConnection: reason) + } + } + .store(in: &observers) + self.server = server + server.startListening() { [weak self] error in + guard let self else { + return + } + Logger.general.info(category: "TransferToDesktop", message: "Failed to start listening: \(error)") + self.alert(R.string.localizable.connection_establishment_failed()) { _ in + self.navigationController?.popViewController(animated: true) + } + } + } else { + alert(R.string.localizable.login_desktop_first()) + } + } + + private func updateCoversationRangeRow(conversationRange: DeviceTransferRange.Conversation) { + range.conversation = conversationRange + conversationRangeRow.subtitle = conversationRange.title + } + + private func updateDateRangeRow(dateRange: DeviceTransferRange.Date) { + range.date = dateRange + dateRangeRow.subtitle = dateRange.title + } + +} diff --git a/Mixin/UserInterface/Controllers/DeviceTransfer/TransferToPhoneViewController.swift b/Mixin/UserInterface/Controllers/DeviceTransfer/TransferToPhoneViewController.swift index 88a693342a..42bbb5e6e2 100644 --- a/Mixin/UserInterface/Controllers/DeviceTransfer/TransferToPhoneViewController.swift +++ b/Mixin/UserInterface/Controllers/DeviceTransfer/TransferToPhoneViewController.swift @@ -3,10 +3,20 @@ import MixinServices class TransferToPhoneViewController: DeviceTransferSettingViewController { + private lazy var conversationRangeRow = SettingsRow(title: R.string.localizable.conversations(), + subtitle: DeviceTransferRange.Conversation.all.title, + accessory: .disclosure) + private lazy var dateRangeRow = SettingsRow(title: R.string.localizable.date(), + subtitle: DeviceTransferRange.Date.all.title, + accessory: .disclosure) + private lazy var dataSource = SettingsDataSource(sections: [ - SettingsSection(rows: [SettingsRow(title: R.string.localizable.transfer_now(), titleStyle: .highlighted)]) + SettingsSection(rows: [SettingsRow(title: R.string.localizable.transfer_now(), titleStyle: .highlighted)]), + SettingsRadioSection(rows: [conversationRangeRow, dateRangeRow]) ]) + private var range = DeviceTransferRange(conversation: .all, date: .all) + override func viewDidLoad() { super.viewDidLoad() tableHeaderView.imageView.image = R.image.setting.ic_transfer_phone() @@ -26,13 +36,40 @@ extension TransferToPhoneViewController: UITableViewDelegate { func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) - guard ReachabilityManger.shared.isReachableOnEthernetOrWiFi else { - Logger.general.info(category: "TransferToPhone", message: "Network is not reachable") - alert(R.string.localizable.devices_on_same_network()) - return + let controller: UIViewController + switch indexPath.section { + case 0: + guard ReachabilityManger.shared.isReachableOnEthernetOrWiFi else { + Logger.general.info(category: "TransferToPhone", message: "Network is not reachable") + alert(R.string.localizable.devices_on_same_network()) + return + } + controller = TransferToPhoneQRCodeViewController.instance() + default: + switch indexPath.row { + case 0: + controller = DeviceTransferConversationSelectionViewController.instance(range: range.conversation, + rangeChanged: updateCoversationRangeRow(conversationRange:)) + default: + controller = DeviceTransferDateSelectionViewController.instance(range: range.date, + rangeChanged: updateDateRangeRow(dateRange:)) + } } - let controller = TransferToPhoneQRCodeViewController.instance() navigationController?.pushViewController(controller, animated: true) } } + +extension TransferToPhoneViewController { + + private func updateCoversationRangeRow(conversationRange: DeviceTransferRange.Conversation) { + range.conversation = conversationRange + conversationRangeRow.subtitle = conversationRange.title + } + + private func updateDateRangeRow(dateRange: DeviceTransferRange.Date) { + range.date = dateRange + dateRangeRow.subtitle = dateRange.title + } + +} diff --git a/Mixin/UserInterface/Controllers/DeviceTransfer/View/DeviceTransferDateSelectionView.xib b/Mixin/UserInterface/Controllers/DeviceTransfer/View/DeviceTransferDateSelectionView.xib new file mode 100644 index 0000000000..d2ae061bfe --- /dev/null +++ b/Mixin/UserInterface/Controllers/DeviceTransfer/View/DeviceTransferDateSelectionView.xib @@ -0,0 +1,188 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Mixin/UserInterface/Windows/DeviceTransferSelectedConversationWindow.swift b/Mixin/UserInterface/Windows/DeviceTransferSelectedConversationWindow.swift new file mode 100644 index 0000000000..3a15b88975 --- /dev/null +++ b/Mixin/UserInterface/Windows/DeviceTransferSelectedConversationWindow.swift @@ -0,0 +1,83 @@ +import UIKit + +class DeviceTransferSelectedConversationWindow: BottomSheetView { + + @IBOutlet weak var label: UILabel! + @IBOutlet weak var tableView: UITableView! + + @IBOutlet weak var tableViewHeightConstraint: NSLayoutConstraint! + + private let rowHeight = 70.0 + private let maxTableViewHeight = 500.0 + + private var deletionHandler: ((_ receiver: MessageReceiver) -> Void)? + private var selections = [MessageReceiver]() { + didSet { + if selections.count == 1 { + label.text = R.string.localizable.items_selected_one() + } else { + label.text = R.string.localizable.items_selected_count(selections.count) + } + let height = Double(selections.count) * rowHeight + tableViewHeightConstraint.constant = min(height, maxTableViewHeight) + } + } + + override func awakeFromNib() { + super.awakeFromNib() + tableView.dataSource = self + tableView.delegate = self + tableView.register(CheckmarkPeerCell.nib, forCellReuseIdentifier: CheckmarkPeerCell.reuseIdentifier) + } + + class func instance() -> DeviceTransferSelectedConversationWindow { + R.nib.deviceTransferSelectedConversationWindow(owner: self)! + } + + func render(selections: [MessageReceiver], deletionHandler: @escaping ((_ receiver: MessageReceiver) -> Void)) { + self.selections = selections + self.deletionHandler = deletionHandler + self.tableView.reloadData() + } + + @IBAction func dismissAction(_ sender: Any) { + dismissPopupController(animated: true) + } + +} + +extension DeviceTransferSelectedConversationWindow: UITableViewDataSource { + + func numberOfSections(in tableView: UITableView) -> Int { + 1 + } + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + selections.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: CheckmarkPeerCell.reuseIdentifier, for: indexPath) as! CheckmarkPeerCell + cell.render(receiver: selections[indexPath.row]) + return cell + } + +} + +extension DeviceTransferSelectedConversationWindow: UITableViewDelegate { + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + deletionHandler?(selections[indexPath.row]) + if selections.count == 1 { + dismissPopupController(animated: true) + } else { + selections.remove(at: indexPath.row) + tableView.reloadData() + } + } + + func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { + cell.isSelected = true + } + +} diff --git a/Mixin/UserInterface/Windows/DeviceTransferSelectedConversationWindow.xib b/Mixin/UserInterface/Windows/DeviceTransferSelectedConversationWindow.xib new file mode 100644 index 0000000000..8b500b2614 --- /dev/null +++ b/Mixin/UserInterface/Windows/DeviceTransferSelectedConversationWindow.xib @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From f8822b29f4427e3167620f9a4a010ccb94388eda Mon Sep 17 00:00:00 2001 From: fanyu Date: Fri, 9 Jun 2023 11:17:39 +0800 Subject: [PATCH 03/28] Add range for server --- Mixin/Service/DeviceTransfer/DeviceTransferServer.swift | 6 ++++-- .../DeviceTransfer/TransferToDesktopViewController.swift | 2 +- .../TransferToPhoneQRCodeViewController.swift | 6 ++++-- .../DeviceTransfer/TransferToPhoneViewController.swift | 2 +- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/Mixin/Service/DeviceTransfer/DeviceTransferServer.swift b/Mixin/Service/DeviceTransfer/DeviceTransferServer.swift index 8415f60e5e..546bd22350 100644 --- a/Mixin/Service/DeviceTransfer/DeviceTransferServer.swift +++ b/Mixin/Service/DeviceTransfer/DeviceTransferServer.swift @@ -25,6 +25,7 @@ final class DeviceTransferServer { // Access on main queue @Published private(set) var lastConnectionRejectedReason: ConnectionRejectedReason? + private let range: DeviceTransferRange private let queue = Queue(label: "one.mixin.messenger.DeviceTransferServer") private let dataLoaderQueue = Queue(label: "one.mixin.messenger.DeviceTransferServer.Loader") private let speedInspector = NetworkSpeedInspector() @@ -41,8 +42,9 @@ final class DeviceTransferServer { Unmanaged.passUnretained(self).toOpaque() } - init() { - Logger.general.info(category: "DeviceTransferServer", message: "\(opaquePointer) init") + init(range: DeviceTransferRange) { + self.range = range + Logger.general.info(category: "DeviceTransferServer", message: "\(opaquePointer) init with range: \(range)") } deinit { diff --git a/Mixin/UserInterface/Controllers/DeviceTransfer/TransferToDesktopViewController.swift b/Mixin/UserInterface/Controllers/DeviceTransfer/TransferToDesktopViewController.swift index 99f533e0c6..7b5c7a5ab8 100644 --- a/Mixin/UserInterface/Controllers/DeviceTransfer/TransferToDesktopViewController.swift +++ b/Mixin/UserInterface/Controllers/DeviceTransfer/TransferToDesktopViewController.swift @@ -168,7 +168,7 @@ extension TransferToDesktopViewController { rows: [SettingsRow(title: R.string.localizable.waiting(), titleStyle: .normal)]) section.setAccessory(.busy, forRowAt: 0) dataSource.replaceSection(at: 0, with: section, animation: .automatic) - let server = DeviceTransferServer() + let server = DeviceTransferServer(range: range) server.$state .receive(on: DispatchQueue.main) .sink { [weak self] state in diff --git a/Mixin/UserInterface/Controllers/DeviceTransfer/TransferToPhoneQRCodeViewController.swift b/Mixin/UserInterface/Controllers/DeviceTransfer/TransferToPhoneQRCodeViewController.swift index 7e4123019c..0a8c6270db 100644 --- a/Mixin/UserInterface/Controllers/DeviceTransfer/TransferToPhoneQRCodeViewController.swift +++ b/Mixin/UserInterface/Controllers/DeviceTransfer/TransferToPhoneQRCodeViewController.swift @@ -13,6 +13,7 @@ class TransferToPhoneQRCodeViewController: UIViewController { private var server: DeviceTransferServer? private var hasTrasferStarted = false private var isListening = false + private var range: DeviceTransferRange! private let userID = myUserId @@ -37,8 +38,9 @@ class TransferToPhoneQRCodeViewController: UIViewController { } } - class func instance() -> UIViewController { + class func instance(range: DeviceTransferRange) -> UIViewController { let vc = TransferToPhoneQRCodeViewController() + vc.range = range return ContainerViewController.instance(viewController: vc, title: R.string.localizable.waiting_for_other_device()) } @@ -59,7 +61,7 @@ extension TransferToPhoneQRCodeViewController { isListening = false observers.forEach { $0.cancel() } observers.removeAll() - let server = DeviceTransferServer() + let server = DeviceTransferServer(range: range) server.$state .receive(on: DispatchQueue.main) .sink { [weak self] state in diff --git a/Mixin/UserInterface/Controllers/DeviceTransfer/TransferToPhoneViewController.swift b/Mixin/UserInterface/Controllers/DeviceTransfer/TransferToPhoneViewController.swift index 42bbb5e6e2..ee2f99df01 100644 --- a/Mixin/UserInterface/Controllers/DeviceTransfer/TransferToPhoneViewController.swift +++ b/Mixin/UserInterface/Controllers/DeviceTransfer/TransferToPhoneViewController.swift @@ -44,7 +44,7 @@ extension TransferToPhoneViewController: UITableViewDelegate { alert(R.string.localizable.devices_on_same_network()) return } - controller = TransferToPhoneQRCodeViewController.instance() + controller = TransferToPhoneQRCodeViewController.instance(range: range) default: switch indexPath.row { case 0: From d9dc3e8c70f940294f88bec1d6aa7a6e66d169bb Mon Sep 17 00:00:00 2001 From: fanyu Date: Tue, 13 Jun 2023 16:42:50 +0800 Subject: [PATCH 04/28] Filter conversation and time --- Mixin.xcodeproj/project.pbxproj | 8 +- .../DeviceTransfer/DeviceTransferServer.swift | 10 +- .../DeviceTransferServerDataSource.swift | 128 ++++++++++++------ ...rConversationSelectionViewController.swift | 16 +-- ...eTransferDateSelectionViewController.swift | 28 ++-- ...Range.swift => DeviceTransferFilter.swift} | 42 +++++- .../RestoreFromCloudViewController.swift | 2 +- .../TransferToDesktopViewController.swift | 34 ++--- .../TransferToPhoneQRCodeViewController.swift | 8 +- .../TransferToPhoneViewController.swift | 34 ++--- .../Database/User/DAO/ConversationDAO.swift | 17 ++- .../Database/User/DAO/MessageDAO.swift | 53 +++++++- .../Database/User/DAO/MessageMentionDAO.swift | 17 ++- .../Database/User/DAO/ParticipantDAO.swift | 17 ++- .../Database/User/DAO/PinMessageDAO.swift | 30 +++- .../Database/User/Model/Message.swift | 6 + 16 files changed, 318 insertions(+), 132 deletions(-) rename Mixin/UserInterface/Controllers/DeviceTransfer/{DeviceTransferRange.swift => DeviceTransferFilter.swift} (53%) diff --git a/Mixin.xcodeproj/project.pbxproj b/Mixin.xcodeproj/project.pbxproj index d83302ce37..d08ea47eaf 100644 --- a/Mixin.xcodeproj/project.pbxproj +++ b/Mixin.xcodeproj/project.pbxproj @@ -625,7 +625,7 @@ 7C7635B826A13461006101DB /* HomeAppsConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C7635B726A13461006101DB /* HomeAppsConstants.swift */; }; 7C7DBAD62A2F3464008D4B0E /* DeviceTransferDateSelectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C7DBAD52A2F3464008D4B0E /* DeviceTransferDateSelectionViewController.swift */; }; 7C7DBAD82A2F34CC008D4B0E /* DeviceTransferConversationSelectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C7DBAD72A2F34CC008D4B0E /* DeviceTransferConversationSelectionViewController.swift */; }; - 7C7DBADA2A2F391C008D4B0E /* DeviceTransferRange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C7DBAD92A2F391C008D4B0E /* DeviceTransferRange.swift */; }; + 7C7DBADA2A2F391C008D4B0E /* DeviceTransferFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C7DBAD92A2F391C008D4B0E /* DeviceTransferFilter.swift */; }; 7C8CC5A4280D347A00F7CBDF /* PreviewWallpaperViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C8CC5A2280D347A00F7CBDF /* PreviewWallpaperViewController.swift */; }; 7C8CC5A7280D40E900F7CBDF /* PreviewWallpaperCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C8CC5A6280D40E900F7CBDF /* PreviewWallpaperCell.swift */; }; 7C8FA78D27687D1500855AFD /* DeleteAccountSettingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C8FA78C27687D1500855AFD /* DeleteAccountSettingViewController.swift */; }; @@ -1704,7 +1704,7 @@ 7C7635B726A13461006101DB /* HomeAppsConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeAppsConstants.swift; sourceTree = ""; }; 7C7DBAD52A2F3464008D4B0E /* DeviceTransferDateSelectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceTransferDateSelectionViewController.swift; sourceTree = ""; }; 7C7DBAD72A2F34CC008D4B0E /* DeviceTransferConversationSelectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceTransferConversationSelectionViewController.swift; sourceTree = ""; }; - 7C7DBAD92A2F391C008D4B0E /* DeviceTransferRange.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceTransferRange.swift; sourceTree = ""; }; + 7C7DBAD92A2F391C008D4B0E /* DeviceTransferFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceTransferFilter.swift; sourceTree = ""; }; 7C8CC5A2280D347A00F7CBDF /* PreviewWallpaperViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewWallpaperViewController.swift; sourceTree = ""; }; 7C8CC5A6280D40E900F7CBDF /* PreviewWallpaperCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewWallpaperCell.swift; sourceTree = ""; }; 7C8FA78C27687D1500855AFD /* DeleteAccountSettingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeleteAccountSettingViewController.swift; sourceTree = ""; }; @@ -2801,7 +2801,7 @@ 7C7579D529DC573F0002DA0B /* DeviceTransferProgressViewController.swift */, 7C7DBAD72A2F34CC008D4B0E /* DeviceTransferConversationSelectionViewController.swift */, 7C7DBAD52A2F3464008D4B0E /* DeviceTransferDateSelectionViewController.swift */, - 7C7DBAD92A2F391C008D4B0E /* DeviceTransferRange.swift */, + 7C7DBAD92A2F391C008D4B0E /* DeviceTransferFilter.swift */, ); path = DeviceTransfer; sourceTree = ""; @@ -5160,7 +5160,7 @@ 7B57186E24D9375300682D86 /* MixinAPIError+Description.swift in Sources */, E0320FA523CC3D1600A651D4 /* MessageTagLabel.swift in Sources */, 7B333A8623406AD000FDA848 /* GalleryTransitionFromMessageCellView.swift in Sources */, - 7C7DBADA2A2F391C008D4B0E /* DeviceTransferRange.swift in Sources */, + 7C7DBADA2A2F391C008D4B0E /* DeviceTransferFilter.swift in Sources */, 7B54F95922B243EA00908A9D /* EmergencyContactVerifyPinViewController.swift in Sources */, 7B68F78B2191741300B79978 /* BiometryType.swift in Sources */, 7BD0532820A41F6A00C36F69 /* RangeExtension.swift in Sources */, diff --git a/Mixin/Service/DeviceTransfer/DeviceTransferServer.swift b/Mixin/Service/DeviceTransfer/DeviceTransferServer.swift index 546bd22350..c67252a821 100644 --- a/Mixin/Service/DeviceTransfer/DeviceTransferServer.swift +++ b/Mixin/Service/DeviceTransfer/DeviceTransferServer.swift @@ -25,7 +25,7 @@ final class DeviceTransferServer { // Access on main queue @Published private(set) var lastConnectionRejectedReason: ConnectionRejectedReason? - private let range: DeviceTransferRange + private let filter: DeviceTransferFilter private let queue = Queue(label: "one.mixin.messenger.DeviceTransferServer") private let dataLoaderQueue = Queue(label: "one.mixin.messenger.DeviceTransferServer.Loader") private let speedInspector = NetworkSpeedInspector() @@ -42,9 +42,9 @@ final class DeviceTransferServer { Unmanaged.passUnretained(self).toOpaque() } - init(range: DeviceTransferRange) { - self.range = range - Logger.general.info(category: "DeviceTransferServer", message: "\(opaquePointer) init with range: \(range)") + init(filter: DeviceTransferFilter) { + self.filter = filter + Logger.general.info(category: "DeviceTransferServer", message: "\(opaquePointer) init with filter: \(filter)") } deinit { @@ -322,7 +322,7 @@ extension DeviceTransferServer { Logger.general.warn(category: "DeviceTransferServer", message: "Not transfering due to invalid state") return } - let dataSource = DeviceTransferServerDataSource(key: key, remotePlatform: remotePlatform) + let dataSource = DeviceTransferServerDataSource(key: key, remotePlatform: remotePlatform, filter: filter) let count = dataSource.totalCount() let start = DeviceTransferCommand(action: .start(count)) do { diff --git a/Mixin/Service/DeviceTransfer/DeviceTransferServerDataSource.swift b/Mixin/Service/DeviceTransfer/DeviceTransferServerDataSource.swift index da676c6142..605b12b70b 100644 --- a/Mixin/Service/DeviceTransfer/DeviceTransferServerDataSource.swift +++ b/Mixin/Service/DeviceTransfer/DeviceTransferServerDataSource.swift @@ -9,11 +9,17 @@ final class DeviceTransferServerDataSource { private let key: DeviceTransferKey private let remotePlatform: DeviceTransferPlatform private let fileContentBuffer: UnsafeMutablePointer + private let conversationIDs: String? + private let fromDate: String? + private let needsFilterData: Bool - init(key: DeviceTransferKey, remotePlatform: DeviceTransferPlatform) { + init(key: DeviceTransferKey, remotePlatform: DeviceTransferPlatform, filter: DeviceTransferFilter) { self.key = key self.remotePlatform = remotePlatform self.fileContentBuffer = .allocate(capacity: fileChunkSize) + conversationIDs = filter.conversation.joinedIDs + fromDate = filter.time.utcString + needsFilterData = conversationIDs != nil || fromDate != nil } deinit { @@ -27,22 +33,27 @@ extension DeviceTransferServerDataSource { func totalCount() -> Int { assert(!Queue.main.isCurrent) - let messagesCount = MessageDAO.shared.messagesCount() - let attachmentsCount = attachmentsCount() - let total = ConversationDAO.shared.conversationsCount() - + ParticipantDAO.shared.participantsCount() + let messagesCount = MessageDAO.shared.messagesCount(matching: conversationIDs, sinceDate: fromDate) + let attachmentsCount = needsFilterData + ? MessageDAO.shared.mediaMessagesCount(matching: conversationIDs) + : attachmentsCount() + let transcriptMessageCount = needsFilterData + ? MessageDAO.shared.transcriptMessageCount(matching: conversationIDs, sinceDate: fromDate) + : TranscriptMessageDAO.shared.transcriptMessagesCount() + let total = ConversationDAO.shared.conversationsCount(matching: conversationIDs) + + ParticipantDAO.shared.participantsCount(matching: conversationIDs) + UserDAO.shared.usersCount() + AppDAO.shared.appsCount() + AssetDAO.shared.assetsCount() + SnapshotDAO.shared.snapshotsCount() + StickerDAO.shared.stickersCount() - + PinMessageDAO.shared.pinMessagesCount() - + TranscriptMessageDAO.shared.transcriptMessagesCount() + + PinMessageDAO.shared.pinMessagesCount(matching: conversationIDs, sinceDate: fromDate) + + transcriptMessageCount + messagesCount - + MessageMentionDAO.shared.messageMentionsCount() + + MessageMentionDAO.shared.messageMentionsCount(matching: conversationIDs) + ExpiredMessageDAO.shared.expiredMessagesCount() + attachmentsCount - Logger.general.info(category: "DeviceTransferServerDataSource", message: "Total: \(total), Messages: \(messagesCount), attachments: \(attachmentsCount)") + Logger.general.info(category: "DeviceTransferServerDataSource", message: "Total: \(total), Messages: \(messagesCount), attachments: \(attachmentsCount), transcriptMessageCount: \(transcriptMessageCount)") return total } @@ -156,7 +167,9 @@ extension DeviceTransferServerDataSource { let databaseItemCount: Int switch location.type { case .conversation: - let conversations = ConversationDAO.shared.conversations(limit: limit, after: location.primaryID) + let conversations = ConversationDAO.shared.conversations(limit: limit, + after: location.primaryID, + matching: conversationIDs) databaseItemCount = conversations.count nextPrimaryID = conversations.last?.conversationId nextSecondaryID = nil @@ -171,7 +184,10 @@ extension DeviceTransferServerDataSource { } } case .participant: - let participants = ParticipantDAO.shared.participants(limit: limit, after: location.primaryID, with: location.secondaryID) + let participants = ParticipantDAO.shared.participants(limit: limit, + after: location.primaryID, + with: location.secondaryID, + matching: conversationIDs) databaseItemCount = participants.count nextPrimaryID = participants.last?.conversationId nextSecondaryID = participants.last?.userId @@ -261,7 +277,10 @@ extension DeviceTransferServerDataSource { } } case .pinMessage: - let pinMessages = PinMessageDAO.shared.pinMessages(limit: limit, after: location.primaryID) + let pinMessages = PinMessageDAO.shared.pinMessages(limit: limit, + after: location.primaryID, + matching: conversationIDs, + sinceDate: fromDate) databaseItemCount = pinMessages.count nextPrimaryID = pinMessages.last?.messageId nextSecondaryID = nil @@ -276,49 +295,61 @@ extension DeviceTransferServerDataSource { } } case .transcriptMessage: - let transcriptMessages = TranscriptMessageDAO.shared.transcriptMessages(limit: limit, after: location.primaryID, with: location.secondaryID) - databaseItemCount = transcriptMessages.count - nextPrimaryID = transcriptMessages.last?.transcriptId - nextSecondaryID = transcriptMessages.last?.messageId - transferItems = transcriptMessages.compactMap { transcriptMessage in - let deviceTransferTranscriptMessage = DeviceTransferTranscriptMessage(transcriptMessage: transcriptMessage, to: remotePlatform) - do { - let outputData = try DeviceTransferProtocol.output(type: location.type, data: deviceTransferTranscriptMessage, key: key) - if let mediaURL = transcriptMessage.mediaUrl, !mediaURL.isEmpty, transcriptMessage.mediaStatus == MediaStatus.DONE.rawValue { - let url = AttachmentContainer.url(transcriptId: transcriptMessage.transcriptId, filename: mediaURL) - let attachment = TransferItem.Attachment(messageID: transcriptMessage.messageId, url: url) - return TransferItem(rawItem: transcriptMessage, outputData: outputData, attachment: attachment) - } else { - return TransferItem(rawItem: transcriptMessage, outputData: outputData, attachment: nil) - } - } catch { - Logger.general.error(category: "DeviceTransferServerDataSource", message: "Failed to output: \(error)") - return nil - } + if needsFilterData { + databaseItemCount = 0 + nextPrimaryID = nil + nextSecondaryID = nil + transferItems = [] + } else { + let transcriptMessages = TranscriptMessageDAO.shared.transcriptMessages(limit: limit, after: location.primaryID, with: location.secondaryID) + databaseItemCount = transcriptMessages.count + nextPrimaryID = transcriptMessages.last?.transcriptId + nextSecondaryID = transcriptMessages.last?.messageId + transferItems = transcriptTransferItems(for: transcriptMessages) } case .message: - let messages = MessageDAO.shared.messages(limit: limit, after: location.primaryID) + let messages = MessageDAO.shared.messages(limit: limit, + after: location.primaryID, + matching: conversationIDs, + sinceDate: fromDate) databaseItemCount = messages.count nextPrimaryID = messages.last?.messageId nextSecondaryID = nil - transferItems = messages.compactMap { message in + var messageItems = [TransferItem]() + var transcriptMessageItems = [TransferItem]() + var transcriptMessageCount = 0 + for message in messages { let deviceTransferMessage = DeviceTransferMessage(message: message, to: remotePlatform) do { let outputData = try DeviceTransferProtocol.output(type: location.type, data: deviceTransferMessage, key: key) + let attachment: TransferItem.Attachment? if let mediaURL = message.mediaUrl, !mediaURL.isEmpty, message.mediaStatus == MediaStatus.DONE.rawValue, let category = AttachmentContainer.Category(messageCategory: message.category) { let url = AttachmentContainer.url(for: category, filename: mediaURL) - let attachment = TransferItem.Attachment(messageID: message.messageId, url: url) - return TransferItem(rawItem: message, outputData: outputData, attachment: attachment) + attachment = TransferItem.Attachment(messageID: message.messageId, url: url) } else { - return TransferItem(rawItem: message, outputData: outputData, attachment: nil) + attachment = nil + } + if let item = TransferItem(rawItem: message, outputData: outputData, attachment: attachment) { + messageItems.append(item) } } catch { - Logger.general.error(category: "DeviceTransferServerDataSource", message: "Failed to output: \(error)") - return nil + Logger.general.error(category: "DeviceTransferServerDataSource", message: "Failed to output message: \(error)") } + // TranscriptMessage + if needsFilterData && message.category.hasSuffix("_TRANSCRIPT") { + transcriptMessageCount += 1 + let transcriptMessages = TranscriptMessageDAO.shared.transcriptMessages(transcriptId: message.messageId) + transcriptMessageItems = transcriptTransferItems(for: transcriptMessages) + } + } + if transcriptMessageCount != 0 { + Logger.general.info(category: "DeviceTransferServerDataSource", message: "Send transcriptMessages along with messages: \(transcriptMessageCount)") } + transferItems = transcriptMessageItems + messageItems case .messageMention: - let messageMentions = MessageMentionDAO.shared.messageMentions(limit: limit, after: location.primaryID) + let messageMentions = MessageMentionDAO.shared.messageMentions(limit: limit, + after: location.primaryID, + matching: conversationIDs) databaseItemCount = messageMentions.count nextPrimaryID = messageMentions.last?.messageId nextSecondaryID = nil @@ -430,4 +461,23 @@ extension DeviceTransferServerDataSource { return true } + private func transcriptTransferItems(for transcriptMessages: [TranscriptMessage]) -> [TransferItem] { + transcriptMessages.compactMap { transcriptMessage in + let deviceTransferTranscriptMessage = DeviceTransferTranscriptMessage(transcriptMessage: transcriptMessage, to: remotePlatform) + do { + let outputData = try DeviceTransferProtocol.output(type: .transcriptMessage, data: deviceTransferTranscriptMessage, key: key) + if let mediaURL = transcriptMessage.mediaUrl, !mediaURL.isEmpty, transcriptMessage.mediaStatus == MediaStatus.DONE.rawValue { + let url = AttachmentContainer.url(transcriptId: transcriptMessage.transcriptId, filename: mediaURL) + let attachment = TransferItem.Attachment(messageID: transcriptMessage.messageId, url: url) + return TransferItem(rawItem: transcriptMessage, outputData: outputData, attachment: attachment) + } else { + return TransferItem(rawItem: transcriptMessage, outputData: outputData, attachment: nil) + } + } catch { + Logger.general.error(category: "DeviceTransferServerDataSource", message: "Failed to output: \(error)") + return nil + } + } + } + } diff --git a/Mixin/UserInterface/Controllers/DeviceTransfer/DeviceTransferConversationSelectionViewController.swift b/Mixin/UserInterface/Controllers/DeviceTransfer/DeviceTransferConversationSelectionViewController.swift index 79188ed76f..7f044b8baa 100644 --- a/Mixin/UserInterface/Controllers/DeviceTransfer/DeviceTransferConversationSelectionViewController.swift +++ b/Mixin/UserInterface/Controllers/DeviceTransfer/DeviceTransferConversationSelectionViewController.swift @@ -25,13 +25,13 @@ class DeviceTransferConversationSelectionViewController: PeerViewController Void)? + private var filter: DeviceTransferFilter.Conversation = .all + private var changeHandler: DeviceTransferFilter.ConversationChangeHandler? - class func instance(range: DeviceTransferRange.Conversation, rangeChanged: @escaping ((DeviceTransferRange.Conversation) -> Void)) -> UIViewController { + class func instance(filter: DeviceTransferFilter.Conversation, changeHandler: @escaping DeviceTransferFilter.ConversationChangeHandler) -> UIViewController { let controller = DeviceTransferConversationSelectionViewController() - controller.range = range - controller.rangeChanged = rangeChanged + controller.filter = filter + controller.changeHandler = changeHandler return ContainerViewController.instance(viewController: controller, title: R.string.localizable.conversations()) } @@ -51,7 +51,7 @@ class DeviceTransferConversationSelectionViewController: PeerViewController Void)? + private var filter: DeviceTransferFilter.Time = .all + private var changeHandler: DeviceTransferFilter.TimeChangeHandler? convenience init() { self.init(nib: R.nib.deviceTransferDateSelectionView) @@ -18,7 +18,7 @@ class DeviceTransferDateSelectionViewController: UIViewController { super.viewDidLoad() segmentedControl.setTitle(R.string.localizable.month(), forSegmentAt: 0) segmentedControl.setTitle(R.string.localizable.year(), forSegmentAt: 1) - switch range { + switch filter { case .all: updateAllDateSelection() case .lastMonths(let months): @@ -32,10 +32,10 @@ class DeviceTransferDateSelectionViewController: UIViewController { } } - class func instance(range: DeviceTransferRange.Date, rangeChanged: @escaping ((DeviceTransferRange.Date) -> Void)) -> UIViewController { + class func instance(filter: DeviceTransferFilter.Time, changeHandler: @escaping DeviceTransferFilter.TimeChangeHandler) -> UIViewController { let controller = DeviceTransferDateSelectionViewController() - controller.range = range - controller.rangeChanged = rangeChanged + controller.filter = filter + controller.changeHandler = changeHandler return ContainerViewController.instance(viewController: controller, title: R.string.localizable.date()) } @@ -48,11 +48,11 @@ class DeviceTransferDateSelectionViewController: UIViewController { } @IBAction func dateUnitChangedAction(_ sender: Any) { - updateDateRange() + updateDateFilter() } @IBAction func textChangedAction(_ sender: Any) { - updateDateRange() + updateDateFilter() } @IBAction func editingBeginAction(_ sender: Any) { @@ -68,7 +68,7 @@ extension DeviceTransferDateSelectionViewController: ContainerViewControllerDele } func barRightButtonTappedAction() { - rangeChanged?(range) + changeHandler?(filter) navigationController?.popViewController(animated: true) } @@ -80,7 +80,7 @@ extension DeviceTransferDateSelectionViewController { allDateCheckmark.isHidden = false lastDateCheckmark.isHidden = true segmentedControl.isEnabled = false - range = .all + filter = .all container?.rightButton.isEnabled = true textField.resignFirstResponder() } @@ -90,19 +90,19 @@ extension DeviceTransferDateSelectionViewController { lastDateCheckmark.isHidden = false segmentedControl.isEnabled = true textField.becomeFirstResponder() - updateDateRange() + updateDateFilter() } - private func updateDateRange() { + private func updateDateFilter() { guard let count = textField.text?.intValue, count > 0 else { container?.rightButton.isEnabled = false return } container?.rightButton.isEnabled = true if segmentedControl.selectedSegmentIndex == 0 { - range = .lastMonths(count) + filter = .lastMonths(count) } else { - range = .lastYears(count) + filter = .lastYears(count) } } diff --git a/Mixin/UserInterface/Controllers/DeviceTransfer/DeviceTransferRange.swift b/Mixin/UserInterface/Controllers/DeviceTransfer/DeviceTransferFilter.swift similarity index 53% rename from Mixin/UserInterface/Controllers/DeviceTransfer/DeviceTransferRange.swift rename to Mixin/UserInterface/Controllers/DeviceTransfer/DeviceTransferFilter.swift index 4d389f555a..471f64c082 100644 --- a/Mixin/UserInterface/Controllers/DeviceTransfer/DeviceTransferRange.swift +++ b/Mixin/UserInterface/Controllers/DeviceTransfer/DeviceTransferFilter.swift @@ -1,8 +1,13 @@ import Foundation +import MixinServices -struct DeviceTransferRange { +struct DeviceTransferFilter { + + typealias TimeChangeHandler = (DeviceTransferFilter.Time) -> Void + typealias ConversationChangeHandler = (DeviceTransferFilter.Conversation) -> Void enum Conversation { + case all case designated(Array) @@ -18,9 +23,20 @@ struct DeviceTransferRange { } } } + + var joinedIDs: String? { + switch self { + case .all: + return nil + case .designated(let ids): + return ids.joined(separator: "', '") + } + } + } - enum Date { + enum Time { + case all case lastMonths(Int) case lastYears(Int) @@ -43,9 +59,29 @@ struct DeviceTransferRange { } } } + + var utcString: String? { + let monthsAgo: Int + switch self { + case .all: + monthsAgo = 0 + case .lastMonths(let months): + monthsAgo = months + case .lastYears(let years): + monthsAgo = years * 12 + } + if monthsAgo == 0 { + return nil + } else { + let calendar = Calendar.current + let startOfToday = calendar.startOfDay(for: Date()) + return calendar.date(byAdding: .month, value: -monthsAgo, to: startOfToday)?.toUTCString() + } + } + } var conversation: Conversation - var date: Date + var time: Time } diff --git a/Mixin/UserInterface/Controllers/DeviceTransfer/RestoreFromCloudViewController.swift b/Mixin/UserInterface/Controllers/DeviceTransfer/RestoreFromCloudViewController.swift index e90b618a4a..6cf60840b3 100644 --- a/Mixin/UserInterface/Controllers/DeviceTransfer/RestoreFromCloudViewController.swift +++ b/Mixin/UserInterface/Controllers/DeviceTransfer/RestoreFromCloudViewController.swift @@ -38,7 +38,7 @@ extension RestoreFromCloudViewController: UITableViewDelegate { section.setAccessory(.busy, forRowAt: indexPath.row) DispatchQueue.global().async { if let lastMessageCreatedAt = MessageDAO.shared.lastMessageCreatedAt() { - let messageCount = MessageDAO.shared.messagesCount() + let messageCount = MessageDAO.shared.messagesCount(matching: nil, sinceDate: nil) let formattedCount = NumberFormatter.decimal.string(from: NSNumber(value: messageCount)) ?? "\(messageCount)" let createdAt = DateFormatter.dateFull.string(from: lastMessageCreatedAt.toUTCDate()) DispatchQueue.main.async { [weak self] in diff --git a/Mixin/UserInterface/Controllers/DeviceTransfer/TransferToDesktopViewController.swift b/Mixin/UserInterface/Controllers/DeviceTransfer/TransferToDesktopViewController.swift index 7b5c7a5ab8..039e4493b5 100644 --- a/Mixin/UserInterface/Controllers/DeviceTransfer/TransferToDesktopViewController.swift +++ b/Mixin/UserInterface/Controllers/DeviceTransfer/TransferToDesktopViewController.swift @@ -7,18 +7,18 @@ class TransferToDesktopViewController: DeviceTransferSettingViewController { private lazy var actionSection = SettingsRadioSection(rows: [ SettingsRow(title: R.string.localizable.transfer_now(), titleStyle: .highlighted), ]) - private lazy var conversationRangeRow = SettingsRow(title: R.string.localizable.conversations(), - subtitle: DeviceTransferRange.Conversation.all.title, + private lazy var conversationFilterRow = SettingsRow(title: R.string.localizable.conversations(), + subtitle: DeviceTransferFilter.Conversation.all.title, accessory: .disclosure) - private lazy var dateRangeRow = SettingsRow(title: R.string.localizable.date(), - subtitle: DeviceTransferRange.Date.all.title, + private lazy var dateFilterRow = SettingsRow(title: R.string.localizable.date(), + subtitle: DeviceTransferFilter.Time.all.title, accessory: .disclosure) private lazy var dataSource = SettingsDataSource(sections: [ actionSection, - SettingsRadioSection(rows: [conversationRangeRow, dateRangeRow]) + SettingsRadioSection(rows: [conversationFilterRow, dateFilterRow]) ]) - private var range = DeviceTransferRange(conversation: .all, date: .all) + private var filter = DeviceTransferFilter(conversation: .all, time: .all) private var observers: Set = [] private var server: DeviceTransferServer? @@ -49,11 +49,11 @@ extension TransferToDesktopViewController: UITableViewDelegate { let controller: UIViewController switch indexPath.row { case 0: - controller = DeviceTransferConversationSelectionViewController.instance(range: range.conversation, - rangeChanged: updateCoversationRangeRow(conversationRange:)) + controller = DeviceTransferConversationSelectionViewController.instance(filter: filter.conversation, + changeHandler: updateCoversationFilterRow(conversationFilter:)) default: - controller = DeviceTransferDateSelectionViewController.instance(range: range.date, - rangeChanged: updateDateRangeRow(dateRange:)) + controller = DeviceTransferDateSelectionViewController.instance(filter: filter.time, + changeHandler: updateDateFilterRow(timeFilter:)) } navigationController?.pushViewController(controller, animated: true) } @@ -168,7 +168,7 @@ extension TransferToDesktopViewController { rows: [SettingsRow(title: R.string.localizable.waiting(), titleStyle: .normal)]) section.setAccessory(.busy, forRowAt: 0) dataSource.replaceSection(at: 0, with: section, animation: .automatic) - let server = DeviceTransferServer(range: range) + let server = DeviceTransferServer(filter: filter) server.$state .receive(on: DispatchQueue.main) .sink { [weak self] state in @@ -197,14 +197,14 @@ extension TransferToDesktopViewController { } } - private func updateCoversationRangeRow(conversationRange: DeviceTransferRange.Conversation) { - range.conversation = conversationRange - conversationRangeRow.subtitle = conversationRange.title + private func updateCoversationFilterRow(conversationFilter: DeviceTransferFilter.Conversation) { + filter.conversation = conversationFilter + conversationFilterRow.subtitle = conversationFilter.title } - private func updateDateRangeRow(dateRange: DeviceTransferRange.Date) { - range.date = dateRange - dateRangeRow.subtitle = dateRange.title + private func updateDateFilterRow(timeFilter: DeviceTransferFilter.Time) { + filter.time = timeFilter + dateFilterRow.subtitle = timeFilter.title } } diff --git a/Mixin/UserInterface/Controllers/DeviceTransfer/TransferToPhoneQRCodeViewController.swift b/Mixin/UserInterface/Controllers/DeviceTransfer/TransferToPhoneQRCodeViewController.swift index 0a8c6270db..f42eea5a42 100644 --- a/Mixin/UserInterface/Controllers/DeviceTransfer/TransferToPhoneQRCodeViewController.swift +++ b/Mixin/UserInterface/Controllers/DeviceTransfer/TransferToPhoneQRCodeViewController.swift @@ -13,7 +13,7 @@ class TransferToPhoneQRCodeViewController: UIViewController { private var server: DeviceTransferServer? private var hasTrasferStarted = false private var isListening = false - private var range: DeviceTransferRange! + private var filter: DeviceTransferFilter! private let userID = myUserId @@ -38,9 +38,9 @@ class TransferToPhoneQRCodeViewController: UIViewController { } } - class func instance(range: DeviceTransferRange) -> UIViewController { + class func instance(filter: DeviceTransferFilter) -> UIViewController { let vc = TransferToPhoneQRCodeViewController() - vc.range = range + vc.filter = filter return ContainerViewController.instance(viewController: vc, title: R.string.localizable.waiting_for_other_device()) } @@ -61,7 +61,7 @@ extension TransferToPhoneQRCodeViewController { isListening = false observers.forEach { $0.cancel() } observers.removeAll() - let server = DeviceTransferServer(range: range) + let server = DeviceTransferServer(filter: filter) server.$state .receive(on: DispatchQueue.main) .sink { [weak self] state in diff --git a/Mixin/UserInterface/Controllers/DeviceTransfer/TransferToPhoneViewController.swift b/Mixin/UserInterface/Controllers/DeviceTransfer/TransferToPhoneViewController.swift index ee2f99df01..cc8a6079bb 100644 --- a/Mixin/UserInterface/Controllers/DeviceTransfer/TransferToPhoneViewController.swift +++ b/Mixin/UserInterface/Controllers/DeviceTransfer/TransferToPhoneViewController.swift @@ -3,19 +3,19 @@ import MixinServices class TransferToPhoneViewController: DeviceTransferSettingViewController { - private lazy var conversationRangeRow = SettingsRow(title: R.string.localizable.conversations(), - subtitle: DeviceTransferRange.Conversation.all.title, + private lazy var conversationFilterRow = SettingsRow(title: R.string.localizable.conversations(), + subtitle: DeviceTransferFilter.Conversation.all.title, accessory: .disclosure) - private lazy var dateRangeRow = SettingsRow(title: R.string.localizable.date(), - subtitle: DeviceTransferRange.Date.all.title, + private lazy var dateFilterRow = SettingsRow(title: R.string.localizable.date(), + subtitle: DeviceTransferFilter.Time.all.title, accessory: .disclosure) private lazy var dataSource = SettingsDataSource(sections: [ SettingsSection(rows: [SettingsRow(title: R.string.localizable.transfer_now(), titleStyle: .highlighted)]), - SettingsRadioSection(rows: [conversationRangeRow, dateRangeRow]) + SettingsRadioSection(rows: [conversationFilterRow, dateFilterRow]) ]) - private var range = DeviceTransferRange(conversation: .all, date: .all) + private var filter = DeviceTransferFilter(conversation: .all, time: .all) override func viewDidLoad() { super.viewDidLoad() @@ -44,15 +44,15 @@ extension TransferToPhoneViewController: UITableViewDelegate { alert(R.string.localizable.devices_on_same_network()) return } - controller = TransferToPhoneQRCodeViewController.instance(range: range) + controller = TransferToPhoneQRCodeViewController.instance(filter: filter) default: switch indexPath.row { case 0: - controller = DeviceTransferConversationSelectionViewController.instance(range: range.conversation, - rangeChanged: updateCoversationRangeRow(conversationRange:)) + controller = DeviceTransferConversationSelectionViewController.instance(filter: filter.conversation, + changeHandler: updateCoversationFilterRow(conversationFilter:)) default: - controller = DeviceTransferDateSelectionViewController.instance(range: range.date, - rangeChanged: updateDateRangeRow(dateRange:)) + controller = DeviceTransferDateSelectionViewController.instance(filter: filter.time, + changeHandler: updateDateFilterRow(timeFilter:)) } } navigationController?.pushViewController(controller, animated: true) @@ -62,14 +62,14 @@ extension TransferToPhoneViewController: UITableViewDelegate { extension TransferToPhoneViewController { - private func updateCoversationRangeRow(conversationRange: DeviceTransferRange.Conversation) { - range.conversation = conversationRange - conversationRangeRow.subtitle = conversationRange.title + private func updateCoversationFilterRow(conversationFilter: DeviceTransferFilter.Conversation) { + filter.conversation = conversationFilter + conversationFilterRow.subtitle = conversationFilter.title } - private func updateDateRangeRow(dateRange: DeviceTransferRange.Date) { - range.date = dateRange - dateRangeRow.subtitle = dateRange.title + private func updateDateFilterRow(timeFilter: DeviceTransferFilter.Time) { + filter.time = timeFilter + dateFilterRow.subtitle = timeFilter.title } } diff --git a/MixinServices/MixinServices/Database/User/DAO/ConversationDAO.swift b/MixinServices/MixinServices/Database/User/DAO/ConversationDAO.swift index 45026fb0e4..39060fca13 100644 --- a/MixinServices/MixinServices/Database/User/DAO/ConversationDAO.swift +++ b/MixinServices/MixinServices/Database/User/DAO/ConversationDAO.swift @@ -659,17 +659,26 @@ public final class ConversationDAO: UserDatabaseDAO { } } - public func conversations(limit: Int, after conversationId: String?) -> [Conversation] { + public func conversations(limit: Int, after conversationId: String?, matching conversationIDs: String?) -> [Conversation] { var sql = "SELECT * FROM conversations" - if let conversationId { + if let conversationIDs { + sql += " WHERE conversation_id in ('\(conversationIDs)')" + if let conversationId { + sql += " AND ROWID > IFNULL((SELECT ROWID FROM conversations WHERE conversation_id = '\(conversationId)'), 0)" + } + } else if let conversationId { sql += " WHERE ROWID > IFNULL((SELECT ROWID FROM conversations WHERE conversation_id = '\(conversationId)'), 0)" } sql += " ORDER BY ROWID LIMIT ?" return db.select(with: sql, arguments: [limit]) } - public func conversationsCount() -> Int { - let count: Int? = db.select(with: "SELECT COUNT(*) FROM conversations") + public func conversationsCount(matching conversationIDs: String?) -> Int { + var sql = "SELECT COUNT(*) FROM conversations" + if let conversationIDs { + sql += " WHERE conversation_id in ('\(conversationIDs)')" + } + let count: Int? = db.select(with: sql) return count ?? 0 } diff --git a/MixinServices/MixinServices/Database/User/DAO/MessageDAO.swift b/MixinServices/MixinServices/Database/User/DAO/MessageDAO.swift index 9611c9d115..1459f28303 100644 --- a/MixinServices/MixinServices/Database/User/DAO/MessageDAO.swift +++ b/MixinServices/MixinServices/Database/User/DAO/MessageDAO.swift @@ -980,17 +980,62 @@ extension MessageDAO { silentNotification: silentNotification) } - public func messages(limit: Int, after messageId: String?) -> [Message] { + public func messages(limit: Int, after messageId: String?, matching conversationIDs: String?, sinceDate date: String?) -> [Message] { var sql = "SELECT * FROM messages" - if let messageId { + if let conversationIDs { + sql += " WHERE conversation_id in ('\(conversationIDs)')" + if let date { + sql += " AND created_at >= '\(date)'" + } + if let messageId { + sql += " AND ROWID > IFNULL((SELECT ROWID FROM messages WHERE id = '\(messageId)'), 0)" + } + } else if let date { + sql += " WHERE created_at >= '\(date)'" + if let messageId { + sql += " AND ROWID > IFNULL((SELECT ROWID FROM messages WHERE id = '\(messageId)'), 0)" + } + } else if let messageId { sql += " WHERE ROWID > IFNULL((SELECT ROWID FROM messages WHERE id = '\(messageId)'), 0)" } sql += " ORDER BY ROWID LIMIT ?" return db.select(with: sql, arguments: [limit]) } - public func messagesCount() -> Int { - let count: Int? = db.select(with: "SELECT COUNT(*) FROM messages") + public func messagesCount(matching conversationIDs: String?, sinceDate date: String?) -> Int { + var sql = "SELECT COUNT(*) FROM messages" + if let conversationIDs { + sql += " WHERE conversation_id in ('\(conversationIDs)')" + if let date { + sql += " AND created_at >= '\(date)'" + } + } else if let date { + sql += " WHERE created_at >= '\(date)'" + } + let count: Int? = db.select(with: sql) + return count ?? 0 + } + + public func mediaMessagesCount(matching conversationIDs: String?) -> Int { + let categories = MessageCategory.allMediaCategories.map(\.rawValue).joined(separator: "', '") + var sql = "SELECT COUNT(*) FROM messages WHERE category in ('\(categories)')" + if let conversationIDs { + sql += " AND conversation_id in ('\(conversationIDs)')" + } + let count: Int? = db.select(with: sql) + return count ?? 0 + } + + public func transcriptMessageCount(matching conversationIDs: String?, sinceDate date: String?) -> Int { + let categories = MessageCategory.transcriptCategories.map(\.rawValue).joined(separator: "', '") + var sql = "SELECT COUNT(*) FROM messages WHERE category in ('\(categories)')" + if let conversationIDs { + sql += " AND conversation_id in ('\(conversationIDs)')" + } + if let date { + sql += " AND created_at >= '\(date)'" + } + let count: Int? = db.select(with: sql) return count ?? 0 } diff --git a/MixinServices/MixinServices/Database/User/DAO/MessageMentionDAO.swift b/MixinServices/MixinServices/Database/User/DAO/MessageMentionDAO.swift index 3b7540ab5f..b804e2fe41 100644 --- a/MixinServices/MixinServices/Database/User/DAO/MessageMentionDAO.swift +++ b/MixinServices/MixinServices/Database/User/DAO/MessageMentionDAO.swift @@ -15,17 +15,26 @@ public final class MessageMentionDAO: UserDatabaseDAO { return db.select(with: sql, arguments: [conversationId]) } - public func messageMentions(limit: Int, after messageId: String?) -> [MessageMention] { + public func messageMentions(limit: Int, after messageId: String?, matching conversationIDs: String?) -> [MessageMention] { var sql = "SELECT * FROM message_mentions" - if let messageId { + if let conversationIDs { + sql += " WHERE conversation_id in ('\(conversationIDs)')" + if let messageId { + sql += " AND ROWID > IFNULL((SELECT ROWID FROM message_mentions WHERE message_id = '\(messageId)'), 0)" + } + } else if let messageId { sql += " WHERE ROWID > IFNULL((SELECT ROWID FROM message_mentions WHERE message_id = '\(messageId)'), 0)" } sql += " ORDER BY ROWID LIMIT ?" return db.select(with: sql, arguments: [limit]) } - public func messageMentionsCount() -> Int { - let count: Int? = db.select(with: "SELECT COUNT(*) FROM message_mentions") + public func messageMentionsCount(matching conversationIDs: String?) -> Int { + var sql = "SELECT COUNT(*) FROM message_mentions" + if let conversationIDs { + sql += " WHERE conversation_id in ('\(conversationIDs)')" + } + let count: Int? = db.select(with: sql) return count ?? 0 } diff --git a/MixinServices/MixinServices/Database/User/DAO/ParticipantDAO.swift b/MixinServices/MixinServices/Database/User/DAO/ParticipantDAO.swift index 835a16c125..6dabb9e863 100644 --- a/MixinServices/MixinServices/Database/User/DAO/ParticipantDAO.swift +++ b/MixinServices/MixinServices/Database/User/DAO/ParticipantDAO.swift @@ -175,17 +175,26 @@ public final class ParticipantDAO: UserDatabaseDAO { .map({ ParticipantRequest(userId: $0.userId, role: $0.role) }) } - public func participants(limit: Int, after conversationId: String?, with userId: String?) -> [Participant] { + public func participants(limit: Int, after conversationId: String?, with userId: String?, matching conversationIDs: String?) -> [Participant] { var sql = "SELECT * FROM participants" - if let conversationId, let userId { + if let conversationIDs { + sql += " WHERE conversation_id in ('\(conversationIDs)')" + if let conversationId, let userId { + sql += " AND ROWID > IFNULL((SELECT ROWID FROM participants WHERE conversation_id = '\(conversationId)' AND user_id = '\(userId)'), 0)" + } + } else if let conversationId, let userId { sql += " WHERE ROWID > IFNULL((SELECT ROWID FROM participants WHERE conversation_id = '\(conversationId)' AND user_id = '\(userId)'), 0)" } sql += " ORDER BY ROWID LIMIT ?" return db.select(with: sql, arguments: [limit]) } - public func participantsCount() -> Int { - let count: Int? = db.select(with: "SELECT COUNT(*) FROM participants") + public func participantsCount(matching conversationIDs: String?) -> Int { + var sql = "SELECT COUNT(*) FROM participants" + if let conversationIDs { + sql += " WHERE conversation_id in ('\(conversationIDs)')" + } + let count: Int? = db.select(with: sql) return count ?? 0 } diff --git a/MixinServices/MixinServices/Database/User/DAO/PinMessageDAO.swift b/MixinServices/MixinServices/Database/User/DAO/PinMessageDAO.swift index 9809fdd072..900df98733 100644 --- a/MixinServices/MixinServices/Database/User/DAO/PinMessageDAO.swift +++ b/MixinServices/MixinServices/Database/User/DAO/PinMessageDAO.swift @@ -113,17 +113,39 @@ public final class PinMessageDAO: UserDatabaseDAO { where: PinMessage.column(of: .conversationId) == conversationId) } - public func pinMessages(limit: Int, after messageId: String?) -> [PinMessage] { + public func pinMessages(limit: Int, after messageId: String?, matching conversationIDs: String?, sinceDate date: String?) -> [PinMessage] { var sql = "SELECT * FROM pin_messages" - if let messageId { + if let conversationIDs { + sql += " WHERE conversation_id in ('\(conversationIDs)')" + if let date { + sql += " AND created_at >= '\(date)'" + } + if let messageId { + sql += " AND ROWID > IFNULL((SELECT ROWID FROM pin_messages WHERE message_id = '\(messageId)'), 0)" + } + } else if let date { + sql += " WHERE created_at >= '\(date)'" + if let messageId { + sql += " AND ROWID > IFNULL((SELECT ROWID FROM pin_messages WHERE message_id = '\(messageId)'), 0)" + } + } else if let messageId { sql += " WHERE ROWID > IFNULL((SELECT ROWID FROM pin_messages WHERE message_id = '\(messageId)'), 0)" } sql += " ORDER BY ROWID LIMIT ?" return db.select(with: sql, arguments: [limit]) } - public func pinMessagesCount() -> Int { - let count: Int? = db.select(with: "SELECT COUNT(*) FROM pin_messages") + public func pinMessagesCount(matching conversationIDs: String?, sinceDate date: String?) -> Int { + var sql = "SELECT COUNT(*) FROM pin_messages" + if let conversationIDs { + sql += " WHERE conversation_id in ('\(conversationIDs)')" + if let date { + sql += " AND created_at >= '\(date)'" + } + } else if let date { + sql += " WHERE created_at >= '\(date)'" + } + let count: Int? = db.select(with: sql) return count ?? 0 } diff --git a/MixinServices/MixinServices/Database/User/Model/Message.swift b/MixinServices/MixinServices/Database/User/Model/Message.swift index be9fef9fab..aaa408c6c0 100644 --- a/MixinServices/MixinServices/Database/User/Model/Message.swift +++ b/MixinServices/MixinServices/Database/User/Model/Message.swift @@ -370,6 +370,12 @@ public enum MessageCategory: String, Decodable { public static let allMediaCategoriesString: Set = Set(allMediaCategories.map(\.rawValue)) + public static let transcriptCategories: [MessageCategory] = [ + .PLAIN_TRANSCRIPT, + .SIGNAL_TRANSCRIPT, + .ENCRYPTED_TRANSCRIPT + ] + public static let endCallCategories: [MessageCategory] = [ .WEBRTC_AUDIO_END, .WEBRTC_AUDIO_BUSY, From 209555f0b8bd16f3abc1a0aca29d2b5946b8e9fb Mon Sep 17 00:00:00 2001 From: fanyu Date: Wed, 14 Jun 2023 14:12:37 +0800 Subject: [PATCH 05/28] Limit the number of parameters --- .../DeviceTransferServerDataSource.swift | 4 +- .../DeviceTransfer/DeviceTransferFilter.swift | 4 +- .../Database/User/DAO/ConversationDAO.swift | 45 +++++-- .../Database/User/DAO/MessageDAO.swift | 111 ++++++++++++------ .../Database/User/DAO/MessageMentionDAO.swift | 45 +++++-- .../Database/User/DAO/ParticipantDAO.swift | 45 +++++-- .../Database/User/DAO/PinMessageDAO.swift | 64 ++++++---- .../Database/User/UserDatabaseDAO.swift | 2 + 8 files changed, 220 insertions(+), 100 deletions(-) diff --git a/Mixin/Service/DeviceTransfer/DeviceTransferServerDataSource.swift b/Mixin/Service/DeviceTransfer/DeviceTransferServerDataSource.swift index 605b12b70b..ea4336edcd 100644 --- a/Mixin/Service/DeviceTransfer/DeviceTransferServerDataSource.swift +++ b/Mixin/Service/DeviceTransfer/DeviceTransferServerDataSource.swift @@ -9,7 +9,7 @@ final class DeviceTransferServerDataSource { private let key: DeviceTransferKey private let remotePlatform: DeviceTransferPlatform private let fileContentBuffer: UnsafeMutablePointer - private let conversationIDs: String? + private let conversationIDs: [String]? private let fromDate: String? private let needsFilterData: Bool @@ -17,7 +17,7 @@ final class DeviceTransferServerDataSource { self.key = key self.remotePlatform = remotePlatform self.fileContentBuffer = .allocate(capacity: fileChunkSize) - conversationIDs = filter.conversation.joinedIDs + conversationIDs = filter.conversation.ids fromDate = filter.time.utcString needsFilterData = conversationIDs != nil || fromDate != nil } diff --git a/Mixin/UserInterface/Controllers/DeviceTransfer/DeviceTransferFilter.swift b/Mixin/UserInterface/Controllers/DeviceTransfer/DeviceTransferFilter.swift index 471f64c082..8a644837ea 100644 --- a/Mixin/UserInterface/Controllers/DeviceTransfer/DeviceTransferFilter.swift +++ b/Mixin/UserInterface/Controllers/DeviceTransfer/DeviceTransferFilter.swift @@ -24,12 +24,12 @@ struct DeviceTransferFilter { } } - var joinedIDs: String? { + var ids: [String]? { switch self { case .all: return nil case .designated(let ids): - return ids.joined(separator: "', '") + return ids } } diff --git a/MixinServices/MixinServices/Database/User/DAO/ConversationDAO.swift b/MixinServices/MixinServices/Database/User/DAO/ConversationDAO.swift index 39060fca13..59157132a0 100644 --- a/MixinServices/MixinServices/Database/User/DAO/ConversationDAO.swift +++ b/MixinServices/MixinServices/Database/User/DAO/ConversationDAO.swift @@ -659,27 +659,46 @@ public final class ConversationDAO: UserDatabaseDAO { } } - public func conversations(limit: Int, after conversationId: String?, matching conversationIDs: String?) -> [Conversation] { - var sql = "SELECT * FROM conversations" + public func conversations(limit: Int, after conversationId: String?, matching conversationIDs: [String]?) -> [Conversation] { if let conversationIDs { - sql += " WHERE conversation_id in ('\(conversationIDs)')" + var totalConversations = [Conversation]() + for i in stride(from: 0, to: conversationIDs.count, by: Self.maxCountOfHostParameter) { + let endIndex = min(i + Self.maxCountOfHostParameter, conversationIDs.count) + let ids = Array(conversationIDs[i.. Int { - var sql = "SELECT COUNT(*) FROM conversations" + public func conversationsCount(matching conversationIDs: [String]?) -> Int { if let conversationIDs { - sql += " WHERE conversation_id in ('\(conversationIDs)')" + var totalCount = 0 + for i in stride(from: 0, to: conversationIDs.count, by: Self.maxCountOfHostParameter) { + let endIndex = min(i + Self.maxCountOfHostParameter, conversationIDs.count) + let ids = Array(conversationIDs[i.. [Message] { - var sql = "SELECT * FROM messages" + public func messages(limit: Int, after messageId: String?, matching conversationIDs: [String]?, sinceDate date: String?) -> [Message] { if let conversationIDs { - sql += " WHERE conversation_id in ('\(conversationIDs)')" - if let date { - sql += " AND created_at >= '\(date)'" + var totalMessages = [Message]() + for i in stride(from: 0, to: conversationIDs.count, by: Self.maxCountOfHostParameter) { + let endIndex = min(i + Self.maxCountOfHostParameter, conversationIDs.count) + let ids = Array(conversationIDs[i.. Int { - var sql = "SELECT COUNT(*) FROM messages" + public func messagesCount(matching conversationIDs: [String]?, sinceDate date: String?) -> Int { if let conversationIDs { - sql += " WHERE conversation_id in ('\(conversationIDs)')" + var totalCount = 0 + for i in stride(from: 0, to: conversationIDs.count, by: Self.maxCountOfHostParameter) { + let endIndex = min(i + Self.maxCountOfHostParameter, conversationIDs.count) + let ids = Array(conversationIDs[i.. Int { + public func mediaMessagesCount(matching conversationIDs: [String]?) -> Int { let categories = MessageCategory.allMediaCategories.map(\.rawValue).joined(separator: "', '") - var sql = "SELECT COUNT(*) FROM messages WHERE category in ('\(categories)')" if let conversationIDs { - sql += " AND conversation_id in ('\(conversationIDs)')" + var totalCount = 0 + for i in stride(from: 0, to: conversationIDs.count, by: Self.maxCountOfHostParameter) { + let endIndex = min(i + Self.maxCountOfHostParameter, conversationIDs.count) + let ids = Array(conversationIDs[i.. Int { + public func transcriptMessageCount(matching conversationIDs: [String]?, sinceDate date: String?) -> Int { let categories = MessageCategory.transcriptCategories.map(\.rawValue).joined(separator: "', '") - var sql = "SELECT COUNT(*) FROM messages WHERE category in ('\(categories)')" if let conversationIDs { - sql += " AND conversation_id in ('\(conversationIDs)')" - } - if let date { - sql += " AND created_at >= '\(date)'" + var totalCount = 0 + for i in stride(from: 0, to: conversationIDs.count, by: Self.maxCountOfHostParameter) { + let endIndex = min(i + Self.maxCountOfHostParameter, conversationIDs.count) + let ids = Array(conversationIDs[i.. String? { diff --git a/MixinServices/MixinServices/Database/User/DAO/MessageMentionDAO.swift b/MixinServices/MixinServices/Database/User/DAO/MessageMentionDAO.swift index b804e2fe41..de662e542d 100644 --- a/MixinServices/MixinServices/Database/User/DAO/MessageMentionDAO.swift +++ b/MixinServices/MixinServices/Database/User/DAO/MessageMentionDAO.swift @@ -15,27 +15,46 @@ public final class MessageMentionDAO: UserDatabaseDAO { return db.select(with: sql, arguments: [conversationId]) } - public func messageMentions(limit: Int, after messageId: String?, matching conversationIDs: String?) -> [MessageMention] { - var sql = "SELECT * FROM message_mentions" + public func messageMentions(limit: Int, after messageId: String?, matching conversationIDs: [String]?) -> [MessageMention] { if let conversationIDs { - sql += " WHERE conversation_id in ('\(conversationIDs)')" + var totalMessageMentions = [MessageMention]() + for i in stride(from: 0, to: conversationIDs.count, by: Self.maxCountOfHostParameter) { + let endIndex = min(i + Self.maxCountOfHostParameter, conversationIDs.count) + let ids = Array(conversationIDs[i.. Int { - var sql = "SELECT COUNT(*) FROM message_mentions" + public func messageMentionsCount(matching conversationIDs: [String]?) -> Int { if let conversationIDs { - sql += " WHERE conversation_id in ('\(conversationIDs)')" + var totalCount = 0 + for i in stride(from: 0, to: conversationIDs.count, by: Self.maxCountOfHostParameter) { + let endIndex = min(i + Self.maxCountOfHostParameter, conversationIDs.count) + let ids = Array(conversationIDs[i.. [Participant] { - var sql = "SELECT * FROM participants" + public func participants(limit: Int, after conversationId: String?, with userId: String?, matching conversationIDs: [String]?) -> [Participant] { if let conversationIDs { - sql += " WHERE conversation_id in ('\(conversationIDs)')" + var totalParticipants = [Participant]() + for i in stride(from: 0, to: conversationIDs.count, by: Self.maxCountOfHostParameter) { + let endIndex = min(i + Self.maxCountOfHostParameter, conversationIDs.count) + let ids = Array(conversationIDs[i.. Int { - var sql = "SELECT COUNT(*) FROM participants" + public func participantsCount(matching conversationIDs: [String]?) -> Int { if let conversationIDs { - sql += " WHERE conversation_id in ('\(conversationIDs)')" + var totalCount = 0 + for i in stride(from: 0, to: conversationIDs.count, by: Self.maxCountOfHostParameter) { + let endIndex = min(i + Self.maxCountOfHostParameter, conversationIDs.count) + let ids = Array(conversationIDs[i.. [PinMessage] { - var sql = "SELECT * FROM pin_messages" + public func pinMessages(limit: Int, after messageId: String?, matching conversationIDs: [String]?, sinceDate date: String?) -> [PinMessage] { if let conversationIDs { - sql += " WHERE conversation_id in ('\(conversationIDs)')" - if let date { - sql += " AND created_at >= '\(date)'" + var totalPinMessages = [PinMessage]() + for i in stride(from: 0, to: conversationIDs.count, by: Self.maxCountOfHostParameter) { + let endIndex = min(i + Self.maxCountOfHostParameter, conversationIDs.count) + let ids = Array(conversationIDs[i.. Int { - var sql = "SELECT COUNT(*) FROM pin_messages" + public func pinMessagesCount(matching conversationIDs: [String]?, sinceDate date: String?) -> Int { if let conversationIDs { - sql += " WHERE conversation_id in ('\(conversationIDs)')" + var totalCount = 0 + for i in stride(from: 0, to: conversationIDs.count, by: Self.maxCountOfHostParameter) { + let endIndex = min(i + Self.maxCountOfHostParameter, conversationIDs.count) + let ids = Array(conversationIDs[i.. Date: Wed, 14 Jun 2023 15:37:16 +0800 Subject: [PATCH 06/28] Log transcript message count --- .../DeviceTransferServerDataSource.swift | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/Mixin/Service/DeviceTransfer/DeviceTransferServerDataSource.swift b/Mixin/Service/DeviceTransfer/DeviceTransferServerDataSource.swift index ea4336edcd..969009e388 100644 --- a/Mixin/Service/DeviceTransfer/DeviceTransferServerDataSource.swift +++ b/Mixin/Service/DeviceTransfer/DeviceTransferServerDataSource.swift @@ -13,6 +13,8 @@ final class DeviceTransferServerDataSource { private let fromDate: String? private let needsFilterData: Bool + private var transcriptMessageCount = 0 + init(key: DeviceTransferKey, remotePlatform: DeviceTransferPlatform, filter: DeviceTransferFilter) { self.key = key self.remotePlatform = remotePlatform @@ -53,7 +55,7 @@ extension DeviceTransferServerDataSource { + MessageMentionDAO.shared.messageMentionsCount(matching: conversationIDs) + ExpiredMessageDAO.shared.expiredMessagesCount() + attachmentsCount - Logger.general.info(category: "DeviceTransferServerDataSource", message: "Total: \(total), Messages: \(messagesCount), attachments: \(attachmentsCount), transcriptMessageCount: \(transcriptMessageCount)") + Logger.general.info(category: "DeviceTransferServerDataSource", message: "Total: \(total), Messages: \(messagesCount), Attachments: \(attachmentsCount), TranscriptMessages: \(transcriptMessageCount)") return total } @@ -129,7 +131,7 @@ extension DeviceTransferServerDataSource { var fileCount = 0 while let location = nextLocation { let (databaseItemCount, transferItems, nextPrimaryID, nextSecondaryID) = items(on: location) - if transferItems.isEmpty { + if transferItems.isEmpty && !(needsFilterData && location.type == .transcriptMessage) { Logger.general.info(category: "DeviceTransferServerDataSource", message: "\(location.type) is empty") } recordCount += transferItems.count @@ -151,7 +153,18 @@ extension DeviceTransferServerDataSource { } else { nextLocation = nil } - Logger.general.info(category: "DeviceTransferServerDataSource", message: "Send \(location.type) \(recordCount)") + if needsFilterData, location.type == .message { + let message: String + if transcriptMessageCount == 0 { + message = "\(DeviceTransferRecordType.transcriptMessage) is empty" + } else { + message = "Send \(DeviceTransferRecordType.transcriptMessage) \(transcriptMessageCount)" + } + Logger.general.info(category: "DeviceTransferServerDataSource", message: message) + } + if !needsFilterData || location.type != .transcriptMessage { + Logger.general.info(category: "DeviceTransferServerDataSource", message: "Send \(location.type) \(recordCount)") + } recordCount = 0 } else { nextLocation = Location(type: location.type, primaryID: nextPrimaryID, secondaryID: nextSecondaryID) @@ -317,7 +330,6 @@ extension DeviceTransferServerDataSource { nextSecondaryID = nil var messageItems = [TransferItem]() var transcriptMessageItems = [TransferItem]() - var transcriptMessageCount = 0 for message in messages { let deviceTransferMessage = DeviceTransferMessage(message: message, to: remotePlatform) do { @@ -342,9 +354,6 @@ extension DeviceTransferServerDataSource { transcriptMessageItems = transcriptTransferItems(for: transcriptMessages) } } - if transcriptMessageCount != 0 { - Logger.general.info(category: "DeviceTransferServerDataSource", message: "Send transcriptMessages along with messages: \(transcriptMessageCount)") - } transferItems = transcriptMessageItems + messageItems case .messageMention: let messageMentions = MessageMentionDAO.shared.messageMentions(limit: limit, From 8f2ad1750a658de2cbeab84f241031ee5e305fad Mon Sep 17 00:00:00 2001 From: fanyu Date: Wed, 14 Jun 2023 17:28:38 +0800 Subject: [PATCH 07/28] Filter messages with rowid --- .../DeviceTransferServerDataSource.swift | 63 ++++++++++++++----- .../Database/User/DAO/ConversationDAO.swift | 8 +-- .../Database/User/DAO/MessageDAO.swift | 49 +++++++-------- .../Database/User/DAO/MessageMentionDAO.swift | 8 +-- .../Database/User/DAO/ParticipantDAO.swift | 8 +-- .../Database/User/DAO/PinMessageDAO.swift | 41 +++++------- .../Database/User/UserDatabaseDAO.swift | 9 ++- 7 files changed, 106 insertions(+), 80 deletions(-) diff --git a/Mixin/Service/DeviceTransfer/DeviceTransferServerDataSource.swift b/Mixin/Service/DeviceTransfer/DeviceTransferServerDataSource.swift index 969009e388..c6962ac6bc 100644 --- a/Mixin/Service/DeviceTransfer/DeviceTransferServerDataSource.swift +++ b/Mixin/Service/DeviceTransfer/DeviceTransferServerDataSource.swift @@ -290,14 +290,32 @@ extension DeviceTransferServerDataSource { } } case .pinMessage: - let pinMessages = PinMessageDAO.shared.pinMessages(limit: limit, - after: location.primaryID, - matching: conversationIDs, - sinceDate: fromDate) + let rowID: Int + if let primaryID = location.primaryID?.intValue { + rowID = primaryID + } else { + if let fromDate { + if let startRowID = PinMessageDAO.shared.messageRowID(createdAt: fromDate) { + rowID = startRowID + } else { + return (0, [], nil, nil) + } + } else { + rowID = -1 + } + } + let pinMessages = PinMessageDAO.shared.pinMessages(limit: limit, after: rowID, matching: conversationIDs) databaseItemCount = pinMessages.count - nextPrimaryID = pinMessages.last?.messageId + if let messageID = pinMessages.last?.messageId, let rowID = PinMessageDAO.shared.messageRowID(messageID: messageID) { + nextPrimaryID = "\(rowID)" + } else { + nextPrimaryID = nil + } nextSecondaryID = nil transferItems = pinMessages.compactMap { pinMessage in + if let fromDate, pinMessage.createdAt < fromDate { + return nil + } let deviceTransferPinMessage = DeviceTransferPinMessage(pinMessage: pinMessage) do { let outputData = try DeviceTransferProtocol.output(type: location.type, data: deviceTransferPinMessage, key: key) @@ -309,10 +327,7 @@ extension DeviceTransferServerDataSource { } case .transcriptMessage: if needsFilterData { - databaseItemCount = 0 - nextPrimaryID = nil - nextSecondaryID = nil - transferItems = [] + return (0, [], nil, nil) } else { let transcriptMessages = TranscriptMessageDAO.shared.transcriptMessages(limit: limit, after: location.primaryID, with: location.secondaryID) databaseItemCount = transcriptMessages.count @@ -321,16 +336,34 @@ extension DeviceTransferServerDataSource { transferItems = transcriptTransferItems(for: transcriptMessages) } case .message: - let messages = MessageDAO.shared.messages(limit: limit, - after: location.primaryID, - matching: conversationIDs, - sinceDate: fromDate) + let rowID: Int + if let primaryID = location.primaryID?.intValue { + rowID = primaryID + } else { + if let fromDate { + if let startRowID = MessageDAO.shared.messageRowID(createdAt: fromDate) { + rowID = startRowID + } else { + return (0, [], nil, nil) + } + } else { + rowID = -1 + } + } + let messages = MessageDAO.shared.messages(limit: limit, after: rowID, matching: conversationIDs) databaseItemCount = messages.count - nextPrimaryID = messages.last?.messageId + if let messageID = messages.last?.messageId, let rowID = MessageDAO.shared.messageRowID(messageID: messageID) { + nextPrimaryID = "\(rowID)" + } else { + nextPrimaryID = nil + } nextSecondaryID = nil var messageItems = [TransferItem]() var transcriptMessageItems = [TransferItem]() for message in messages { + if let fromDate, message.createdAt < fromDate { + continue + } let deviceTransferMessage = DeviceTransferMessage(message: message, to: remotePlatform) do { let outputData = try DeviceTransferProtocol.output(type: location.type, data: deviceTransferMessage, key: key) @@ -412,7 +445,7 @@ extension DeviceTransferServerDataSource { return false } #if DEBUG - Logger.general.debug(category: "DeviceTransferServerDataSource", message: "Send File: \(url)") + //Logger.general.debug(category: "DeviceTransferServerDataSource", message: "Send File: \(url)") #endif let encryptor = try AESCryptor(operation: .encrypt, iv: iv, key: key.aes) diff --git a/MixinServices/MixinServices/Database/User/DAO/ConversationDAO.swift b/MixinServices/MixinServices/Database/User/DAO/ConversationDAO.swift index 59157132a0..447f4cbf91 100644 --- a/MixinServices/MixinServices/Database/User/DAO/ConversationDAO.swift +++ b/MixinServices/MixinServices/Database/User/DAO/ConversationDAO.swift @@ -662,8 +662,8 @@ public final class ConversationDAO: UserDatabaseDAO { public func conversations(limit: Int, after conversationId: String?, matching conversationIDs: [String]?) -> [Conversation] { if let conversationIDs { var totalConversations = [Conversation]() - for i in stride(from: 0, to: conversationIDs.count, by: Self.maxCountOfHostParameter) { - let endIndex = min(i + Self.maxCountOfHostParameter, conversationIDs.count) + for i in stride(from: 0, to: conversationIDs.count, by: Self.strideForDeviceTransfer) { + let endIndex = min(i + Self.strideForDeviceTransfer, conversationIDs.count) let ids = Array(conversationIDs[i.. Int { if let conversationIDs { var totalCount = 0 - for i in stride(from: 0, to: conversationIDs.count, by: Self.maxCountOfHostParameter) { - let endIndex = min(i + Self.maxCountOfHostParameter, conversationIDs.count) + for i in stride(from: 0, to: conversationIDs.count, by: Self.strideForDeviceTransfer) { + let endIndex = min(i + Self.strideForDeviceTransfer, conversationIDs.count) let ids = Array(conversationIDs[i.. [Message] { + public func messages(limit: Int, after rowID: Int, matching conversationIDs: [String]?) -> [Message] { if let conversationIDs { var totalMessages = [Message]() - for i in stride(from: 0, to: conversationIDs.count, by: Self.maxCountOfHostParameter) { - let endIndex = min(i + Self.maxCountOfHostParameter, conversationIDs.count) + for i in stride(from: 0, to: conversationIDs.count, by: Self.strideForDeviceTransfer) { + let endIndex = min(i + Self.strideForDeviceTransfer, conversationIDs.count) let ids = Array(conversationIDs[i.. Int? { + db.select(with: "SELECT ROWID FROM messages WHERE created_at >= ? LIMIT 1", arguments: [createdAt]) + } + + public func messageRowID(messageID: String) -> Int? { + db.select(with: "SELECT ROWID FROM messages WHERE id = ?", arguments: [messageID]) + } + public func messagesCount(matching conversationIDs: [String]?, sinceDate date: String?) -> Int { if let conversationIDs { var totalCount = 0 - for i in stride(from: 0, to: conversationIDs.count, by: Self.maxCountOfHostParameter) { - let endIndex = min(i + Self.maxCountOfHostParameter, conversationIDs.count) + for i in stride(from: 0, to: conversationIDs.count, by: Self.strideForDeviceTransfer) { + let endIndex = min(i + Self.strideForDeviceTransfer, conversationIDs.count) let ids = Array(conversationIDs[i.. [MessageMention] { if let conversationIDs { var totalMessageMentions = [MessageMention]() - for i in stride(from: 0, to: conversationIDs.count, by: Self.maxCountOfHostParameter) { - let endIndex = min(i + Self.maxCountOfHostParameter, conversationIDs.count) + for i in stride(from: 0, to: conversationIDs.count, by: Self.strideForDeviceTransfer) { + let endIndex = min(i + Self.strideForDeviceTransfer, conversationIDs.count) let ids = Array(conversationIDs[i.. Int { if let conversationIDs { var totalCount = 0 - for i in stride(from: 0, to: conversationIDs.count, by: Self.maxCountOfHostParameter) { - let endIndex = min(i + Self.maxCountOfHostParameter, conversationIDs.count) + for i in stride(from: 0, to: conversationIDs.count, by: Self.strideForDeviceTransfer) { + let endIndex = min(i + Self.strideForDeviceTransfer, conversationIDs.count) let ids = Array(conversationIDs[i.. [Participant] { if let conversationIDs { var totalParticipants = [Participant]() - for i in stride(from: 0, to: conversationIDs.count, by: Self.maxCountOfHostParameter) { - let endIndex = min(i + Self.maxCountOfHostParameter, conversationIDs.count) + for i in stride(from: 0, to: conversationIDs.count, by: Self.strideForDeviceTransfer) { + let endIndex = min(i + Self.strideForDeviceTransfer, conversationIDs.count) let ids = Array(conversationIDs[i.. Int { if let conversationIDs { var totalCount = 0 - for i in stride(from: 0, to: conversationIDs.count, by: Self.maxCountOfHostParameter) { - let endIndex = min(i + Self.maxCountOfHostParameter, conversationIDs.count) + for i in stride(from: 0, to: conversationIDs.count, by: Self.strideForDeviceTransfer) { + let endIndex = min(i + Self.strideForDeviceTransfer, conversationIDs.count) let ids = Array(conversationIDs[i.. [PinMessage] { + public func pinMessages(limit: Int, after rowID: Int, matching conversationIDs: [String]?) -> [PinMessage] { if let conversationIDs { var totalPinMessages = [PinMessage]() - for i in stride(from: 0, to: conversationIDs.count, by: Self.maxCountOfHostParameter) { - let endIndex = min(i + Self.maxCountOfHostParameter, conversationIDs.count) + for i in stride(from: 0, to: conversationIDs.count, by: Self.strideForDeviceTransfer) { + let endIndex = min(i + Self.strideForDeviceTransfer, conversationIDs.count) let ids = Array(conversationIDs[i.. Int { if let conversationIDs { var totalCount = 0 - for i in stride(from: 0, to: conversationIDs.count, by: Self.maxCountOfHostParameter) { - let endIndex = min(i + Self.maxCountOfHostParameter, conversationIDs.count) + for i in stride(from: 0, to: conversationIDs.count, by: Self.strideForDeviceTransfer) { + let endIndex = min(i + Self.strideForDeviceTransfer, conversationIDs.count) let ids = Array(conversationIDs[i.. Int? { + db.select(with: "SELECT ROWID FROM pin_messages WHERE created_at >= ? LIMIT 1", arguments: [createdAt]) + } + + public func messageRowID(messageID: String) -> Int? { + db.select(with: "SELECT ROWID FROM pin_messages WHERE message_id = ?", arguments: [messageID]) + } + public func save(pinMessage: PinMessage) { db.save(pinMessage) } diff --git a/MixinServices/MixinServices/Database/User/UserDatabaseDAO.swift b/MixinServices/MixinServices/Database/User/UserDatabaseDAO.swift index 8f7dfecdc3..b73fbc85dd 100644 --- a/MixinServices/MixinServices/Database/User/UserDatabaseDAO.swift +++ b/MixinServices/MixinServices/Database/User/UserDatabaseDAO.swift @@ -6,6 +6,13 @@ public class UserDatabaseDAO { UserDatabase.current } - public static let maxCountOfHostParameter = 999 +} + +extension UserDatabaseDAO { + + // To prevent excessive memory allocations, + // the maximum value of a host parameter number is SQLITE_MAX_VARIABLE_NUMBER, + // which defaults to 999 for SQLite versions prior to 3.32.0 (2020-05-22) or 32766 for SQLite versions after 3.32.0. + public static let strideForDeviceTransfer = 900 } From b95aae36305df41788b4b7e0d0b01fb90cd2b439 Mon Sep 17 00:00:00 2001 From: fanyu Date: Wed, 14 Jun 2023 21:12:16 +0800 Subject: [PATCH 08/28] Update query --- .../DeviceTransferServerDataSource.swift | 13 +-- .../Database/User/DAO/ConversationDAO.swift | 46 +++------- .../Database/User/DAO/MessageDAO.swift | 90 ++++++------------- .../Database/User/DAO/MessageMentionDAO.swift | 46 +++------- .../Database/User/DAO/ParticipantDAO.swift | 48 ++++------ .../Database/User/DAO/PinMessageDAO.swift | 45 +++------- .../Database/User/UserDatabaseDAO.swift | 9 -- 7 files changed, 88 insertions(+), 209 deletions(-) diff --git a/Mixin/Service/DeviceTransfer/DeviceTransferServerDataSource.swift b/Mixin/Service/DeviceTransfer/DeviceTransferServerDataSource.swift index c6962ac6bc..c6d89beecb 100644 --- a/Mixin/Service/DeviceTransfer/DeviceTransferServerDataSource.swift +++ b/Mixin/Service/DeviceTransfer/DeviceTransferServerDataSource.swift @@ -131,7 +131,7 @@ extension DeviceTransferServerDataSource { var fileCount = 0 while let location = nextLocation { let (databaseItemCount, transferItems, nextPrimaryID, nextSecondaryID) = items(on: location) - if transferItems.isEmpty && !(needsFilterData && location.type == .transcriptMessage) { + if transferItems.isEmpty { Logger.general.info(category: "DeviceTransferServerDataSource", message: "\(location.type) is empty") } recordCount += transferItems.count @@ -154,13 +154,8 @@ extension DeviceTransferServerDataSource { nextLocation = nil } if needsFilterData, location.type == .message { - let message: String - if transcriptMessageCount == 0 { - message = "\(DeviceTransferRecordType.transcriptMessage) is empty" - } else { - message = "Send \(DeviceTransferRecordType.transcriptMessage) \(transcriptMessageCount)" - } - Logger.general.info(category: "DeviceTransferServerDataSource", message: message) + recordCount -= transcriptMessageCount + Logger.general.info(category: "DeviceTransferServerDataSource", message: "Send \(DeviceTransferRecordType.transcriptMessage) \(transcriptMessageCount)") } if !needsFilterData || location.type != .transcriptMessage { Logger.general.info(category: "DeviceTransferServerDataSource", message: "Send \(location.type) \(recordCount)") @@ -445,7 +440,7 @@ extension DeviceTransferServerDataSource { return false } #if DEBUG - //Logger.general.debug(category: "DeviceTransferServerDataSource", message: "Send File: \(url)") + Logger.general.debug(category: "DeviceTransferServerDataSource", message: "Send File: \(url)") #endif let encryptor = try AESCryptor(operation: .encrypt, iv: iv, key: key.aes) diff --git a/MixinServices/MixinServices/Database/User/DAO/ConversationDAO.swift b/MixinServices/MixinServices/Database/User/DAO/ConversationDAO.swift index 447f4cbf91..c67f75e17b 100644 --- a/MixinServices/MixinServices/Database/User/DAO/ConversationDAO.swift +++ b/MixinServices/MixinServices/Database/User/DAO/ConversationDAO.swift @@ -660,45 +660,27 @@ public final class ConversationDAO: UserDatabaseDAO { } public func conversations(limit: Int, after conversationId: String?, matching conversationIDs: [String]?) -> [Conversation] { + var sql = "SELECT * FROM conversations" if let conversationIDs { - var totalConversations = [Conversation]() - for i in stride(from: 0, to: conversationIDs.count, by: Self.strideForDeviceTransfer) { - let endIndex = min(i + Self.strideForDeviceTransfer, conversationIDs.count) - let ids = Array(conversationIDs[i.. Int { + var sql = "SELECT COUNT(*) FROM conversations" if let conversationIDs { - var totalCount = 0 - for i in stride(from: 0, to: conversationIDs.count, by: Self.strideForDeviceTransfer) { - let endIndex = min(i + Self.strideForDeviceTransfer, conversationIDs.count) - let ids = Array(conversationIDs[i.. [Message] { + var sql = "SELECT * FROM messages WHERE ROWID > ?" if let conversationIDs { - var totalMessages = [Message]() - for i in stride(from: 0, to: conversationIDs.count, by: Self.strideForDeviceTransfer) { - let endIndex = min(i + Self.strideForDeviceTransfer, conversationIDs.count) - let ids = Array(conversationIDs[i.. Int? { @@ -1006,71 +999,42 @@ extension MessageDAO { } public func messagesCount(matching conversationIDs: [String]?, sinceDate date: String?) -> Int { + var sql = "SELECT COUNT(*) FROM messages" + if let date { + sql += " WHERE created_at >= '\(date)'" + } if let conversationIDs { - var totalCount = 0 - for i in stride(from: 0, to: conversationIDs.count, by: Self.strideForDeviceTransfer) { - let endIndex = min(i + Self.strideForDeviceTransfer, conversationIDs.count) - let ids = Array(conversationIDs[i.. Int { let categories = MessageCategory.allMediaCategories.map(\.rawValue).joined(separator: "', '") + var sql = "SELECT COUNT(*) FROM messages WHERE category IN ('\(categories)') AND media_status IN ('DONE', 'READ')" if let conversationIDs { - var totalCount = 0 - for i in stride(from: 0, to: conversationIDs.count, by: Self.strideForDeviceTransfer) { - let endIndex = min(i + Self.strideForDeviceTransfer, conversationIDs.count) - let ids = Array(conversationIDs[i.. Int { let categories = MessageCategory.transcriptCategories.map(\.rawValue).joined(separator: "', '") + var sql = "SELECT COUNT(*) FROM messages WHERE category IN ('\(categories)')" + if let date { + sql += " AND created_at >= '\(date)'" + } if let conversationIDs { - var totalCount = 0 - for i in stride(from: 0, to: conversationIDs.count, by: Self.strideForDeviceTransfer) { - let endIndex = min(i + Self.strideForDeviceTransfer, conversationIDs.count) - let ids = Array(conversationIDs[i.. String? { diff --git a/MixinServices/MixinServices/Database/User/DAO/MessageMentionDAO.swift b/MixinServices/MixinServices/Database/User/DAO/MessageMentionDAO.swift index c20b1ec850..39ba07d4b5 100644 --- a/MixinServices/MixinServices/Database/User/DAO/MessageMentionDAO.swift +++ b/MixinServices/MixinServices/Database/User/DAO/MessageMentionDAO.swift @@ -16,45 +16,27 @@ public final class MessageMentionDAO: UserDatabaseDAO { } public func messageMentions(limit: Int, after messageId: String?, matching conversationIDs: [String]?) -> [MessageMention] { + var sql = "SELECT * FROM message_mentions" if let conversationIDs { - var totalMessageMentions = [MessageMention]() - for i in stride(from: 0, to: conversationIDs.count, by: Self.strideForDeviceTransfer) { - let endIndex = min(i + Self.strideForDeviceTransfer, conversationIDs.count) - let ids = Array(conversationIDs[i.. Int { + var sql = "SELECT COUNT(*) FROM message_mentions" if let conversationIDs { - var totalCount = 0 - for i in stride(from: 0, to: conversationIDs.count, by: Self.strideForDeviceTransfer) { - let endIndex = min(i + Self.strideForDeviceTransfer, conversationIDs.count) - let ids = Array(conversationIDs[i.. [Participant] { + public func participants(limit: Int, after conversationId: String?, with userId: String?, matching conversationIDs: [String]?) -> [Participant] { + var sql = "SELECT * FROM participants" if let conversationIDs { - var totalParticipants = [Participant]() - for i in stride(from: 0, to: conversationIDs.count, by: Self.strideForDeviceTransfer) { - let endIndex = min(i + Self.strideForDeviceTransfer, conversationIDs.count) - let ids = Array(conversationIDs[i.. Int { + var sql = "SELECT COUNT(*) FROM participants" if let conversationIDs { - var totalCount = 0 - for i in stride(from: 0, to: conversationIDs.count, by: Self.strideForDeviceTransfer) { - let endIndex = min(i + Self.strideForDeviceTransfer, conversationIDs.count) - let ids = Array(conversationIDs[i.. [PinMessage] { + var sql = "SELECT * FROM pin_messages WHERE ROWID > ?" if let conversationIDs { - var totalPinMessages = [PinMessage]() - for i in stride(from: 0, to: conversationIDs.count, by: Self.strideForDeviceTransfer) { - let endIndex = min(i + Self.strideForDeviceTransfer, conversationIDs.count) - let ids = Array(conversationIDs[i.. Int { + var sql = "SELECT COUNT(*) FROM pin_messages" if let conversationIDs { - var totalCount = 0 - for i in stride(from: 0, to: conversationIDs.count, by: Self.strideForDeviceTransfer) { - let endIndex = min(i + Self.strideForDeviceTransfer, conversationIDs.count) - let ids = Array(conversationIDs[i.. Int? { diff --git a/MixinServices/MixinServices/Database/User/UserDatabaseDAO.swift b/MixinServices/MixinServices/Database/User/UserDatabaseDAO.swift index b73fbc85dd..0aa6e30f71 100644 --- a/MixinServices/MixinServices/Database/User/UserDatabaseDAO.swift +++ b/MixinServices/MixinServices/Database/User/UserDatabaseDAO.swift @@ -7,12 +7,3 @@ public class UserDatabaseDAO { } } - -extension UserDatabaseDAO { - - // To prevent excessive memory allocations, - // the maximum value of a host parameter number is SQLITE_MAX_VARIABLE_NUMBER, - // which defaults to 999 for SQLite versions prior to 3.32.0 (2020-05-22) or 32766 for SQLite versions after 3.32.0. - public static let strideForDeviceTransfer = 900 - -} From 0428089ed90040860c29bda6b922ae03d4aa61ed Mon Sep 17 00:00:00 2001 From: fanyu Date: Thu, 15 Jun 2023 13:10:48 +0800 Subject: [PATCH 09/28] Update query --- .../DeviceTransferServerDataSource.swift | 18 ++++++++++++------ .../RestoreFromCloudViewController.swift | 2 +- .../Database/User/DAO/MessageDAO.swift | 19 +++++++++++-------- .../Database/User/DAO/PinMessageDAO.swift | 12 ++++++------ 4 files changed, 30 insertions(+), 21 deletions(-) diff --git a/Mixin/Service/DeviceTransfer/DeviceTransferServerDataSource.swift b/Mixin/Service/DeviceTransfer/DeviceTransferServerDataSource.swift index c6d89beecb..d67fbc1e8e 100644 --- a/Mixin/Service/DeviceTransfer/DeviceTransferServerDataSource.swift +++ b/Mixin/Service/DeviceTransfer/DeviceTransferServerDataSource.swift @@ -35,12 +35,18 @@ extension DeviceTransferServerDataSource { func totalCount() -> Int { assert(!Queue.main.isCurrent) - let messagesCount = MessageDAO.shared.messagesCount(matching: conversationIDs, sinceDate: fromDate) + let rowID: Int? + if let fromDate { + rowID = MessageDAO.shared.messageRowID(createdAt: fromDate) + } else { + rowID = nil + } + let messagesCount = MessageDAO.shared.messagesCount(matching: conversationIDs, after: rowID) let attachmentsCount = needsFilterData - ? MessageDAO.shared.mediaMessagesCount(matching: conversationIDs) + ? MessageDAO.shared.mediaMessagesCount(matching: conversationIDs, after: rowID) : attachmentsCount() let transcriptMessageCount = needsFilterData - ? MessageDAO.shared.transcriptMessageCount(matching: conversationIDs, sinceDate: fromDate) + ? MessageDAO.shared.transcriptMessageCount(matching: conversationIDs, after: rowID) : TranscriptMessageDAO.shared.transcriptMessagesCount() let total = ConversationDAO.shared.conversationsCount(matching: conversationIDs) + ParticipantDAO.shared.participantsCount(matching: conversationIDs) @@ -49,7 +55,7 @@ extension DeviceTransferServerDataSource { + AssetDAO.shared.assetsCount() + SnapshotDAO.shared.snapshotsCount() + StickerDAO.shared.stickersCount() - + PinMessageDAO.shared.pinMessagesCount(matching: conversationIDs, sinceDate: fromDate) + + PinMessageDAO.shared.pinMessagesCount(matching: conversationIDs, after: rowID) + transcriptMessageCount + messagesCount + MessageMentionDAO.shared.messageMentionsCount(matching: conversationIDs) @@ -291,7 +297,7 @@ extension DeviceTransferServerDataSource { } else { if let fromDate { if let startRowID = PinMessageDAO.shared.messageRowID(createdAt: fromDate) { - rowID = startRowID + rowID = startRowID - 1 } else { return (0, [], nil, nil) } @@ -337,7 +343,7 @@ extension DeviceTransferServerDataSource { } else { if let fromDate { if let startRowID = MessageDAO.shared.messageRowID(createdAt: fromDate) { - rowID = startRowID + rowID = startRowID - 1 } else { return (0, [], nil, nil) } diff --git a/Mixin/UserInterface/Controllers/DeviceTransfer/RestoreFromCloudViewController.swift b/Mixin/UserInterface/Controllers/DeviceTransfer/RestoreFromCloudViewController.swift index 6cf60840b3..ee974f187f 100644 --- a/Mixin/UserInterface/Controllers/DeviceTransfer/RestoreFromCloudViewController.swift +++ b/Mixin/UserInterface/Controllers/DeviceTransfer/RestoreFromCloudViewController.swift @@ -38,7 +38,7 @@ extension RestoreFromCloudViewController: UITableViewDelegate { section.setAccessory(.busy, forRowAt: indexPath.row) DispatchQueue.global().async { if let lastMessageCreatedAt = MessageDAO.shared.lastMessageCreatedAt() { - let messageCount = MessageDAO.shared.messagesCount(matching: nil, sinceDate: nil) + let messageCount = MessageDAO.shared.messagesCount(matching: nil, after: nil) let formattedCount = NumberFormatter.decimal.string(from: NSNumber(value: messageCount)) ?? "\(messageCount)" let createdAt = DateFormatter.dateFull.string(from: lastMessageCreatedAt.toUTCDate()) DispatchQueue.main.async { [weak self] in diff --git a/MixinServices/MixinServices/Database/User/DAO/MessageDAO.swift b/MixinServices/MixinServices/Database/User/DAO/MessageDAO.swift index 5134955b15..b82ad38ec8 100644 --- a/MixinServices/MixinServices/Database/User/DAO/MessageDAO.swift +++ b/MixinServices/MixinServices/Database/User/DAO/MessageDAO.swift @@ -998,23 +998,26 @@ extension MessageDAO { db.select(with: "SELECT ROWID FROM messages WHERE id = ?", arguments: [messageID]) } - public func messagesCount(matching conversationIDs: [String]?, sinceDate date: String?) -> Int { + public func messagesCount(matching conversationIDs: [String]?, after rowID: Int?) -> Int { var sql = "SELECT COUNT(*) FROM messages" - if let date { - sql += " WHERE created_at >= '\(date)'" + if let rowID { + sql += " WHERE ROWID >= \(rowID)" } if let conversationIDs { let ids = conversationIDs.joined(separator: "', '") - sql += date == nil ? " WHERE " : " AND " + sql += rowID == nil ? " WHERE " : " AND " sql += "conversation_id IN ('\(ids)')" } let count: Int? = db.select(with: sql) return count ?? 0 } - public func mediaMessagesCount(matching conversationIDs: [String]?) -> Int { + public func mediaMessagesCount(matching conversationIDs: [String]?, after rowID: Int?) -> Int { let categories = MessageCategory.allMediaCategories.map(\.rawValue).joined(separator: "', '") var sql = "SELECT COUNT(*) FROM messages WHERE category IN ('\(categories)') AND media_status IN ('DONE', 'READ')" + if let rowID { + sql += " AND ROWID >= \(rowID)" + } if let conversationIDs { let ids = conversationIDs.joined(separator: "', '") sql += " AND conversation_id IN ('\(ids)')" @@ -1023,11 +1026,11 @@ extension MessageDAO { return count ?? 0 } - public func transcriptMessageCount(matching conversationIDs: [String]?, sinceDate date: String?) -> Int { + public func transcriptMessageCount(matching conversationIDs: [String]?, after rowID: Int?) -> Int { let categories = MessageCategory.transcriptCategories.map(\.rawValue).joined(separator: "', '") var sql = "SELECT COUNT(*) FROM messages WHERE category IN ('\(categories)')" - if let date { - sql += " AND created_at >= '\(date)'" + if let rowID { + sql += " AND ROWID >= \(rowID)" } if let conversationIDs { let ids = conversationIDs.joined(separator: "', '") diff --git a/MixinServices/MixinServices/Database/User/DAO/PinMessageDAO.swift b/MixinServices/MixinServices/Database/User/DAO/PinMessageDAO.swift index 4b1bb09ee5..1312b77031 100644 --- a/MixinServices/MixinServices/Database/User/DAO/PinMessageDAO.swift +++ b/MixinServices/MixinServices/Database/User/DAO/PinMessageDAO.swift @@ -123,15 +123,15 @@ public final class PinMessageDAO: UserDatabaseDAO { return db.select(with: sql, arguments: [rowID, limit]) } - public func pinMessagesCount(matching conversationIDs: [String]?, sinceDate date: String?) -> Int { + public func pinMessagesCount(matching conversationIDs: [String]?, after rowID: Int?) -> Int { var sql = "SELECT COUNT(*) FROM pin_messages" + if let rowID { + sql += " WHERE ROWID >= \(rowID)" + } if let conversationIDs { let ids = conversationIDs.joined(separator: "', '") - sql += " WHERE conversation_id IN ('\(ids)')" - } - if let date { - sql += conversationIDs == nil ? " WHERE " : " AND " - sql += "created_at >= '\(date)'" + sql += rowID == nil ? " WHERE " : " AND " + sql += "conversation_id IN ('\(ids)')" } let count: Int? = db.select(with: sql) return count ?? 0 From 54520eab3ba8fac5d01fd356b48b6c47185b0580 Mon Sep 17 00:00:00 2001 From: fanyu Date: Thu, 15 Jun 2023 15:25:45 +0800 Subject: [PATCH 10/28] Apply code style --- .../DeviceTransferServerDataSource.swift | 16 ++++++++-------- .../DeviceTransfer/DeviceTransferFilter.swift | 9 +++++++++ 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/Mixin/Service/DeviceTransfer/DeviceTransferServerDataSource.swift b/Mixin/Service/DeviceTransfer/DeviceTransferServerDataSource.swift index d67fbc1e8e..ef6c90d393 100644 --- a/Mixin/Service/DeviceTransfer/DeviceTransferServerDataSource.swift +++ b/Mixin/Service/DeviceTransfer/DeviceTransferServerDataSource.swift @@ -9,9 +9,9 @@ final class DeviceTransferServerDataSource { private let key: DeviceTransferKey private let remotePlatform: DeviceTransferPlatform private let fileContentBuffer: UnsafeMutablePointer + private let filter: DeviceTransferFilter private let conversationIDs: [String]? private let fromDate: String? - private let needsFilterData: Bool private var transcriptMessageCount = 0 @@ -19,9 +19,9 @@ final class DeviceTransferServerDataSource { self.key = key self.remotePlatform = remotePlatform self.fileContentBuffer = .allocate(capacity: fileChunkSize) + self.filter = filter conversationIDs = filter.conversation.ids fromDate = filter.time.utcString - needsFilterData = conversationIDs != nil || fromDate != nil } deinit { @@ -42,10 +42,10 @@ extension DeviceTransferServerDataSource { rowID = nil } let messagesCount = MessageDAO.shared.messagesCount(matching: conversationIDs, after: rowID) - let attachmentsCount = needsFilterData + let attachmentsCount = filter.shouldFilter ? MessageDAO.shared.mediaMessagesCount(matching: conversationIDs, after: rowID) : attachmentsCount() - let transcriptMessageCount = needsFilterData + let transcriptMessageCount = filter.shouldFilter ? MessageDAO.shared.transcriptMessageCount(matching: conversationIDs, after: rowID) : TranscriptMessageDAO.shared.transcriptMessagesCount() let total = ConversationDAO.shared.conversationsCount(matching: conversationIDs) @@ -159,11 +159,11 @@ extension DeviceTransferServerDataSource { } else { nextLocation = nil } - if needsFilterData, location.type == .message { + if filter.shouldFilter, location.type == .message { recordCount -= transcriptMessageCount Logger.general.info(category: "DeviceTransferServerDataSource", message: "Send \(DeviceTransferRecordType.transcriptMessage) \(transcriptMessageCount)") } - if !needsFilterData || location.type != .transcriptMessage { + if !filter.shouldFilter || location.type != .transcriptMessage { Logger.general.info(category: "DeviceTransferServerDataSource", message: "Send \(location.type) \(recordCount)") } recordCount = 0 @@ -327,7 +327,7 @@ extension DeviceTransferServerDataSource { } } case .transcriptMessage: - if needsFilterData { + if filter.shouldFilter { return (0, [], nil, nil) } else { let transcriptMessages = TranscriptMessageDAO.shared.transcriptMessages(limit: limit, after: location.primaryID, with: location.secondaryID) @@ -382,7 +382,7 @@ extension DeviceTransferServerDataSource { Logger.general.error(category: "DeviceTransferServerDataSource", message: "Failed to output message: \(error)") } // TranscriptMessage - if needsFilterData && message.category.hasSuffix("_TRANSCRIPT") { + if filter.shouldFilter && message.category.hasSuffix("_TRANSCRIPT") { transcriptMessageCount += 1 let transcriptMessages = TranscriptMessageDAO.shared.transcriptMessages(transcriptId: message.messageId) transcriptMessageItems = transcriptTransferItems(for: transcriptMessages) diff --git a/Mixin/UserInterface/Controllers/DeviceTransfer/DeviceTransferFilter.swift b/Mixin/UserInterface/Controllers/DeviceTransfer/DeviceTransferFilter.swift index 8a644837ea..3f6d0e61be 100644 --- a/Mixin/UserInterface/Controllers/DeviceTransfer/DeviceTransferFilter.swift +++ b/Mixin/UserInterface/Controllers/DeviceTransfer/DeviceTransferFilter.swift @@ -84,4 +84,13 @@ struct DeviceTransferFilter { var conversation: Conversation var time: Time + var shouldFilter: Bool { + switch (time, conversation) { + case (.all, .all): + return false + default: + return true + } + } + } From ea95badc5fd812912d45378f9b99bced100d851c Mon Sep 17 00:00:00 2001 From: fanyu Date: Thu, 15 Jun 2023 17:14:05 +0800 Subject: [PATCH 11/28] Update pin message row id --- .../DeviceTransferServerDataSource.swift | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/Mixin/Service/DeviceTransfer/DeviceTransferServerDataSource.swift b/Mixin/Service/DeviceTransfer/DeviceTransferServerDataSource.swift index ef6c90d393..8ee500db54 100644 --- a/Mixin/Service/DeviceTransfer/DeviceTransferServerDataSource.swift +++ b/Mixin/Service/DeviceTransfer/DeviceTransferServerDataSource.swift @@ -35,18 +35,21 @@ extension DeviceTransferServerDataSource { func totalCount() -> Int { assert(!Queue.main.isCurrent) - let rowID: Int? + let messageRowID: Int? + let pinMessageRowID: Int? if let fromDate { - rowID = MessageDAO.shared.messageRowID(createdAt: fromDate) + messageRowID = MessageDAO.shared.messageRowID(createdAt: fromDate) + pinMessageRowID = PinMessageDAO.shared.messageRowID(createdAt: fromDate) } else { - rowID = nil + messageRowID = nil + pinMessageRowID = nil } - let messagesCount = MessageDAO.shared.messagesCount(matching: conversationIDs, after: rowID) + let messagesCount = MessageDAO.shared.messagesCount(matching: conversationIDs, after: messageRowID) let attachmentsCount = filter.shouldFilter - ? MessageDAO.shared.mediaMessagesCount(matching: conversationIDs, after: rowID) + ? MessageDAO.shared.mediaMessagesCount(matching: conversationIDs, after: messageRowID) : attachmentsCount() let transcriptMessageCount = filter.shouldFilter - ? MessageDAO.shared.transcriptMessageCount(matching: conversationIDs, after: rowID) + ? MessageDAO.shared.transcriptMessageCount(matching: conversationIDs, after: messageRowID) : TranscriptMessageDAO.shared.transcriptMessagesCount() let total = ConversationDAO.shared.conversationsCount(matching: conversationIDs) + ParticipantDAO.shared.participantsCount(matching: conversationIDs) @@ -55,7 +58,7 @@ extension DeviceTransferServerDataSource { + AssetDAO.shared.assetsCount() + SnapshotDAO.shared.snapshotsCount() + StickerDAO.shared.stickersCount() - + PinMessageDAO.shared.pinMessagesCount(matching: conversationIDs, after: rowID) + + PinMessageDAO.shared.pinMessagesCount(matching: conversationIDs, after: pinMessageRowID) + transcriptMessageCount + messagesCount + MessageMentionDAO.shared.messageMentionsCount(matching: conversationIDs) From 6ecfb89c3458b176b8e329714402558592161fae Mon Sep 17 00:00:00 2001 From: fanyu Date: Thu, 15 Jun 2023 21:01:25 +0800 Subject: [PATCH 12/28] Update SQL query statement --- .../DeviceTransferServerDataSource.swift | 35 ++++++-- .../DeviceTransfer/DeviceTransferFilter.swift | 17 ++++ .../Database/User/DAO/ConversationDAO.swift | 28 ++++--- .../Database/User/DAO/MessageDAO.swift | 82 +++++++++++++------ .../Database/User/DAO/MessageMentionDAO.swift | 27 +++--- .../Database/User/DAO/ParticipantDAO.swift | 27 +++--- .../Database/User/DAO/PinMessageDAO.swift | 28 +++++-- .../Database/User/UserDatabaseDAO.swift | 10 +++ 8 files changed, 183 insertions(+), 71 deletions(-) diff --git a/Mixin/Service/DeviceTransfer/DeviceTransferServerDataSource.swift b/Mixin/Service/DeviceTransfer/DeviceTransferServerDataSource.swift index 8ee500db54..920545a1d2 100644 --- a/Mixin/Service/DeviceTransfer/DeviceTransferServerDataSource.swift +++ b/Mixin/Service/DeviceTransfer/DeviceTransferServerDataSource.swift @@ -10,8 +10,9 @@ final class DeviceTransferServerDataSource { private let remotePlatform: DeviceTransferPlatform private let fileContentBuffer: UnsafeMutablePointer private let filter: DeviceTransferFilter - private let conversationIDs: [String]? private let fromDate: String? + private let conversationIDs: [String]? + private let conversationIDsForFetching: [String]? private var transcriptMessageCount = 0 @@ -20,8 +21,9 @@ final class DeviceTransferServerDataSource { self.remotePlatform = remotePlatform self.fileContentBuffer = .allocate(capacity: fileChunkSize) self.filter = filter - conversationIDs = filter.conversation.ids fromDate = filter.time.utcString + conversationIDs = filter.conversation.ids + conversationIDsForFetching = filter.conversation.idsForFetching } deinit { @@ -186,11 +188,14 @@ extension DeviceTransferServerDataSource { case .conversation: let conversations = ConversationDAO.shared.conversations(limit: limit, after: location.primaryID, - matching: conversationIDs) + matching: conversationIDsForFetching) databaseItemCount = conversations.count nextPrimaryID = conversations.last?.conversationId nextSecondaryID = nil transferItems = conversations.compactMap { conversation in + guard filter.isValidItem(conversationID: conversation.conversationId) else { + return nil + } let deviceTransferConversation = DeviceTransferConversation(conversation: conversation, to: remotePlatform) do { let outputData = try DeviceTransferProtocol.output(type: location.type, data: deviceTransferConversation, key: key) @@ -204,11 +209,14 @@ extension DeviceTransferServerDataSource { let participants = ParticipantDAO.shared.participants(limit: limit, after: location.primaryID, with: location.secondaryID, - matching: conversationIDs) + matching: conversationIDsForFetching) databaseItemCount = participants.count nextPrimaryID = participants.last?.conversationId nextSecondaryID = participants.last?.userId transferItems = participants.compactMap { participant in + guard filter.isValidItem(conversationID: participant.conversationId) else { + return nil + } let deviceTransferParticipant = DeviceTransferParticipant(participant: participant) do { let outputData = try DeviceTransferProtocol.output(type: location.type, data: deviceTransferParticipant, key: key) @@ -308,7 +316,9 @@ extension DeviceTransferServerDataSource { rowID = -1 } } - let pinMessages = PinMessageDAO.shared.pinMessages(limit: limit, after: rowID, matching: conversationIDs) + let pinMessages = PinMessageDAO.shared.pinMessages(limit: limit, + after: rowID, + matching: conversationIDsForFetching) databaseItemCount = pinMessages.count if let messageID = pinMessages.last?.messageId, let rowID = PinMessageDAO.shared.messageRowID(messageID: messageID) { nextPrimaryID = "\(rowID)" @@ -317,6 +327,9 @@ extension DeviceTransferServerDataSource { } nextSecondaryID = nil transferItems = pinMessages.compactMap { pinMessage in + guard filter.isValidItem(conversationID: pinMessage.conversationId) else { + return nil + } if let fromDate, pinMessage.createdAt < fromDate { return nil } @@ -354,7 +367,9 @@ extension DeviceTransferServerDataSource { rowID = -1 } } - let messages = MessageDAO.shared.messages(limit: limit, after: rowID, matching: conversationIDs) + let messages = MessageDAO.shared.messages(limit: limit, + after: rowID, + matching: conversationIDsForFetching) databaseItemCount = messages.count if let messageID = messages.last?.messageId, let rowID = MessageDAO.shared.messageRowID(messageID: messageID) { nextPrimaryID = "\(rowID)" @@ -365,6 +380,9 @@ extension DeviceTransferServerDataSource { var messageItems = [TransferItem]() var transcriptMessageItems = [TransferItem]() for message in messages { + guard filter.isValidItem(conversationID: message.conversationId) else { + continue + } if let fromDate, message.createdAt < fromDate { continue } @@ -395,11 +413,14 @@ extension DeviceTransferServerDataSource { case .messageMention: let messageMentions = MessageMentionDAO.shared.messageMentions(limit: limit, after: location.primaryID, - matching: conversationIDs) + matching: conversationIDsForFetching) databaseItemCount = messageMentions.count nextPrimaryID = messageMentions.last?.messageId nextSecondaryID = nil transferItems = messageMentions.compactMap { messageMention in + guard filter.isValidItem(conversationID: messageMention.conversationId) else { + return nil + } let deviceTransferMessageMention = DeviceTransferMessageMention(messageMention: messageMention) do { let outputData = try DeviceTransferProtocol.output(type: location.type, data: deviceTransferMessageMention, key: key) diff --git a/Mixin/UserInterface/Controllers/DeviceTransfer/DeviceTransferFilter.swift b/Mixin/UserInterface/Controllers/DeviceTransfer/DeviceTransferFilter.swift index 3f6d0e61be..1ac650ca11 100644 --- a/Mixin/UserInterface/Controllers/DeviceTransfer/DeviceTransferFilter.swift +++ b/Mixin/UserInterface/Controllers/DeviceTransfer/DeviceTransferFilter.swift @@ -33,6 +33,15 @@ struct DeviceTransferFilter { } } + var idsForFetching: [String]? { + switch self { + case .all: + return nil + case .designated(let ids): + return ids.count > UserDatabaseDAO.strideForDeviceTransfer ? nil : ids + } + } + } enum Time { @@ -93,4 +102,12 @@ struct DeviceTransferFilter { } } + func isValidItem(conversationID: String) -> Bool { + if case .designated(let ids) = conversation, ids.count > UserDatabaseDAO.strideForDeviceTransfer { + return ids.contains(conversationID) + } else { + return true + } + } + } diff --git a/MixinServices/MixinServices/Database/User/DAO/ConversationDAO.swift b/MixinServices/MixinServices/Database/User/DAO/ConversationDAO.swift index c67f75e17b..157cee5365 100644 --- a/MixinServices/MixinServices/Database/User/DAO/ConversationDAO.swift +++ b/MixinServices/MixinServices/Database/User/DAO/ConversationDAO.swift @@ -661,26 +661,34 @@ public final class ConversationDAO: UserDatabaseDAO { public func conversations(limit: Int, after conversationId: String?, matching conversationIDs: [String]?) -> [Conversation] { var sql = "SELECT * FROM conversations" + if let conversationId { + sql += " WHERE ROWID > IFNULL((SELECT ROWID FROM conversations WHERE conversation_id = '\(conversationId)'), 0)" + } if let conversationIDs { let ids = conversationIDs.joined(separator: "', '") - sql += " WHERE conversation_id IN ('\(ids)')" - } - if let conversationId { - sql += conversationIDs == nil ? " WHERE " : " AND " - sql += "ROWID > IFNULL((SELECT ROWID FROM conversations WHERE conversation_id = '\(conversationId)'), 0)" + sql += conversationId == nil ? " WHERE" : " AND" + sql += " conversation_id IN ('\(ids)')" } sql += " ORDER BY ROWID LIMIT ?" return db.select(with: sql, arguments: [limit]) } public func conversationsCount(matching conversationIDs: [String]?) -> Int { - var sql = "SELECT COUNT(*) FROM conversations" if let conversationIDs { - let ids = conversationIDs.joined(separator: "', '") - sql += " WHERE conversation_id IN ('\(ids)')" + var totalCount = 0 + for i in stride(from: 0, to: conversationIDs.count, by: Self.strideForDeviceTransfer) { + let endIndex = min(i + Self.strideForDeviceTransfer, conversationIDs.count) + let ids = Array(conversationIDs[i.. Int { - var sql = "SELECT COUNT(*) FROM messages" - if let rowID { - sql += " WHERE ROWID >= \(rowID)" - } if let conversationIDs { - let ids = conversationIDs.joined(separator: "', '") - sql += rowID == nil ? " WHERE " : " AND " - sql += "conversation_id IN ('\(ids)')" + var totalCount = 0 + for i in stride(from: 0, to: conversationIDs.count, by: Self.strideForDeviceTransfer) { + let endIndex = min(i + Self.strideForDeviceTransfer, conversationIDs.count) + let ids = Array(conversationIDs[i.. Int { let categories = MessageCategory.allMediaCategories.map(\.rawValue).joined(separator: "', '") - var sql = "SELECT COUNT(*) FROM messages WHERE category IN ('\(categories)') AND media_status IN ('DONE', 'READ')" - if let rowID { - sql += " AND ROWID >= \(rowID)" - } if let conversationIDs { - let ids = conversationIDs.joined(separator: "', '") - sql += " AND conversation_id IN ('\(ids)')" + var totalCount = 0 + for i in stride(from: 0, to: conversationIDs.count, by: Self.strideForDeviceTransfer) { + let endIndex = min(i + Self.strideForDeviceTransfer, conversationIDs.count) + let ids = Array(conversationIDs[i.. Int { let categories = MessageCategory.transcriptCategories.map(\.rawValue).joined(separator: "', '") - var sql = "SELECT COUNT(*) FROM messages WHERE category IN ('\(categories)')" - if let rowID { - sql += " AND ROWID >= \(rowID)" - } if let conversationIDs { - let ids = conversationIDs.joined(separator: "', '") - sql += " AND conversation_id IN ('\(ids)')" + var totalCount = 0 + for i in stride(from: 0, to: conversationIDs.count, by: Self.strideForDeviceTransfer) { + let endIndex = min(i + Self.strideForDeviceTransfer, conversationIDs.count) + let ids = Array(conversationIDs[i.. String? { diff --git a/MixinServices/MixinServices/Database/User/DAO/MessageMentionDAO.swift b/MixinServices/MixinServices/Database/User/DAO/MessageMentionDAO.swift index 39ba07d4b5..9f1680c33e 100644 --- a/MixinServices/MixinServices/Database/User/DAO/MessageMentionDAO.swift +++ b/MixinServices/MixinServices/Database/User/DAO/MessageMentionDAO.swift @@ -17,26 +17,33 @@ public final class MessageMentionDAO: UserDatabaseDAO { public func messageMentions(limit: Int, after messageId: String?, matching conversationIDs: [String]?) -> [MessageMention] { var sql = "SELECT * FROM message_mentions" + if let messageId { + sql += " WHERE ROWID > IFNULL((SELECT ROWID FROM message_mentions WHERE message_id = '\(messageId)'), 0)" + } if let conversationIDs { + sql += messageId == nil ? " WHERE" : " AND" let ids = conversationIDs.joined(separator: "', '") - sql += " WHERE conversation_id IN ('\(ids)')" - } - if let messageId { - sql += conversationIDs == nil ? " WHERE " : " AND " - sql += "ROWID > IFNULL((SELECT ROWID FROM message_mentions WHERE message_id = '\(messageId)'), 0)" + sql += " conversation_id IN ('\(ids)')" } sql += " ORDER BY ROWID LIMIT ?" return db.select(with: sql, arguments: [limit]) } public func messageMentionsCount(matching conversationIDs: [String]?) -> Int { - var sql = "SELECT COUNT(*) FROM message_mentions" if let conversationIDs { - let ids = conversationIDs.joined(separator: "', '") - sql += " WHERE conversation_id IN ('\(ids)')" + var totalCount = 0 + for i in stride(from: 0, to: conversationIDs.count, by: Self.strideForDeviceTransfer) { + let endIndex = min(i + Self.strideForDeviceTransfer, conversationIDs.count) + let ids = Array(conversationIDs[i.. [Participant] { var sql = "SELECT * FROM participants" + if let conversationId, let userId { + sql += " WHERE ROWID > IFNULL((SELECT ROWID FROM participants WHERE conversation_id = '\(conversationId)' AND user_id = '\(userId)'), 0)" + } if let conversationIDs { let ids = conversationIDs.joined(separator: "', '") - sql += " WHERE conversation_id IN ('\(ids)')" - } - if let conversationId, let userId { - sql += conversationIDs == nil ? " WHERE " : " AND " - sql += "ROWID > IFNULL((SELECT ROWID FROM participants WHERE conversation_id = '\(conversationId)' AND user_id = '\(userId)'), 0)" + sql += conversationId == nil ? " WHERE" : " AND" + sql += " conversation_id IN ('\(ids)')" } sql += " ORDER BY ROWID LIMIT ?" return db.select(with: sql, arguments: [limit]) } public func participantsCount(matching conversationIDs: [String]?) -> Int { - var sql = "SELECT COUNT(*) FROM participants" if let conversationIDs { - let ids = conversationIDs.joined(separator: "', '") - sql += " WHERE conversation_id IN ('\(ids)')" + var totalCount = 0 + for i in stride(from: 0, to: conversationIDs.count, by: Self.strideForDeviceTransfer) { + let endIndex = min(i + Self.strideForDeviceTransfer, conversationIDs.count) + let ids = Array(conversationIDs[i.. Int { - var sql = "SELECT COUNT(*) FROM pin_messages" - if let rowID { - sql += " WHERE ROWID >= \(rowID)" - } if let conversationIDs { - let ids = conversationIDs.joined(separator: "', '") - sql += rowID == nil ? " WHERE " : " AND " - sql += "conversation_id IN ('\(ids)')" + var totalCount = 0 + for i in stride(from: 0, to: conversationIDs.count, by: Self.strideForDeviceTransfer) { + let endIndex = min(i + Self.strideForDeviceTransfer, conversationIDs.count) + let ids = Array(conversationIDs[i.. Int? { diff --git a/MixinServices/MixinServices/Database/User/UserDatabaseDAO.swift b/MixinServices/MixinServices/Database/User/UserDatabaseDAO.swift index 0aa6e30f71..176fb26f7d 100644 --- a/MixinServices/MixinServices/Database/User/UserDatabaseDAO.swift +++ b/MixinServices/MixinServices/Database/User/UserDatabaseDAO.swift @@ -7,3 +7,13 @@ public class UserDatabaseDAO { } } + +extension UserDatabaseDAO { + + // To prevent excessive memory allocations, + // the maximum value of a host parameter number is SQLITE_MAX_VARIABLE_NUMBER, + // which defaults to 999 for SQLite versions prior to 3.32.0 (2020-05-22) or 32766 for SQLite versions after 3.32.0. + // Therefore, we default to grouping with 900 + public static let strideForDeviceTransfer = 900 + +} From fc99a23130f5fa21786402b6209dcc649f20902c Mon Sep 17 00:00:00 2001 From: fanyu Date: Fri, 16 Jun 2023 15:15:13 +0800 Subject: [PATCH 13/28] Optimize SQL query --- MixinServices/MixinServices/Database/User/DAO/MessageDAO.swift | 2 +- .../MixinServices/Database/User/DAO/PinMessageDAO.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/MixinServices/MixinServices/Database/User/DAO/MessageDAO.swift b/MixinServices/MixinServices/Database/User/DAO/MessageDAO.swift index 90f30472ce..1a4ec8e71a 100644 --- a/MixinServices/MixinServices/Database/User/DAO/MessageDAO.swift +++ b/MixinServices/MixinServices/Database/User/DAO/MessageDAO.swift @@ -991,7 +991,7 @@ extension MessageDAO { } public func messageRowID(createdAt: String) -> Int? { - db.select(with: "SELECT ROWID FROM messages WHERE created_at >= ? LIMIT 1", arguments: [createdAt]) + db.select(with: "SELECT ROWID FROM messages WHERE created_at >= ? ORDER BY ROWID LIMIT 1", arguments: [createdAt]) } public func messageRowID(messageID: String) -> Int? { diff --git a/MixinServices/MixinServices/Database/User/DAO/PinMessageDAO.swift b/MixinServices/MixinServices/Database/User/DAO/PinMessageDAO.swift index cc9cdb0074..872aade7b2 100644 --- a/MixinServices/MixinServices/Database/User/DAO/PinMessageDAO.swift +++ b/MixinServices/MixinServices/Database/User/DAO/PinMessageDAO.swift @@ -148,7 +148,7 @@ public final class PinMessageDAO: UserDatabaseDAO { } public func messageRowID(createdAt: String) -> Int? { - db.select(with: "SELECT ROWID FROM pin_messages WHERE created_at >= ? LIMIT 1", arguments: [createdAt]) + db.select(with: "SELECT ROWID FROM pin_messages WHERE created_at >= ? ORDER BY ROWID LIMIT 1", arguments: [createdAt]) } public func messageRowID(messageID: String) -> Int? { From 60439831aeb6e2c05a0942e3b06d1382ef29cfc3 Mon Sep 17 00:00:00 2001 From: fanyu Date: Fri, 16 Jun 2023 15:27:27 +0800 Subject: [PATCH 14/28] Apply code style --- .../MixinServices/Database/User/DAO/ConversationDAO.swift | 2 +- .../MixinServices/Database/User/DAO/MessageDAO.swift | 6 +++--- .../MixinServices/Database/User/DAO/MessageMentionDAO.swift | 2 +- .../MixinServices/Database/User/DAO/ParticipantDAO.swift | 2 +- .../MixinServices/Database/User/DAO/PinMessageDAO.swift | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/MixinServices/MixinServices/Database/User/DAO/ConversationDAO.swift b/MixinServices/MixinServices/Database/User/DAO/ConversationDAO.swift index 157cee5365..530ce6cfbb 100644 --- a/MixinServices/MixinServices/Database/User/DAO/ConversationDAO.swift +++ b/MixinServices/MixinServices/Database/User/DAO/ConversationDAO.swift @@ -679,7 +679,7 @@ public final class ConversationDAO: UserDatabaseDAO { for i in stride(from: 0, to: conversationIDs.count, by: Self.strideForDeviceTransfer) { let endIndex = min(i + Self.strideForDeviceTransfer, conversationIDs.count) let ids = Array(conversationIDs[i.. Date: Fri, 16 Jun 2023 20:15:47 +0800 Subject: [PATCH 15/28] Fix the crash caused by incorrectly declared properties --- Mixin.xcodeproj/project.pbxproj | 4 ++ .../Common/PeerViewController.swift | 1 + .../Controllers/Common/Views/PeerView.xib | 59 +-------------- ...rConversationSelectionViewController.swift | 21 +++--- ...ansferConversationSelectionToolbarView.xib | 72 +++++++++++++++++++ 5 files changed, 91 insertions(+), 66 deletions(-) create mode 100644 Mixin/UserInterface/Controllers/DeviceTransfer/View/DeviceTransferConversationSelectionToolbarView.xib diff --git a/Mixin.xcodeproj/project.pbxproj b/Mixin.xcodeproj/project.pbxproj index d08ea47eaf..f4aecbfdbe 100644 --- a/Mixin.xcodeproj/project.pbxproj +++ b/Mixin.xcodeproj/project.pbxproj @@ -772,6 +772,7 @@ 949569A8263B13BF00E043FE /* TranscriptMessageViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 949569A7263B13BF00E043FE /* TranscriptMessageViewModel.swift */; }; 949A3686261D9C5C004251B2 /* post.css in Resources */ = {isa = PBXBuildFile; fileRef = 949A3685261D9C5C004251B2 /* post.css */; }; 94A1B1DF25BFD7CB0098586D /* LinkLocatingTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94A1B1DE25BFD7CB0098586D /* LinkLocatingTextView.swift */; }; + 94B309012A3C910E004C96A4 /* DeviceTransferConversationSelectionToolbarView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 94B309002A3C910E004C96A4 /* DeviceTransferConversationSelectionToolbarView.xib */; }; 94B7643525EA8E9B00EDF9C6 /* CacheableAssetFileDescription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94B7643425EA8E9B00EDF9C6 /* CacheableAssetFileDescription.swift */; }; 94B7B6DC26B43562000B0AC5 /* SilentNotificationMessagePreviewView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 94B7B6DB26B43562000B0AC5 /* SilentNotificationMessagePreviewView.xib */; }; 94B7B6DE26B43581000B0AC5 /* SilentNotificationMessagePreviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94B7B6DD26B43580000B0AC5 /* SilentNotificationMessagePreviewViewController.swift */; }; @@ -1857,6 +1858,7 @@ 949569A7263B13BF00E043FE /* TranscriptMessageViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TranscriptMessageViewModel.swift; sourceTree = ""; }; 949A3685261D9C5C004251B2 /* post.css */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.css; path = post.css; sourceTree = ""; }; 94A1B1DE25BFD7CB0098586D /* LinkLocatingTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkLocatingTextView.swift; sourceTree = ""; }; + 94B309002A3C910E004C96A4 /* DeviceTransferConversationSelectionToolbarView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = DeviceTransferConversationSelectionToolbarView.xib; sourceTree = ""; }; 94B7643425EA8E9B00EDF9C6 /* CacheableAssetFileDescription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CacheableAssetFileDescription.swift; sourceTree = ""; }; 94B7B6DB26B43562000B0AC5 /* SilentNotificationMessagePreviewView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SilentNotificationMessagePreviewView.xib; sourceTree = ""; }; 94B7B6DD26B43580000B0AC5 /* SilentNotificationMessagePreviewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SilentNotificationMessagePreviewViewController.swift; sourceTree = ""; }; @@ -2682,6 +2684,7 @@ 7CEB735629DB272F006FB5B2 /* RestoreChatTableViewCell.swift */, 7CEB735729DB272F006FB5B2 /* RestoreChatTableViewCell.xib */, 7CE78FB02A30883200FEB942 /* DeviceTransferDateSelectionView.xib */, + 94B309002A3C910E004C96A4 /* DeviceTransferConversationSelectionToolbarView.xib */, ); path = View; sourceTree = ""; @@ -4279,6 +4282,7 @@ DFB18FFC2330E71E0021CAF3 /* AnnouncementView.xib in Resources */, 7BF52B232199AC2E007A5A74 /* NoTransactionFooterView.xib in Resources */, 7CE5E7A9269BDA29000B7904 /* HomeAppsPinTipsView.xib in Resources */, + 94B309012A3C910E004C96A4 /* DeviceTransferConversationSelectionToolbarView.xib in Resources */, 7BD639CB2282B17B00B7B3A6 /* PeerView.xib in Resources */, 7C4C03A928532D2F003DE0C0 /* PhoneContactCell.xib in Resources */, 7BE73BFE230A8D0300B97FC6 /* group_separator_2.png in Resources */, diff --git a/Mixin/UserInterface/Controllers/Common/PeerViewController.swift b/Mixin/UserInterface/Controllers/Common/PeerViewController.swift index 4efc01543d..e5ece7d2b0 100644 --- a/Mixin/UserInterface/Controllers/Common/PeerViewController.swift +++ b/Mixin/UserInterface/Controllers/Common/PeerViewController.swift @@ -14,6 +14,7 @@ class PeerViewController - - - - - + @@ -48,76 +44,25 @@ - - - - - - + - - - - - diff --git a/Mixin/UserInterface/Controllers/DeviceTransfer/DeviceTransferConversationSelectionViewController.swift b/Mixin/UserInterface/Controllers/DeviceTransfer/DeviceTransferConversationSelectionViewController.swift index 7f044b8baa..2eac626f1d 100644 --- a/Mixin/UserInterface/Controllers/DeviceTransfer/DeviceTransferConversationSelectionViewController.swift +++ b/Mixin/UserInterface/Controllers/DeviceTransfer/DeviceTransferConversationSelectionViewController.swift @@ -4,12 +4,12 @@ import MixinServices class DeviceTransferConversationSelectionViewController: PeerViewController { - @IBOutlet weak var actionView: UIView! @IBOutlet weak var operationAllButton: UIButton! @IBOutlet weak var showSelectedButton: UIButton! - @IBOutlet weak var showActionViewConstraint: NSLayoutConstraint! - @IBOutlet weak var hideActionViewConstraint: NSLayoutConstraint! + private var toolbarView: UIView! + private var filter: DeviceTransferFilter.Conversation = .all + private var changeHandler: DeviceTransferFilter.ConversationChangeHandler? private var selections = [MessageReceiver]() { didSet { @@ -25,9 +25,6 @@ class DeviceTransferConversationSelectionViewController: PeerViewController UIViewController { let controller = DeviceTransferConversationSelectionViewController() controller.filter = filter @@ -37,9 +34,15 @@ class DeviceTransferConversationSelectionViewController: PeerViewController + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 398a9da8918bad3de6aa385fd86b70e9611edd66 Mon Sep 17 00:00:00 2001 From: fanyu Date: Mon, 19 Jun 2023 10:44:50 +0800 Subject: [PATCH 16/28] Explicitly specify the ascending sorting order --- MixinServices/MixinServices/Database/User/DAO/AppDAO.swift | 2 +- MixinServices/MixinServices/Database/User/DAO/AssetDAO.swift | 2 +- .../MixinServices/Database/User/DAO/ConversationDAO.swift | 2 +- .../MixinServices/Database/User/DAO/ExpiredMessageDAO.swift | 2 +- .../MixinServices/Database/User/DAO/MessageDAO.swift | 4 ++-- .../MixinServices/Database/User/DAO/MessageMentionDAO.swift | 2 +- .../MixinServices/Database/User/DAO/ParticipantDAO.swift | 2 +- .../MixinServices/Database/User/DAO/PinMessageDAO.swift | 4 ++-- .../MixinServices/Database/User/DAO/SnapshotDAO.swift | 2 +- .../MixinServices/Database/User/DAO/StickerDAO.swift | 2 +- .../Database/User/DAO/TranscriptMessageDAO.swift | 2 +- MixinServices/MixinServices/Database/User/DAO/UserDAO.swift | 2 +- 12 files changed, 14 insertions(+), 14 deletions(-) diff --git a/MixinServices/MixinServices/Database/User/DAO/AppDAO.swift b/MixinServices/MixinServices/Database/User/DAO/AppDAO.swift index 986108d201..81efde1a8a 100644 --- a/MixinServices/MixinServices/Database/User/DAO/AppDAO.swift +++ b/MixinServices/MixinServices/Database/User/DAO/AppDAO.swift @@ -40,7 +40,7 @@ public final class AppDAO: UserDatabaseDAO { if let appId { sql += " WHERE ROWID > IFNULL((SELECT ROWID FROM apps WHERE app_id = '\(appId)'), 0)" } - sql += " ORDER BY ROWID LIMIT ?" + sql += " ORDER BY ROWID ASC LIMIT ?" return db.select(with: sql, arguments: [limit]) } diff --git a/MixinServices/MixinServices/Database/User/DAO/AssetDAO.swift b/MixinServices/MixinServices/Database/User/DAO/AssetDAO.swift index 87de4889f4..d39b59896c 100644 --- a/MixinServices/MixinServices/Database/User/DAO/AssetDAO.swift +++ b/MixinServices/MixinServices/Database/User/DAO/AssetDAO.swift @@ -109,7 +109,7 @@ public final class AssetDAO: UserDatabaseDAO { if let assetId { sql += " WHERE ROWID > IFNULL((SELECT ROWID FROM assets WHERE asset_id = '\(assetId)'), 0)" } - sql += " ORDER BY ROWID LIMIT ?" + sql += " ORDER BY ROWID ASC LIMIT ?" return db.select(with: sql, arguments: [limit]) } diff --git a/MixinServices/MixinServices/Database/User/DAO/ConversationDAO.swift b/MixinServices/MixinServices/Database/User/DAO/ConversationDAO.swift index 530ce6cfbb..33657840ec 100644 --- a/MixinServices/MixinServices/Database/User/DAO/ConversationDAO.swift +++ b/MixinServices/MixinServices/Database/User/DAO/ConversationDAO.swift @@ -669,7 +669,7 @@ public final class ConversationDAO: UserDatabaseDAO { sql += conversationId == nil ? " WHERE" : " AND" sql += " conversation_id IN ('\(ids)')" } - sql += " ORDER BY ROWID LIMIT ?" + sql += " ORDER BY ROWID ASC LIMIT ?" return db.select(with: sql, arguments: [limit]) } diff --git a/MixinServices/MixinServices/Database/User/DAO/ExpiredMessageDAO.swift b/MixinServices/MixinServices/Database/User/DAO/ExpiredMessageDAO.swift index 49db63fa0a..439248a86f 100644 --- a/MixinServices/MixinServices/Database/User/DAO/ExpiredMessageDAO.swift +++ b/MixinServices/MixinServices/Database/User/DAO/ExpiredMessageDAO.swift @@ -148,7 +148,7 @@ public final class ExpiredMessageDAO: UserDatabaseDAO { if let messageId { sql += " WHERE ROWID > IFNULL((SELECT ROWID FROM expired_messages WHERE message_id = '\(messageId)'), 0)" } - sql += " ORDER BY ROWID LIMIT ?" + sql += " ORDER BY ROWID ASC LIMIT ?" return db.select(with: sql, arguments: [limit]) } diff --git a/MixinServices/MixinServices/Database/User/DAO/MessageDAO.swift b/MixinServices/MixinServices/Database/User/DAO/MessageDAO.swift index bc99834aeb..a0807c93ea 100644 --- a/MixinServices/MixinServices/Database/User/DAO/MessageDAO.swift +++ b/MixinServices/MixinServices/Database/User/DAO/MessageDAO.swift @@ -986,12 +986,12 @@ extension MessageDAO { let ids = conversationIDs.joined(separator: "', '") sql += " AND conversation_id IN ('\(ids)')" } - sql += " ORDER BY ROWID LIMIT ?" + sql += " ORDER BY ROWID ASC LIMIT ?" return db.select(with: sql, arguments: [rowID, limit]) } public func messageRowID(createdAt: String) -> Int? { - db.select(with: "SELECT ROWID FROM messages WHERE created_at >= ? ORDER BY ROWID LIMIT 1", arguments: [createdAt]) + db.select(with: "SELECT ROWID FROM messages WHERE created_at >= ? ORDER BY ROWID ASC LIMIT 1", arguments: [createdAt]) } public func messageRowID(messageID: String) -> Int? { diff --git a/MixinServices/MixinServices/Database/User/DAO/MessageMentionDAO.swift b/MixinServices/MixinServices/Database/User/DAO/MessageMentionDAO.swift index 81275f7f9d..1ac847b555 100644 --- a/MixinServices/MixinServices/Database/User/DAO/MessageMentionDAO.swift +++ b/MixinServices/MixinServices/Database/User/DAO/MessageMentionDAO.swift @@ -25,7 +25,7 @@ public final class MessageMentionDAO: UserDatabaseDAO { let ids = conversationIDs.joined(separator: "', '") sql += " conversation_id IN ('\(ids)')" } - sql += " ORDER BY ROWID LIMIT ?" + sql += " ORDER BY ROWID ASC LIMIT ?" return db.select(with: sql, arguments: [limit]) } diff --git a/MixinServices/MixinServices/Database/User/DAO/ParticipantDAO.swift b/MixinServices/MixinServices/Database/User/DAO/ParticipantDAO.swift index 062cd141c6..4bc24d5eb0 100644 --- a/MixinServices/MixinServices/Database/User/DAO/ParticipantDAO.swift +++ b/MixinServices/MixinServices/Database/User/DAO/ParticipantDAO.swift @@ -185,7 +185,7 @@ public final class ParticipantDAO: UserDatabaseDAO { sql += conversationId == nil ? " WHERE" : " AND" sql += " conversation_id IN ('\(ids)')" } - sql += " ORDER BY ROWID LIMIT ?" + sql += " ORDER BY ROWID ASC LIMIT ?" return db.select(with: sql, arguments: [limit]) } diff --git a/MixinServices/MixinServices/Database/User/DAO/PinMessageDAO.swift b/MixinServices/MixinServices/Database/User/DAO/PinMessageDAO.swift index 7bd8859f00..8cf3de1aff 100644 --- a/MixinServices/MixinServices/Database/User/DAO/PinMessageDAO.swift +++ b/MixinServices/MixinServices/Database/User/DAO/PinMessageDAO.swift @@ -119,7 +119,7 @@ public final class PinMessageDAO: UserDatabaseDAO { let ids = conversationIDs.joined(separator: "', '") sql += " AND conversation_id IN ('\(ids)')" } - sql += " ORDER BY ROWID LIMIT ?" + sql += " ORDER BY ROWID ASC LIMIT ?" return db.select(with: sql, arguments: [rowID, limit]) } @@ -148,7 +148,7 @@ public final class PinMessageDAO: UserDatabaseDAO { } public func messageRowID(createdAt: String) -> Int? { - db.select(with: "SELECT ROWID FROM pin_messages WHERE created_at >= ? ORDER BY ROWID LIMIT 1", arguments: [createdAt]) + db.select(with: "SELECT ROWID FROM pin_messages WHERE created_at >= ? ORDER BY ROWID ASC LIMIT 1", arguments: [createdAt]) } public func messageRowID(messageID: String) -> Int? { diff --git a/MixinServices/MixinServices/Database/User/DAO/SnapshotDAO.swift b/MixinServices/MixinServices/Database/User/DAO/SnapshotDAO.swift index 3b03d67012..d7036f1f2e 100644 --- a/MixinServices/MixinServices/Database/User/DAO/SnapshotDAO.swift +++ b/MixinServices/MixinServices/Database/User/DAO/SnapshotDAO.swift @@ -121,7 +121,7 @@ public final class SnapshotDAO: UserDatabaseDAO { if let snapshotId { sql += " WHERE ROWID > IFNULL((SELECT ROWID FROM snapshots WHERE snapshot_id = '\(snapshotId)'), 0)" } - sql += " ORDER BY ROWID LIMIT ?" + sql += " ORDER BY ROWID ASC LIMIT ?" return db.select(with: sql, arguments: [limit]) } diff --git a/MixinServices/MixinServices/Database/User/DAO/StickerDAO.swift b/MixinServices/MixinServices/Database/User/DAO/StickerDAO.swift index e17f27c9dc..7b06e875f5 100644 --- a/MixinServices/MixinServices/Database/User/DAO/StickerDAO.swift +++ b/MixinServices/MixinServices/Database/User/DAO/StickerDAO.swift @@ -168,7 +168,7 @@ public final class StickerDAO: UserDatabaseDAO { if let stickerId { sql += " WHERE ROWID > IFNULL((SELECT ROWID FROM stickers WHERE sticker_id = '\(stickerId)'), 0)" } - sql += " ORDER BY ROWID LIMIT ?" + sql += " ORDER BY ROWID ASC LIMIT ?" return db.select(with: sql, arguments: [limit]) } diff --git a/MixinServices/MixinServices/Database/User/DAO/TranscriptMessageDAO.swift b/MixinServices/MixinServices/Database/User/DAO/TranscriptMessageDAO.swift index 8eef4a5c9a..67431d2ef0 100644 --- a/MixinServices/MixinServices/Database/User/DAO/TranscriptMessageDAO.swift +++ b/MixinServices/MixinServices/Database/User/DAO/TranscriptMessageDAO.swift @@ -95,7 +95,7 @@ public final class TranscriptMessageDAO: UserDatabaseDAO { if let transcriptId, let messageId { sql += " WHERE ROWID > IFNULL((SELECT ROWID FROM transcript_messages WHERE transcript_id = '\(transcriptId)' AND message_id = '\(messageId)'), 0)" } - sql += " ORDER BY ROWID LIMIT ?" + sql += " ORDER BY ROWID ASC LIMIT ?" return db.select(with: sql, arguments: [limit]) } diff --git a/MixinServices/MixinServices/Database/User/DAO/UserDAO.swift b/MixinServices/MixinServices/Database/User/DAO/UserDAO.swift index 28fc0a20aa..eb7ae7d607 100644 --- a/MixinServices/MixinServices/Database/User/DAO/UserDAO.swift +++ b/MixinServices/MixinServices/Database/User/DAO/UserDAO.swift @@ -226,7 +226,7 @@ public final class UserDAO: UserDatabaseDAO { if let userId { sql += " WHERE ROWID > IFNULL((SELECT ROWID FROM users WHERE user_id = '\(userId)'), 0)" } - sql += " ORDER BY ROWID LIMIT ?" + sql += " ORDER BY ROWID ASC LIMIT ?" return db.select(with: sql, arguments: [limit]) } From 9e38366809ff37e9cea2f803bebb10e1a12d827f Mon Sep 17 00:00:00 2001 From: fanyu Date: Mon, 19 Jun 2023 11:29:34 +0800 Subject: [PATCH 17/28] Update localization --- Mixin/Resources/en.lproj/Localizable.strings | 2 +- Mixin/Resources/es.lproj/Localizable.strings | 2 +- Mixin/Resources/ja.lproj/Localizable.strings | 2 +- Mixin/Resources/ru.lproj/Localizable.strings | 2 +- Mixin/Resources/zh-Hans.lproj/Localizable.strings | 2 +- Mixin/Resources/zh-Hant.lproj/Localizable.strings | 2 +- .../DeviceTransfer/View/DeviceTransferDateSelectionView.xib | 4 ++-- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Mixin/Resources/en.lproj/Localizable.strings b/Mixin/Resources/en.lproj/Localizable.strings index 6bd40b7bce..9dc7331ebe 100644 --- a/Mixin/Resources/en.lproj/Localizable.strings +++ b/Mixin/Resources/en.lproj/Localizable.strings @@ -304,7 +304,7 @@ "deposit_tip_eth" = "This address supports all ERC-20 tokens, such as ETH, XIN, etc."; "deposit_tip_trx" = "This address supports all TRC-10 and TRC-20 tokens."; "deselect_all" = "Deselect All"; -"designated_time_frame" = "Designated time frame"; +"designated_time_period" = "Designated time period"; "desktop_on_hint" = "You have your desktop logged in"; "desktop_upgrade" = "Please upgrade Mixin Messenger Desktop to the latest version."; "detect_qr_tip" = "Detected a Mixin QR code, tap to recognize"; diff --git a/Mixin/Resources/es.lproj/Localizable.strings b/Mixin/Resources/es.lproj/Localizable.strings index 116aea3a97..abc42ff2d4 100644 --- a/Mixin/Resources/es.lproj/Localizable.strings +++ b/Mixin/Resources/es.lproj/Localizable.strings @@ -304,7 +304,7 @@ "deposit_tip_eth" = "Esta dirección admite todos los tokens ERC-20, como ETH, XIN, etc."; "deposit_tip_trx" = "Esta dirección admite todos los tokens TRC-10 y TRC-20."; "deselect_all" = "Deselect All"; -"designated_time_frame" = "Designated time frame"; +"designated_time_period" = "Designated time period"; "desktop_on_hint" = "Tienes tu escritorio conectado"; "desktop_upgrade" = "Actualiza Mixin Messenger Desktop a la última versión."; "detect_qr_tip" = "Se ha detectado un código QR de Mixin, toca para reconocer"; diff --git a/Mixin/Resources/ja.lproj/Localizable.strings b/Mixin/Resources/ja.lproj/Localizable.strings index 1fad77e7b2..5c21274329 100644 --- a/Mixin/Resources/ja.lproj/Localizable.strings +++ b/Mixin/Resources/ja.lproj/Localizable.strings @@ -304,7 +304,7 @@ "deposit_tip_eth" = "このアドレスはETHやXINなど全てのERC-20トークンをサポートしています"; "deposit_tip_trx" = "このアドレスは全てのTRC-10/TRC-20トークンをサポートしています"; "deselect_all" = "Deselect All"; -"designated_time_frame" = "Designated time frame"; +"designated_time_period" = "Designated time period"; "desktop_on_hint" = "デスクトップにログインしています"; "desktop_upgrade" = "デスクトップ版Mixinを最新バージョンにアップデートしてください"; "detect_qr_tip" = "Mixin QRコードを検出しました、タップしてアクセスします"; diff --git a/Mixin/Resources/ru.lproj/Localizable.strings b/Mixin/Resources/ru.lproj/Localizable.strings index a38984e10f..bcd26c7f2d 100644 --- a/Mixin/Resources/ru.lproj/Localizable.strings +++ b/Mixin/Resources/ru.lproj/Localizable.strings @@ -304,7 +304,7 @@ "deposit_tip_eth" = "Этот адрес поддерживает все токены ERC-20, такие как ETH, XIN и т. д."; "deposit_tip_trx" = "Этот адрес поддерживает все токены TRC-10 и TRC-20."; "deselect_all" = "Deselect All"; -"designated_time_frame" = "Designated time frame"; +"designated_time_period" = "Designated time period"; "desktop_on_hint" = "Вы вошли в свой рабочий стол"; "desktop_upgrade" = "Пожалуйста, обновите Mixin Messenger Desktop до последней версии."; "detect_qr_tip" = "Обнаружен QR-код Mixin, нажмите, чтобы распознать"; diff --git a/Mixin/Resources/zh-Hans.lproj/Localizable.strings b/Mixin/Resources/zh-Hans.lproj/Localizable.strings index 760a8d6b87..d1b1c26e48 100644 --- a/Mixin/Resources/zh-Hans.lproj/Localizable.strings +++ b/Mixin/Resources/zh-Hans.lproj/Localizable.strings @@ -304,7 +304,7 @@ "deposit_tip_eth" = "支持所有符合 ERC-20 标准的代币,例如 ETH、XIN 等。"; "deposit_tip_trx" = "支持 TRX 和所有符合 TRC-10、TRC-20 标准的代币。"; "deselect_all" = "取消所有选择"; -"designated_time_frame" = "指定时间段"; +"designated_time_period" = "指定时间段"; "desktop_on_hint" = "桌面版已登入。"; "desktop_upgrade" = "请升级 Mixin Messenger 桌面端至最新版!"; "detect_qr_tip" = "检测到一个 Mixin 二维码,点击识别"; diff --git a/Mixin/Resources/zh-Hant.lproj/Localizable.strings b/Mixin/Resources/zh-Hant.lproj/Localizable.strings index 42293cbbd7..8db0fa3697 100644 --- a/Mixin/Resources/zh-Hant.lproj/Localizable.strings +++ b/Mixin/Resources/zh-Hant.lproj/Localizable.strings @@ -304,7 +304,7 @@ "deposit_tip_eth" = "支援所有符合 ERC-20 標準的代幣,例如 ETH、XIN 等。"; "deposit_tip_trx" = "支援 TRX 和所有符合 TRC-10、TRC-20 標準的代幣。"; "deselect_all" = "取消所有選擇"; -"designated_time_frame" = "指定時間段"; +"designated_time_period" = "指定時間段"; "desktop_on_hint" = "桌面版已登入。"; "desktop_upgrade" = "請升級 Mixin Messenger 桌面端至最新版!"; "detect_qr_tip" = "檢測到一個 Mixin 二維碼,點選識別"; diff --git a/Mixin/UserInterface/Controllers/DeviceTransfer/View/DeviceTransferDateSelectionView.xib b/Mixin/UserInterface/Controllers/DeviceTransfer/View/DeviceTransferDateSelectionView.xib index d2ae061bfe..fb97ec22f9 100644 --- a/Mixin/UserInterface/Controllers/DeviceTransfer/View/DeviceTransferDateSelectionView.xib +++ b/Mixin/UserInterface/Controllers/DeviceTransfer/View/DeviceTransferDateSelectionView.xib @@ -57,7 +57,7 @@ - +