fix(desktop-chat): estabiliza janelas e melhora multi-conversas
All checks were successful
All checks were successful
This commit is contained in:
parent
380b2e44e9
commit
3f9461a18f
3 changed files with 134 additions and 84 deletions
|
|
@ -1000,35 +1000,15 @@ async fn process_chat_update(
|
|||
}
|
||||
}
|
||||
|
||||
// Se ha multiplas sessoes ativas, usar o hub; senao, abrir janela do chat individual
|
||||
// SIMPLIFICADO: Removido inner_size() que bloqueava a UI thread
|
||||
// Se ha multiplas sessoes ativas, usar o hub; senao, abrir janela do chat individual.
|
||||
//
|
||||
// Importante (UX): em multiplas sessoes, NAO fecha a janela ativa quando chega mensagem em outra conversa.
|
||||
// O hub + badge/notificacao sinalizam novas mensagens e o usuario decide quando alternar.
|
||||
if current_sessions.len() > 1 {
|
||||
// Multiplas sessoes - usar hub window
|
||||
// Primeiro, fechar todas as janelas individuais de chat para evitar sobreposicao
|
||||
for session in ¤t_sessions {
|
||||
let label = format!("chat-{}", session.ticket_id);
|
||||
if let Some(window) = app.get_webview_window(&label) {
|
||||
let _ = window.close();
|
||||
}
|
||||
}
|
||||
|
||||
if app.get_webview_window(HUB_WINDOW_LABEL).is_none() {
|
||||
// Hub nao existe - criar minimizado
|
||||
let _ = open_hub_window(app);
|
||||
} else {
|
||||
// Hub ja existe - mostrar e trazer para frente
|
||||
if let Some(hub) = app.get_webview_window(HUB_WINDOW_LABEL) {
|
||||
let _ = hub.show();
|
||||
let _ = hub.set_focus();
|
||||
let _ = hub.unminimize();
|
||||
}
|
||||
}
|
||||
let _ = open_hub_window(app);
|
||||
} else {
|
||||
// Uma sessao - abrir janela individual
|
||||
// Fechar o Hub se estiver aberto (nao precisa mais quando ha apenas 1 chat)
|
||||
if let Some(hub) = app.get_webview_window(HUB_WINDOW_LABEL) {
|
||||
let _ = hub.close();
|
||||
}
|
||||
// Uma sessao - nao precisa de hub
|
||||
let _ = close_hub_window(app);
|
||||
|
||||
// Fallback: se nao conseguimos detectar delta, pega a sessao com mais unread e mais recente.
|
||||
let session_to_show = if best_delta > 0 {
|
||||
|
|
@ -1041,19 +1021,9 @@ async fn process_chat_update(
|
|||
})
|
||||
};
|
||||
|
||||
// Mostrar janela de chat (se nao existe, cria minimizada; se existe, traz para frente)
|
||||
// Mostrar janela de chat (sempre minimizada/nao intrusiva)
|
||||
if let Some(session) = session_to_show {
|
||||
let label = format!("chat-{}", session.ticket_id);
|
||||
if let Some(window) = app.get_webview_window(&label) {
|
||||
// Janela ja existe - mostrar e trazer para frente
|
||||
let _ = window.show();
|
||||
let _ = window.set_focus();
|
||||
// Garantir que fique visivel mesmo se estava minimizada na taskbar
|
||||
let _ = window.unminimize();
|
||||
} else {
|
||||
// Criar nova janela ja minimizada (menos intrusivo)
|
||||
let _ = open_chat_window_internal(app, &session.ticket_id, session.ticket_ref, true);
|
||||
}
|
||||
let _ = open_chat_window_internal(app, &session.ticket_id, session.ticket_ref, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1087,6 +1057,9 @@ async fn process_chat_update(
|
|||
// WINDOW MANAGEMENT
|
||||
// ============================================================================
|
||||
|
||||
// Serializa operacoes de janela para evitar race/deadlock no Windows (winit/WebView2).
|
||||
static WINDOW_OP_LOCK: Lazy<Mutex<()>> = Lazy::new(|| Mutex::new(()));
|
||||
|
||||
fn resolve_chat_window_position(
|
||||
app: &tauri::AppHandle,
|
||||
window: Option<&tauri::WebviewWindow>,
|
||||
|
|
@ -1128,21 +1101,36 @@ fn resolve_chat_window_position(
|
|||
}
|
||||
|
||||
fn open_chat_window_internal(app: &tauri::AppHandle, ticket_id: &str, ticket_ref: u64, start_minimized: bool) -> Result<(), String> {
|
||||
let _guard = WINDOW_OP_LOCK.lock();
|
||||
open_chat_window_with_state(app, ticket_id, ticket_ref, start_minimized)
|
||||
}
|
||||
|
||||
/// Abre janela de chat com estado inicial de minimizacao configuravel
|
||||
fn open_chat_window_with_state(app: &tauri::AppHandle, ticket_id: &str, ticket_ref: u64, start_minimized: bool) -> Result<(), String> {
|
||||
let label = format!("chat-{}", ticket_id);
|
||||
crate::log_info!(
|
||||
"[WINDOW] open_chat_window: label={} ticket_ref={} start_minimized={}",
|
||||
label,
|
||||
ticket_ref,
|
||||
start_minimized
|
||||
);
|
||||
|
||||
// Verificar se ja existe
|
||||
if let Some(window) = app.get_webview_window(&label) {
|
||||
let _ = window.set_ignore_cursor_events(false);
|
||||
crate::log_info!("[WINDOW] {}: window existe -> show()", label);
|
||||
window.show().map_err(|e| e.to_string())?;
|
||||
window.set_focus().map_err(|e| e.to_string())?;
|
||||
let _ = window.unminimize();
|
||||
if !start_minimized {
|
||||
crate::log_info!("[WINDOW] {}: window existe -> set_focus()", label);
|
||||
window.set_focus().map_err(|e| e.to_string())?;
|
||||
}
|
||||
// Expandir a janela se estiver minimizada (quando clicado na lista)
|
||||
if !start_minimized {
|
||||
let _ = set_chat_minimized(app, ticket_id, false);
|
||||
crate::log_info!("[WINDOW] {}: window existe -> set_chat_minimized(false)", label);
|
||||
let _ = set_chat_minimized_unlocked(app, ticket_id, false);
|
||||
}
|
||||
crate::log_info!("[WINDOW] {}: open_chat_window OK (reuso)", label);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
|
|
@ -1159,7 +1147,17 @@ fn open_chat_window_with_state(app: &tauri::AppHandle, ticket_id: &str, ticket_r
|
|||
// Usar query param ao inves de path para compatibilidade com SPA
|
||||
let url_path = format!("index.html?view=chat&ticketId={}&ticketRef={}", ticket_id, ticket_ref);
|
||||
|
||||
WebviewWindowBuilder::new(
|
||||
crate::log_info!(
|
||||
"[WINDOW] {}: build() inicio size={}x{} pos=({},{}) url={}",
|
||||
label,
|
||||
width,
|
||||
height,
|
||||
x,
|
||||
y,
|
||||
url_path
|
||||
);
|
||||
|
||||
let window = WebviewWindowBuilder::new(
|
||||
app,
|
||||
&label,
|
||||
WebviewUrl::App(url_path.into()),
|
||||
|
|
@ -1172,16 +1170,24 @@ fn open_chat_window_with_state(app: &tauri::AppHandle, ticket_id: &str, ticket_r
|
|||
.transparent(true) // Permite fundo transparente
|
||||
.shadow(false) // Desabilitar sombra para transparencia funcionar corretamente
|
||||
.resizable(false) // Desabilitar redimensionamento manual
|
||||
// REMOVIDO: always_on_top(true) causa competicao de Z-order com multiplas janelas
|
||||
// Mantem o chat acessivel mesmo ao trocar de janela/app (skip_taskbar=true).
|
||||
.always_on_top(true)
|
||||
.skip_taskbar(true)
|
||||
.focused(true)
|
||||
.focused(!start_minimized)
|
||||
.visible(true)
|
||||
.build()
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
crate::log_info!("[WINDOW] {}: build() OK", label);
|
||||
|
||||
// IMPORTANTE: Garantir que a janela receba eventos de cursor (evita click-through)
|
||||
let _ = window.set_ignore_cursor_events(false);
|
||||
|
||||
crate::log_info!("[WINDOW] {}: pos-build set_chat_minimized({}) inicio", label, start_minimized);
|
||||
// Reaplica layout/posicao logo apos criar a janela.
|
||||
// Isso evita que a primeira abertura apareca no canto superior esquerdo em alguns ambientes.
|
||||
let _ = set_chat_minimized(app, ticket_id, start_minimized);
|
||||
let _ = set_chat_minimized_unlocked(app, ticket_id, start_minimized);
|
||||
crate::log_info!("[WINDOW] {}: pos-build set_chat_minimized({}) fim", label, start_minimized);
|
||||
|
||||
crate::log_info!("Janela de chat aberta (minimizada={}): {}", start_minimized, label);
|
||||
Ok(())
|
||||
|
|
@ -1193,6 +1199,7 @@ pub fn open_chat_window(app: &tauri::AppHandle, ticket_id: &str, ticket_ref: u64
|
|||
}
|
||||
|
||||
pub fn close_chat_window(app: &tauri::AppHandle, ticket_id: &str) -> Result<(), String> {
|
||||
let _guard = WINDOW_OP_LOCK.lock();
|
||||
let label = format!("chat-{}", ticket_id);
|
||||
if let Some(window) = app.get_webview_window(&label) {
|
||||
window.close().map_err(|e| e.to_string())?;
|
||||
|
|
@ -1201,6 +1208,7 @@ pub fn close_chat_window(app: &tauri::AppHandle, ticket_id: &str) -> Result<(),
|
|||
}
|
||||
|
||||
pub fn minimize_chat_window(app: &tauri::AppHandle, ticket_id: &str) -> Result<(), String> {
|
||||
let _guard = WINDOW_OP_LOCK.lock();
|
||||
let label = format!("chat-{}", ticket_id);
|
||||
if let Some(window) = app.get_webview_window(&label) {
|
||||
window.hide().map_err(|e| e.to_string())?;
|
||||
|
|
@ -1209,7 +1217,7 @@ pub fn minimize_chat_window(app: &tauri::AppHandle, ticket_id: &str) -> Result<(
|
|||
}
|
||||
|
||||
/// Redimensiona a janela de chat para modo minimizado (chip) ou expandido
|
||||
pub fn set_chat_minimized(app: &tauri::AppHandle, ticket_id: &str, minimized: bool) -> Result<(), String> {
|
||||
fn set_chat_minimized_unlocked(app: &tauri::AppHandle, ticket_id: &str, minimized: bool) -> Result<(), String> {
|
||||
let label = format!("chat-{}", ticket_id);
|
||||
let window = app.get_webview_window(&label).ok_or("Janela não encontrada")?;
|
||||
|
||||
|
|
@ -1224,13 +1232,22 @@ pub fn set_chat_minimized(app: &tauri::AppHandle, ticket_id: &str, minimized: bo
|
|||
let (x, y) = resolve_chat_window_position(app, Some(&window), width, height);
|
||||
|
||||
// Aplicar novo tamanho e posicao
|
||||
crate::log_info!("[WINDOW] {}: set_chat_minimized({}) set_size inicio", label, minimized);
|
||||
window.set_size(tauri::LogicalSize::new(width, height)).map_err(|e| e.to_string())?;
|
||||
crate::log_info!("[WINDOW] {}: set_chat_minimized({}) set_size OK", label, minimized);
|
||||
crate::log_info!("[WINDOW] {}: set_chat_minimized({}) set_position inicio", label, minimized);
|
||||
window.set_position(tauri::LogicalPosition::new(x, y)).map_err(|e| e.to_string())?;
|
||||
crate::log_info!("[WINDOW] {}: set_chat_minimized({}) set_position OK", label, minimized);
|
||||
|
||||
crate::log_info!("Chat {} -> minimized={}", ticket_id, minimized);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_chat_minimized(app: &tauri::AppHandle, ticket_id: &str, minimized: bool) -> Result<(), String> {
|
||||
let _guard = WINDOW_OP_LOCK.lock();
|
||||
set_chat_minimized_unlocked(app, ticket_id, minimized)
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// HUB WINDOW MANAGEMENT (Lista de todas as sessoes)
|
||||
// ============================================================================
|
||||
|
|
@ -1238,14 +1255,19 @@ pub fn set_chat_minimized(app: &tauri::AppHandle, ticket_id: &str, minimized: bo
|
|||
const HUB_WINDOW_LABEL: &str = "chat-hub";
|
||||
|
||||
pub fn open_hub_window(app: &tauri::AppHandle) -> Result<(), String> {
|
||||
let _guard = WINDOW_OP_LOCK.lock();
|
||||
open_hub_window_with_state(app, true) // Por padrao abre minimizada
|
||||
}
|
||||
|
||||
fn open_hub_window_with_state(app: &tauri::AppHandle, start_minimized: bool) -> Result<(), String> {
|
||||
// Verificar se ja existe
|
||||
if let Some(window) = app.get_webview_window(HUB_WINDOW_LABEL) {
|
||||
let _ = window.set_ignore_cursor_events(false);
|
||||
window.show().map_err(|e| e.to_string())?;
|
||||
window.set_focus().map_err(|e| e.to_string())?;
|
||||
let _ = window.unminimize();
|
||||
if !start_minimized {
|
||||
window.set_focus().map_err(|e| e.to_string())?;
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
|
|
@ -1275,9 +1297,10 @@ fn open_hub_window_with_state(app: &tauri::AppHandle, start_minimized: bool) ->
|
|||
.transparent(true)
|
||||
.shadow(false)
|
||||
.resizable(false) // Desabilitar redimensionamento manual
|
||||
// REMOVIDO: always_on_top(true) causa competicao de Z-order com multiplas janelas
|
||||
// Mantem o hub acessivel mesmo ao trocar de janela/app (skip_taskbar=true).
|
||||
.always_on_top(true)
|
||||
.skip_taskbar(true)
|
||||
.focused(true)
|
||||
.focused(!start_minimized)
|
||||
.visible(true)
|
||||
.build()
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
|
@ -1285,7 +1308,9 @@ fn open_hub_window_with_state(app: &tauri::AppHandle, start_minimized: bool) ->
|
|||
// IMPORTANTE: Garantir que a janela receba eventos de cursor (evita click-through)
|
||||
if let Some(hub) = app.get_webview_window(HUB_WINDOW_LABEL) {
|
||||
let _ = hub.set_ignore_cursor_events(false);
|
||||
let _ = hub.set_focus();
|
||||
if !start_minimized {
|
||||
let _ = hub.set_focus();
|
||||
}
|
||||
}
|
||||
|
||||
// REMOVIDO TEMPORARIAMENTE: set_hub_minimized logo apos build pode causar
|
||||
|
|
@ -1297,6 +1322,7 @@ fn open_hub_window_with_state(app: &tauri::AppHandle, start_minimized: bool) ->
|
|||
}
|
||||
|
||||
pub fn close_hub_window(app: &tauri::AppHandle) -> Result<(), String> {
|
||||
let _guard = WINDOW_OP_LOCK.lock();
|
||||
if let Some(window) = app.get_webview_window(HUB_WINDOW_LABEL) {
|
||||
window.close().map_err(|e| e.to_string())?;
|
||||
}
|
||||
|
|
@ -1304,6 +1330,7 @@ pub fn close_hub_window(app: &tauri::AppHandle) -> Result<(), String> {
|
|||
}
|
||||
|
||||
pub fn set_hub_minimized(app: &tauri::AppHandle, minimized: bool) -> Result<(), String> {
|
||||
let _guard = WINDOW_OP_LOCK.lock();
|
||||
let window = app.get_webview_window(HUB_WINDOW_LABEL).ok_or("Hub window não encontrada")?;
|
||||
|
||||
let (width, height) = if minimized {
|
||||
|
|
@ -1318,8 +1345,10 @@ pub fn set_hub_minimized(app: &tauri::AppHandle, minimized: bool) -> Result<(),
|
|||
window.set_size(tauri::LogicalSize::new(width, height)).map_err(|e| e.to_string())?;
|
||||
window.set_position(tauri::LogicalPosition::new(x, y)).map_err(|e| e.to_string())?;
|
||||
|
||||
// Reforcar foco apos resize
|
||||
let _ = window.set_focus();
|
||||
// Foco apenas quando expandir (evita roubar foco ao minimizar apos abrir um chat).
|
||||
if !minimized {
|
||||
let _ = window.set_focus();
|
||||
}
|
||||
|
||||
crate::log_info!("Hub -> minimized={}, size={}x{}, pos=({},{})", minimized, width, height, x, y);
|
||||
Ok(())
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue