diff --git a/builds/esp_idf/ESP_IDF_COMPONENT_SOURCES.cmake b/builds/esp_idf/ESP_IDF_COMPONENT_SOURCES.cmake index 6436ee62..3e1aa62f 100644 --- a/builds/esp_idf/ESP_IDF_COMPONENT_SOURCES.cmake +++ b/builds/esp_idf/ESP_IDF_COMPONENT_SOURCES.cmake @@ -164,6 +164,7 @@ set(TRAILMATE_ESP_IDF_UI_SHARED_SOURCES "${TRAILMATE_ROOT}/modules/chat_presentation_adapters/src/chat_conversation_mapper.cpp" "${TRAILMATE_ROOT}/modules/chat_presentation_adapters/src/chat_message_mapper.cpp" "${TRAILMATE_ROOT}/modules/ui_chat_runtime/src/chat_delivery_action_port_adapter.cpp" + "${TRAILMATE_ROOT}/modules/ui_chat_runtime/src/chat_delivery_feedback_controller.cpp" "${TRAILMATE_ROOT}/modules/ui_chat_runtime/src/chat_delivery_event_projection_adapter.cpp" "${TRAILMATE_ROOT}/modules/ui_chat_runtime/src/chat_page_runtime_event_pump.cpp" "${TRAILMATE_ROOT}/modules/ui_gps_runtime/src/gps_page_runtime_pump.cpp" @@ -228,7 +229,6 @@ set(TRAILMATE_ESP_IDF_UI_SHARED_SOURCES "${TRAILMATE_ROOT}/modules/ui_shared/src/ui/screens/chat/chat_page_runtime.cpp" "${TRAILMATE_ROOT}/modules/ui_shared/src/ui/screens/chat/chat_page_shell.cpp" "${TRAILMATE_ROOT}/modules/ui_shared/src/ui/screens/chat/chat_protocol_support.cpp" - "${TRAILMATE_ROOT}/modules/ui_shared/src/ui/screens/chat/chat_send_flow.cpp" "${TRAILMATE_ROOT}/modules/ui_shared/src/ui/screens/chat/chat_team_workflow.cpp" "${TRAILMATE_ROOT}/modules/ui_shared/src/ui/screens/chat/chat_ui_controller.cpp" "${TRAILMATE_ROOT}/modules/ui_shared/src/ui/screens/common/placeholder_page.cpp" diff --git a/cmake/TrailMateLinuxSources.cmake b/cmake/TrailMateLinuxSources.cmake index 24db8747..8cdf1770 100644 --- a/cmake/TrailMateLinuxSources.cmake +++ b/cmake/TrailMateLinuxSources.cmake @@ -330,6 +330,7 @@ set(TRAIL_MATE_LINUX_UI_SHELL_SOURCES "${TRAIL_MATE_UI_SHARED_SRC_ROOT}/ui/presentation_sources/runtime_chat_action_sink.cpp" "${TRAIL_MATE_UI_SHARED_SRC_ROOT}/ui/presentation_sources/chat_presentation_source.cpp" "${TRAIL_MATE_UI_CHAT_RUNTIME_SRC_ROOT}/chat_delivery_action_port_adapter.cpp" + "${TRAIL_MATE_UI_CHAT_RUNTIME_SRC_ROOT}/chat_delivery_feedback_controller.cpp" "${TRAIL_MATE_UI_CHAT_RUNTIME_SRC_ROOT}/chat_delivery_event_projection_adapter.cpp" "${TRAIL_MATE_UI_KEY_VERIFICATION_RUNTIME_SRC_ROOT}/key_verification_action_sink.cpp" "${TRAIL_MATE_UI_KEY_VERIFICATION_RUNTIME_SRC_ROOT}/key_verification_presentation_source.cpp" @@ -376,7 +377,6 @@ set(TRAIL_MATE_LINUX_UI_SHELL_SOURCES "${TRAIL_MATE_UI_SHARED_SRC_ROOT}/ui/screens/chat/chat_page_runtime.cpp" "${TRAIL_MATE_UI_SHARED_SRC_ROOT}/ui/screens/chat/chat_page_shell.cpp" "${TRAIL_MATE_UI_SHARED_SRC_ROOT}/ui/screens/chat/chat_protocol_support.cpp" - "${TRAIL_MATE_UI_SHARED_SRC_ROOT}/ui/screens/chat/chat_send_flow.cpp" "${TRAIL_MATE_UI_SHARED_SRC_ROOT}/ui/screens/chat/chat_team_workflow.cpp" "${TRAIL_MATE_UI_SHARED_SRC_ROOT}/ui/screens/chat/chat_ui_controller.cpp" "${TRAIL_MATE_UI_PRESENTATION_SRC_ROOT}/menu/menu_model.cpp" diff --git a/modules/ui_chat_runtime/include/ui_chat_runtime/chat_delivery_feedback_controller.h b/modules/ui_chat_runtime/include/ui_chat_runtime/chat_delivery_feedback_controller.h new file mode 100644 index 00000000..c7543c19 --- /dev/null +++ b/modules/ui_chat_runtime/include/ui_chat_runtime/chat_delivery_feedback_controller.h @@ -0,0 +1,44 @@ +#pragma once + +#include "chat/domain/chat_types.h" + +#include + +namespace ui_chat_runtime +{ + +class IChatDeliveryFeedbackPort +{ + public: + virtual ~IChatDeliveryFeedbackPort() = default; + + virtual void showChatDeliverySent(chat::MessageId msg_id) = 0; + virtual void showChatDeliveryFailed(chat::MessageId msg_id) = 0; +}; + +class ChatDeliveryFeedbackController final +{ + public: + explicit ChatDeliveryFeedbackController(IChatDeliveryFeedbackPort& port); + + void onChatSendResult(chat::MessageId msg_id, + bool success, + const chat::ChatMessage* message); + void clear(); + + private: + struct RecentResult + { + chat::MessageId msg_id = 0; + bool success = false; + }; + + bool alreadyNotified(chat::MessageId msg_id, bool success) const; + void remember(chat::MessageId msg_id, bool success); + + IChatDeliveryFeedbackPort& port_; + RecentResult recent_[8] = {}; + size_t next_recent_ = 0; +}; + +} // namespace ui_chat_runtime diff --git a/modules/ui_chat_runtime/src/chat_delivery_feedback_controller.cpp b/modules/ui_chat_runtime/src/chat_delivery_feedback_controller.cpp new file mode 100644 index 00000000..f2911909 --- /dev/null +++ b/modules/ui_chat_runtime/src/chat_delivery_feedback_controller.cpp @@ -0,0 +1,65 @@ +#include "ui_chat_runtime/chat_delivery_feedback_controller.h" + +namespace ui_chat_runtime +{ + +ChatDeliveryFeedbackController::ChatDeliveryFeedbackController( + IChatDeliveryFeedbackPort& port) + : port_(port) +{ +} + +void ChatDeliveryFeedbackController::onChatSendResult( + chat::MessageId msg_id, + bool success, + const chat::ChatMessage* message) +{ + if (msg_id == 0 || message == nullptr || message->from != 0) + { + return; + } + if (alreadyNotified(msg_id, success)) + { + return; + } + + remember(msg_id, success); + if (success) + { + port_.showChatDeliverySent(msg_id); + return; + } + port_.showChatDeliveryFailed(msg_id); +} + +void ChatDeliveryFeedbackController::clear() +{ + for (auto& item : recent_) + { + item = RecentResult{}; + } + next_recent_ = 0; +} + +bool ChatDeliveryFeedbackController::alreadyNotified(chat::MessageId msg_id, + bool success) const +{ + for (const auto& item : recent_) + { + if (item.msg_id == msg_id && item.success == success) + { + return true; + } + } + return false; +} + +void ChatDeliveryFeedbackController::remember(chat::MessageId msg_id, + bool success) +{ + recent_[next_recent_] = RecentResult{msg_id, success}; + next_recent_ = (next_recent_ + 1) % + (sizeof(recent_) / sizeof(recent_[0])); +} + +} // namespace ui_chat_runtime diff --git a/modules/ui_chat_runtime/tests/test_chat_delivery_feedback_controller.cpp b/modules/ui_chat_runtime/tests/test_chat_delivery_feedback_controller.cpp new file mode 100644 index 00000000..774e61f5 --- /dev/null +++ b/modules/ui_chat_runtime/tests/test_chat_delivery_feedback_controller.cpp @@ -0,0 +1,79 @@ +#include "ui_chat_runtime/chat_delivery_feedback_controller.h" + +#include + +namespace +{ + +class FakeFeedbackPort final + : public ::ui_chat_runtime::IChatDeliveryFeedbackPort +{ + public: + void showChatDeliverySent(chat::MessageId msg_id) override + { + ++sent_count; + last_id = msg_id; + } + + void showChatDeliveryFailed(chat::MessageId msg_id) override + { + ++failed_count; + last_id = msg_id; + } + + int sent_count = 0; + int failed_count = 0; + chat::MessageId last_id = 0; +}; + +chat::ChatMessage outgoing(chat::MessageId id) +{ + chat::ChatMessage message; + message.msg_id = id; + message.from = 0; + return message; +} + +chat::ChatMessage incoming(chat::MessageId id) +{ + chat::ChatMessage message = outgoing(id); + message.from = 0x12345678; + return message; +} + +} // namespace + +int main() +{ + FakeFeedbackPort port; + ::ui_chat_runtime::ChatDeliveryFeedbackController controller(port); + + chat::ChatMessage sent = outgoing(100); + controller.onChatSendResult(100, true, &sent); + assert(port.sent_count == 1); + assert(port.failed_count == 0); + assert(port.last_id == 100); + + controller.onChatSendResult(100, true, &sent); + assert(port.sent_count == 1); + + chat::ChatMessage failed = outgoing(101); + controller.onChatSendResult(101, false, &failed); + assert(port.sent_count == 1); + assert(port.failed_count == 1); + assert(port.last_id == 101); + + chat::ChatMessage rx = incoming(102); + controller.onChatSendResult(102, true, &rx); + controller.onChatSendResult(103, false, nullptr); + controller.onChatSendResult(0, false, &failed); + assert(port.sent_count == 1); + assert(port.failed_count == 1); + + controller.clear(); + controller.onChatSendResult(100, true, &sent); + assert(port.sent_count == 2); + assert(port.last_id == 100); + + return 0; +} diff --git a/modules/ui_shared/include/ui/screens/chat/chat_compose_components.h b/modules/ui_shared/include/ui/screens/chat/chat_compose_components.h index 4d1e8b34..db817a2e 100644 --- a/modules/ui_shared/include/ui/screens/chat/chat_compose_components.h +++ b/modules/ui_shared/include/ui/screens/chat/chat_compose_components.h @@ -17,11 +17,6 @@ class ImeWidget; } // namespace widgets } // namespace ui -namespace chat -{ -class ChatService; -} - namespace chat::ui { @@ -44,11 +39,6 @@ class ChatComposeScreen std::string getText() const; void clearText(); - void beginSend(chat::ChatService* service, - chat::MessageId msg_id, - void (*done_cb)(bool ok, bool timeout, void*), - void* user_data); - void setActionCallback(void (*cb)(ActionIntent intent, void*), void* user_data); void setBackCallback(void (*cb)(void*), void* user_data); @@ -62,10 +52,6 @@ class ChatComposeScreen private: chat::ConversationId conv_; - void setEnabled(bool enabled); - lv_obj_t* toastHost() const; - void showSendToast(bool ok, bool timeout, const char* message); - void (*action_cb_)(ActionIntent intent, void*) = nullptr; void* action_cb_user_data_ = nullptr; @@ -74,29 +60,15 @@ class ChatComposeScreen struct Impl; struct LifetimeGuard; - struct DonePayload; Impl* impl_ = nullptr; void init_topbar(); void refresh_len(); - void finishSend(bool ok, bool timeout, const char* message); - void setSendingText(const char* text); - - static lv_timer_t* add_timer(Impl* impl, lv_timer_cb_t cb, uint32_t period_ms, void* user_data); - static void clear_timers(Impl* impl); - static void async_done_cb(void* user_data); - static void schedule_done_async(LifetimeGuard* guard, - void (*done_cb)(bool ok, bool timeout, void*), - void* user_data, - bool ok, - bool timeout); static void on_root_deleted(lv_event_t* e); static void on_action_click(lv_event_t* e); static void on_text_changed(lv_event_t* e); static void on_key(lv_event_t* e); static void on_back(void* user_data); - static void on_send_timer(lv_timer_t* timer); - ::ui::widgets::ImeWidget* ime_widget_ = nullptr; }; diff --git a/modules/ui_shared/include/ui/screens/chat/chat_send_flow.h b/modules/ui_shared/include/ui/screens/chat/chat_send_flow.h deleted file mode 100644 index 016ceff8..00000000 --- a/modules/ui_shared/include/ui/screens/chat/chat_send_flow.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once - -#include "chat/domain/chat_types.h" -#include - -namespace chat -{ -class ChatService; -} - -namespace chat::ui -{ -class ChatComposeScreen; -} - -namespace chat::ui::send_flow -{ - -bool begin_local_text_send(ChatComposeScreen* compose, - chat::ChatService* service, - const chat::ConversationId& conv, - const std::string& text, - void (*done_cb)(bool ok, bool timeout, void*), - void* user_data); - -} // namespace chat::ui::send_flow diff --git a/modules/ui_shared/include/ui/screens/chat/chat_ui_controller.h b/modules/ui_shared/include/ui/screens/chat/chat_ui_controller.h index 1ec1ca13..73e33418 100644 --- a/modules/ui_shared/include/ui/screens/chat/chat_ui_controller.h +++ b/modules/ui_shared/include/ui/screens/chat/chat_ui_controller.h @@ -127,7 +127,6 @@ class UiController : public IChatUiRefreshSink void handleChannelSelected(const chat::ConversationId& conv); void handleSendMessage(const std::string& text); void handleComposeSendDone(bool ok, bool timeout); - static void handleComposeSendDoneCallback(bool ok, bool timeout, void* user_data); void refreshUnreadCounts(); void refreshUnreadCounts(bool force_reload); void cleanupComposeIme(); diff --git a/modules/ui_shared/include/ui/screens/chat_watch/chat_compose_components_watch.h b/modules/ui_shared/include/ui/screens/chat_watch/chat_compose_components_watch.h index 4a05c4eb..d8572743 100644 --- a/modules/ui_shared/include/ui/screens/chat_watch/chat_compose_components_watch.h +++ b/modules/ui_shared/include/ui/screens/chat_watch/chat_compose_components_watch.h @@ -14,11 +14,6 @@ class ImeWidget; } // namespace widgets } // namespace ui -namespace chat -{ -class ChatService; -} - namespace input { class MorseEngine; @@ -46,11 +41,6 @@ class ChatComposeScreen std::string getText() const; void clearText(); - void beginSend(chat::ChatService* service, - chat::MessageId msg_id, - void (*done_cb)(bool ok, bool timeout, void*), - void* user_data); - void setActionCallback(void (*cb)(ActionIntent intent, void*), void* user_data); void setBackCallback(void (*cb)(void*), void* user_data); diff --git a/modules/ui_shared/src/ui/screens/chat/chat_compose_components.cpp b/modules/ui_shared/src/ui/screens/chat/chat_compose_components.cpp index a392500a..5f61c059 100644 --- a/modules/ui_shared/src/ui/screens/chat/chat_compose_components.cpp +++ b/modules/ui_shared/src/ui/screens/chat/chat_compose_components.cpp @@ -9,15 +9,9 @@ #include "ui/localization.h" #include "ui/ui_common.h" #include "ui/widgets/ime/ime_widget.h" -#include "ui/widgets/toast/toast_widget.h" -#include "chat/usecase/chat_service.h" -#include "sys/clock.h" - -#include #include // snprintf #include -#include #ifndef CHAT_COMPOSE_LOG_ENABLE #define CHAT_COMPOSE_LOG_ENABLE 0 @@ -33,28 +27,10 @@ namespace chat::ui { static constexpr size_t kMaxInputBytes = 233; -static constexpr uint32_t kSendTimeoutMs = 3000; - -enum class SendState -{ - Idle, - Waiting -}; struct ChatComposeScreen::LifetimeGuard { bool alive = false; - int pending_async = 0; - ChatComposeScreen* owner = nullptr; -}; - -struct ChatComposeScreen::DonePayload -{ - LifetimeGuard* guard = nullptr; - void (*done_cb)(bool ok, bool timeout, void*) = nullptr; - void* user_data = nullptr; - bool ok = false; - bool timeout = false; }; struct ChatComposeScreen::Impl @@ -63,7 +39,6 @@ struct ChatComposeScreen::Impl chat::ui::compose::layout::Widgets w; chat::ui::compose::input::State input_state; LifetimeGuard* guard = nullptr; - std::vector timers; struct ActionContext { ChatComposeScreen* screen = nullptr; @@ -72,13 +47,6 @@ struct ChatComposeScreen::Impl ActionContext send_ctx; ActionContext position_ctx; ActionContext cancel_ctx; - lv_timer_t* send_timer = nullptr; - uint32_t send_start_ms = 0; - chat::MessageId pending_msg_id = 0; - chat::ChatService* send_service = nullptr; - SendState send_state = SendState::Idle; - void (*send_done_cb)(bool ok, bool timeout, void*) = nullptr; - void* send_done_user_data = nullptr; }; static void set_btn_label_white(lv_obj_t* btn) @@ -127,111 +95,6 @@ static void refresh_textarea_content_font(lv_obj_t* textarea) ::ui::fonts::apply_content_font(textarea, text ? text : "", ::ui::fonts::ui_chrome_font()); } -void ChatComposeScreen::setEnabled(bool enabled) -{ - if (!impl_) return; - auto set = [enabled](lv_obj_t* o) - { - if (!o) return; - if (enabled) lv_obj_clear_state(o, LV_STATE_DISABLED); - else lv_obj_add_state(o, LV_STATE_DISABLED); - }; - - set(impl_->w.send_btn); - set(impl_->w.position_btn); - set(impl_->w.cancel_btn); - set(impl_->w.textarea); - if (impl_->w.top_bar.back_btn) set(impl_->w.top_bar.back_btn); -} - -lv_obj_t* ChatComposeScreen::toastHost() const -{ - if (!impl_ || !impl_->w.container) return lv_screen_active(); - lv_obj_t* p = lv_obj_get_parent(impl_->w.container); - return p ? p : lv_screen_active(); -} - -void ChatComposeScreen::showSendToast(bool ok, bool timeout, const char* message) -{ - ::ui::widgets::Toast::Type type = ::ui::widgets::Toast::Type::Info; - if (timeout) type = ::ui::widgets::Toast::Type::Error; - else if (ok) type = ::ui::widgets::Toast::Type::Success; - else type = ::ui::widgets::Toast::Type::Error; - - ::ui::widgets::Toast::show(toastHost(), ::ui::i18n::tr(message ? message : ""), type); -} - -lv_timer_t* ChatComposeScreen::add_timer(ChatComposeScreen::Impl* impl, - lv_timer_cb_t cb, - uint32_t period_ms, - void* user_data) -{ - if (!impl) return nullptr; - lv_timer_t* timer = lv_timer_create(cb, period_ms, user_data); - if (timer) - { - impl->timers.push_back(timer); - } - return timer; -} - -void ChatComposeScreen::clear_timers(ChatComposeScreen::Impl* impl) -{ - if (!impl) return; - for (auto* timer : impl->timers) - { - if (timer) - { - lv_timer_del(timer); - } - } - impl->timers.clear(); - impl->send_timer = nullptr; -} - -void ChatComposeScreen::async_done_cb(void* user_data) -{ - auto* payload = static_cast(user_data); - if (!payload) - { - return; - } - LifetimeGuard* guard = payload->guard; - if (guard && guard->alive && payload->done_cb) - { - payload->done_cb(payload->ok, payload->timeout, payload->user_data); - } - if (guard) - { - guard->pending_async = std::max(0, guard->pending_async - 1); - if (!guard->alive && guard->pending_async == 0) - { - delete guard; - } - } - delete payload; -} - -void ChatComposeScreen::schedule_done_async(LifetimeGuard* guard, - void (*done_cb)(bool ok, bool timeout, void*), - void* user_data, - bool ok, - bool timeout) -{ - if (!guard || !done_cb) - { - return; - } - auto* payload = new DonePayload(); - payload->guard = guard; - payload->done_cb = done_cb; - payload->user_data = user_data; - payload->ok = ok; - payload->timeout = timeout; - guard->pending_async++; - lv_async_call(async_done_cb, payload); -} - void ChatComposeScreen::on_root_deleted(lv_event_t* e) { auto* screen = static_cast(lv_event_get_user_data(e)); @@ -244,10 +107,6 @@ void ChatComposeScreen::on_root_deleted(lv_event_t* e) { impl->guard->alive = false; } - clear_timers(impl); - impl->send_service = nullptr; - impl->send_done_cb = nullptr; - impl->send_done_user_data = nullptr; screen->action_cb_ = nullptr; screen->action_cb_user_data_ = nullptr; screen->back_cb_ = nullptr; @@ -257,10 +116,7 @@ void ChatComposeScreen::on_root_deleted(lv_event_t* e) LifetimeGuard* guard = impl->guard; screen->impl_ = nullptr; delete impl; - if (guard && guard->pending_async == 0) - { - delete guard; - } + delete guard; } ChatComposeScreen::ChatComposeScreen(lv_obj_t* parent, chat::ConversationId conv) @@ -279,8 +135,6 @@ ChatComposeScreen::ChatComposeScreen(lv_obj_t* parent, chat::ConversationId conv impl_ = new Impl(); impl_->guard = new LifetimeGuard(); impl_->guard->alive = true; - impl_->guard->pending_async = 0; - impl_->guard->owner = this; using namespace chat::ui::compose; @@ -443,66 +297,6 @@ void ChatComposeScreen::clearText() refresh_len(); } -void ChatComposeScreen::beginSend(chat::ChatService* service, - chat::MessageId msg_id, - void (*done_cb)(bool ok, bool timeout, void*), - void* user_data) -{ - if (!impl_ || !impl_->guard || !impl_->guard->alive) return; - if (impl_->send_state == SendState::Waiting) return; - - impl_->send_service = service; - impl_->pending_msg_id = msg_id; - impl_->send_start_ms = sys::millis_now(); - impl_->send_state = SendState::Waiting; - impl_->send_done_cb = done_cb; - impl_->send_done_user_data = user_data; - - setEnabled(false); - - // Optional lightweight toast hint while sending. - ::ui::widgets::Toast::show(toastHost(), ::ui::i18n::tr("Sending..."), ::ui::widgets::Toast::Type::Info); - - if (impl_->send_timer) - { - clear_timers(impl_); - } - - impl_->send_timer = add_timer(impl_, on_send_timer, 150, this); - if (!impl_->send_timer) - { - finishSend(false, false, "Send failed"); - return; - } - - if (impl_->pending_msg_id == 0 || !impl_->send_service) - { - finishSend(false, false, "Send failed"); - return; - } -} - -void ChatComposeScreen::finishSend(bool ok, bool timeout, const char* message) -{ - if (!impl_ || !impl_->guard || !impl_->guard->alive) return; - - showSendToast(ok, timeout, message); - setEnabled(true); - - auto* done_cb = impl_->send_done_cb; - void* done_user = impl_->send_done_user_data; - - clear_timers(impl_); - impl_->send_done_cb = nullptr; - impl_->send_done_user_data = nullptr; - impl_->send_service = nullptr; - impl_->pending_msg_id = 0; - impl_->send_state = SendState::Idle; - - if (done_cb) - schedule_done_async(impl_->guard, done_cb, done_user, ok, timeout); -} - void ChatComposeScreen::setActionCallback(void (*cb)(ActionIntent intent, void*), void* user_data) { action_cb_ = cb; @@ -640,45 +434,6 @@ void ChatComposeScreen::on_key(lv_event_t* e) } } -void ChatComposeScreen::on_send_timer(lv_timer_t* timer) -{ - auto* screen = static_cast(lv_timer_get_user_data(timer)); - if (!screen || !screen->impl_ || !screen->impl_->guard || !screen->impl_->guard->alive) - { - return; - } - auto* impl = screen->impl_; - - uint32_t now = sys::millis_now(); - if (impl->send_state == SendState::Waiting) - { - if (!impl->send_service || impl->pending_msg_id == 0) - { - screen->finishSend(false, false, "Send failed"); - } - else - { - const ChatMessage* msg = impl->send_service->getMessage(impl->pending_msg_id); - if (msg) - { - if (msg->status == MessageStatus::Sent) - { - screen->finishSend(true, false, "Sent"); - } - else if (msg->status == MessageStatus::Failed) - { - screen->finishSend(false, false, "Failed"); - } - } - } - if (impl->send_state == SendState::Waiting && - (now - impl->send_start_ms >= kSendTimeoutMs)) - { - screen->finishSend(false, true, "No response"); - } - } -} - } // namespace chat::ui #endif diff --git a/modules/ui_shared/src/ui/screens/chat/chat_send_flow.cpp b/modules/ui_shared/src/ui/screens/chat/chat_send_flow.cpp deleted file mode 100644 index e2970295..00000000 --- a/modules/ui_shared/src/ui/screens/chat/chat_send_flow.cpp +++ /dev/null @@ -1,26 +0,0 @@ -#include "ui/screens/chat/chat_send_flow.h" - -#include "chat/usecase/chat_service.h" -#include "ui/screens/chat/chat_compose_components.h" - -namespace chat::ui::send_flow -{ - -bool begin_local_text_send(ChatComposeScreen* compose, - chat::ChatService* service, - const chat::ConversationId& conv, - const std::string& text, - void (*done_cb)(bool ok, bool timeout, void*), - void* user_data) -{ - if (!compose || !service || text.empty()) - { - return false; - } - - const chat::MessageId msg_id = service->sendText(conv.channel, text, conv.peer); - compose->beginSend(service, msg_id, done_cb, user_data); - return true; -} - -} // namespace chat::ui::send_flow diff --git a/modules/ui_shared/src/ui/screens/chat/chat_ui_controller.cpp b/modules/ui_shared/src/ui/screens/chat/chat_ui_controller.cpp index 22d9c1c5..5865adb5 100644 --- a/modules/ui_shared/src/ui/screens/chat/chat_ui_controller.cpp +++ b/modules/ui_shared/src/ui/screens/chat/chat_ui_controller.cpp @@ -16,7 +16,6 @@ #include "ui/assets/fonts/font_utils.h" #include "ui/localization.h" #include "ui/screens/chat/chat_protocol_support.h" -#include "ui/screens/chat/chat_send_flow.h" #include "ui/screens/chat/chat_team_workflow.h" #include "ui/ui_common.h" #include "ui/widgets/ime/ime_widget.h" @@ -823,26 +822,12 @@ void UiController::handleSendMessage(const std::string& text) return; } - if (state_ == State::Compose && compose_) - { - if (chat::ui::send_flow::begin_local_text_send(compose_.get(), - &service_, - current_conv_, - text, - handleComposeSendDoneCallback, - this)) - { - return; - } - - ::ui::SystemNotification::show("Send failed", 2000); - handleComposeSendDone(false, false); - return; - } - const ::ui::UiActionResult result = chat_model_.sendMessage(text.c_str()); - ::ui::SystemNotification::show(result.ok ? "Sent" : local_text_send_failure_message(result), - result.ok ? 1400 : 2000); + if (!result.ok) + { + ::ui::SystemNotification::show(local_text_send_failure_message(result), + 2000); + } handleComposeSendDone(result.ok, false); } @@ -856,15 +841,6 @@ void UiController::handleComposeSendDone(bool ok, bool timeout) } } -void UiController::handleComposeSendDoneCallback(bool ok, bool timeout, void* user_data) -{ - auto* controller = static_cast(user_data); - if (controller) - { - controller->handleComposeSendDone(ok, timeout); - } -} - void UiController::refreshUnreadCounts() { refreshUnreadCounts(true); diff --git a/modules/ui_shared/src/ui/screens/chat_watch/chat_compose_components_watch.cpp b/modules/ui_shared/src/ui/screens/chat_watch/chat_compose_components_watch.cpp index 4b14c5e5..8b5432a4 100644 --- a/modules/ui_shared/src/ui/screens/chat_watch/chat_compose_components_watch.cpp +++ b/modules/ui_shared/src/ui/screens/chat_watch/chat_compose_components_watch.cpp @@ -93,17 +93,6 @@ void ChatComposeScreen::clearText() selected_text_.clear(); } -void ChatComposeScreen::beginSend(chat::ChatService*, - chat::MessageId, - void (*done_cb)(bool ok, bool timeout, void*), - void* user_data) -{ - if (done_cb) - { - done_cb(true, false, user_data); - } -} - void ChatComposeScreen::setActionCallback(void (*cb)(ActionIntent intent, void*), void* user_data) { action_cb_ = cb; diff --git a/modules/ui_shared/src/ui/screens/contacts/contacts_page_components.cpp b/modules/ui_shared/src/ui/screens/contacts/contacts_page_components.cpp index 8f3481e4..f9f12c50 100644 --- a/modules/ui_shared/src/ui/screens/contacts/contacts_page_components.cpp +++ b/modules/ui_shared/src/ui/screens/contacts/contacts_page_components.cpp @@ -25,7 +25,6 @@ #include "ui/screens/chat/chat_conversation_components.h" #include "ui/screens/chat/chat_page_shell.h" #include "ui/screens/chat/chat_protocol_support.h" -#include "ui/screens/chat/chat_send_flow.h" #include "ui/screens/contacts/contacts_page_input.h" #include "ui/screens/contacts/contacts_page_layout.h" #include "ui/screens/contacts/contacts_page_styles.h" @@ -148,7 +147,6 @@ static void open_chat_compose(); static void close_chat_compose(); static void on_compose_action(chat::ui::ChatComposeScreen::ActionIntent intent, void* user_data); static void on_compose_back(void* user_data); -static void on_compose_send_done(bool ok, bool timeout, void* user_data); [[maybe_unused]] static void open_team_conversation(); static void close_team_conversation(); static void refresh_team_conversation(); @@ -622,6 +620,43 @@ static const char* team_action_failure_message( return default_message; } +static const char* local_text_failure_message(chat::MeshOperationFailure failure) +{ + switch (failure) + { + case chat::MeshOperationFailure::PeerKeyMissing: + return "Peer key missing"; + case chat::MeshOperationFailure::ChannelKeyMissing: + return "Channel key missing"; + case chat::MeshOperationFailure::TxDisabled: + return "TX disabled"; + case chat::MeshOperationFailure::RadioOffline: + return "Radio offline"; + case chat::MeshOperationFailure::DutyCycleLimited: + return "TX rate limited"; + case chat::MeshOperationFailure::RadioTxFailed: + return "Radio TX failed"; + case chat::MeshOperationFailure::LocalIdentityMissing: + return "Identity missing"; + case chat::MeshOperationFailure::Busy: + return "Radio busy"; + case chat::MeshOperationFailure::Unsupported: + return "Chat unsupported"; + case chat::MeshOperationFailure::InvalidInput: + return "Invalid message"; + case chat::MeshOperationFailure::NotReady: + return "Mesh not ready"; + case chat::MeshOperationFailure::EncodeFailed: + return "Packet build failed"; + case chat::MeshOperationFailure::CryptoFailed: + return "Signature failed"; + case chat::MeshOperationFailure::None: + case chat::MeshOperationFailure::Unknown: + break; + } + return "Send failed"; +} + static uint32_t current_timestamp_seconds() { uint32_t ts = sys::epoch_seconds_now(); @@ -1566,13 +1601,18 @@ static void on_compose_action(chat::ui::ChatComposeScreen::ActionIntent intent, { if (g_contacts_state.chat_service) { - const chat::ConversationId conv(s_compose_channel, s_compose_peer_id, s_compose_protocol); - chat::ui::send_flow::begin_local_text_send(g_contacts_state.compose_screen, - g_contacts_state.chat_service, - conv, - text, - on_compose_send_done, - nullptr); + const chat::MeshSendResult result = + g_contacts_state.chat_service->sendTextDetailed( + s_compose_channel, + text, + s_compose_peer_id); + if (!result.ok || result.msg_id == 0) + { + ::ui::SystemNotification::show( + local_text_failure_message(result.failure), + 2000); + } + close_chat_compose(); return; } } @@ -1585,16 +1625,6 @@ static void on_compose_back(void* /*user_data*/) close_chat_compose(); } -static void on_compose_send_done(bool ok, bool /*timeout*/, void* /*user_data*/) -{ - (void)ok; - close_chat_compose(); - if (g_contacts_state.conversation_screen) - { - refresh_team_conversation(); - } -} - static void refresh_team_conversation() { if (!g_contacts_state.conversation_screen || !is_team_available()) diff --git a/platform/esp/arduino_common/src/app_event_runtime_support.cpp b/platform/esp/arduino_common/src/app_event_runtime_support.cpp index 82c60b04..1f73a40b 100644 --- a/platform/esp/arduino_common/src/app_event_runtime_support.cpp +++ b/platform/esp/arduino_common/src/app_event_runtime_support.cpp @@ -6,6 +6,7 @@ #include "app/app_facades.h" #include "board/BoardBase.h" #include "chat/usecase/contact_service.h" +#include "chat/usecase/chat_service.h" #include "platform/esp/arduino_common/app_runtime_support.h" #include "platform/esp/arduino_common/hostlink/hostlink_bridge_radio.h" #include "platform/ui/settings_store.h" @@ -15,6 +16,7 @@ #include "ui/localization.h" #include "ui/screens/team/team_page_shell.h" #include "ui/widgets/system_notification.h" +#include "ui_chat_runtime/chat_delivery_feedback_controller.h" namespace platform::esp::arduino_common { @@ -44,6 +46,30 @@ bool isTeamRuntimeEvent(sys::EventType type) type == sys::EventType::TeamError; } +class SystemNotificationChatDeliveryFeedbackPort final + : public ::ui_chat_runtime::IChatDeliveryFeedbackPort +{ + public: + void showChatDeliverySent(chat::MessageId msg_id) override + { + (void)msg_id; + ::ui::SystemNotification::show(::ui::i18n::tr("Sent"), 1400); + } + + void showChatDeliveryFailed(chat::MessageId msg_id) override + { + (void)msg_id; + ::ui::SystemNotification::show(::ui::i18n::tr("Send failed"), 2000); + } +}; + +::ui_chat_runtime::ChatDeliveryFeedbackController& chatDeliveryFeedback() +{ + static SystemNotificationChatDeliveryFeedbackPort port; + static ::ui_chat_runtime::ChatDeliveryFeedbackController controller(port); + return controller; +} + void triggerMessageFeedback(app::IAppFacade& app_context) { BoardBase* board = app_context.getBoard(); @@ -138,6 +164,15 @@ void handleTeamChatNotification(app::IAppFacade& app_context, const sys::TeamCha ::ui::SystemNotification::show(notice.c_str(), 3000); } +void handleChatSendResultFeedback(app::IAppFacade& app_context, + const sys::ChatSendResultEvent& event) +{ + chatDeliveryFeedback().onChatSendResult( + event.msg_id, + event.success, + app_context.getChatService().getMessage(event.msg_id)); +} + void tickUiRuntime(app::IAppFacade& app_context) { platform::esp::arduino_common::tickRuntime(app_context); @@ -160,6 +195,11 @@ bool handleUiEvent(app::IAppFacade& app_context, sys::Event* event) switch (event->type) { + case sys::EventType::ChatSendResult: + handleChatSendResultFeedback( + app_context, + *static_cast(event)); + break; case sys::EventType::ChatNewMessage: { auto* msg_event = static_cast(event);