diff --git a/fractal-gtk/src/app/backend_loop.rs b/fractal-gtk/src/app/backend_loop.rs index dd0bc2d7..dcc87d5c 100644 --- a/fractal-gtk/src/app/backend_loop.rs +++ b/fractal-gtk/src/app/backend_loop.rs @@ -236,6 +236,9 @@ pub fn backend_loop(rx: Receiver) { APPOP!(show_error, (error)); APPOP!(set_state, (state)); } + BKResponse::ChangeLanguage(Err(err)) => { + error!("Error forming url to set room language: {:?}", err); + } BKResponse::LoginError(_) => { let error = i18n("Can’t login, try again"); let st = AppState::Login; diff --git a/fractal-gtk/src/app/connect/language.rs b/fractal-gtk/src/app/connect/language.rs new file mode 100644 index 00000000..86889576 --- /dev/null +++ b/fractal-gtk/src/app/connect/language.rs @@ -0,0 +1,40 @@ +use crate::app::App; +use crate::backend::BKCommand; + +use gtk::prelude::*; + +// The TextBufferExt alias is necessary to avoid conflict with gtk's TextBufferExt +use gspell::{CheckerExt, TextBuffer, TextBufferExt as GspellTextBufferExt}; + +impl App { + pub fn connect_language(&self) { + let textview = self.ui.sventry.view.upcast_ref::(); + if let Some(checker) = textview + .get_buffer() + .and_then(|gtk_buffer| TextBuffer::get_from_gtk_text_buffer(>k_buffer)) + .and_then(|gs_buffer| gs_buffer.get_spell_checker()) + { + let op = self.op.clone(); + let _signal_handler = checker.connect_property_language_notify(move |checker| { + if let Some(lang_code) = checker + .get_language() + .and_then(|lang| lang.get_code()) + .map(|lang_code| String::from(lang_code)) + { + /*If the checker is modified by fn set_language in fractal-gtk/src/appop/room.rs + due to the user switching rooms, the op mutex is locked already. + If the checker is modified by gtk due to the user switching the language, the op mutex is unlocked. */ + if let Ok(op) = op.try_lock() { + if let Some(active_room) = &op.active_room { + let server = &op.server_url; + let access_token = unwrap_or_unit_return!(op.access_token.clone()); + op.backend + .send(BKCommand::ChangeLanguage(access_token, server.clone(), lang_code, active_room.clone())) + .unwrap(); + } + } + } + }); + } + } +} diff --git a/fractal-gtk/src/app/connect/mod.rs b/fractal-gtk/src/app/connect/mod.rs index 037eb15a..a180abed 100644 --- a/fractal-gtk/src/app/connect/mod.rs +++ b/fractal-gtk/src/app/connect/mod.rs @@ -5,6 +5,7 @@ mod directory; mod headerbar; mod invite; mod join_room; +mod language; mod leave_room; mod markdown; mod new_room; @@ -20,6 +21,7 @@ impl App { self.connect_send(); self.connect_markdown(); self.connect_autocomplete(); + self.connect_language(); self.connect_directory(); self.connect_leave_room_dialog(); diff --git a/fractal-gtk/src/appop/mod.rs b/fractal-gtk/src/appop/mod.rs index c2ae85cf..ffd5abc6 100644 --- a/fractal-gtk/src/appop/mod.rs +++ b/fractal-gtk/src/appop/mod.rs @@ -34,7 +34,7 @@ mod member; mod message; mod notifications; mod notify; -mod room; +pub mod room; mod room_settings; mod start_chat; pub mod state; diff --git a/fractal-gtk/src/appop/room.rs b/fractal-gtk/src/appop/room.rs index 19b61698..f2ca6d13 100644 --- a/fractal-gtk/src/appop/room.rs +++ b/fractal-gtk/src/appop/room.rs @@ -27,6 +27,9 @@ use rand::{thread_rng, Rng}; use glib::functions::markup_escape_text; +// The TextBufferExt alias is necessary to avoid conflict with gtk's TextBufferExt +use gspell::{CheckerExt, TextBuffer, TextBufferExt as GspellTextBufferExt}; + use std::time::Instant; pub struct Force(pub bool); @@ -63,6 +66,10 @@ impl AppOp { } else if self.rooms.contains_key(&room.id) { // TODO: update the existing rooms let update_room = self.rooms.get_mut(&room.id).unwrap(); + if room.language.is_some() { + update_room.language = room.language.clone(); + }; + let typing_users: Vec = room .typing_users .iter() @@ -152,6 +159,9 @@ impl AppOp { pub fn set_active_room_by_id(&mut self, id: String) { let access_token = unwrap_or_unit_return!(self.access_token.clone()); if let Some(room) = self.rooms.get(&id) { + if let Some(language) = room.language.clone() { + self.set_language(language); + } if let RoomMembership::Invited(ref sender) = room.membership { self.show_inv_dialog(Some(sender), room.name.as_ref()); self.invitation_roomid = Some(room.id.clone()); @@ -698,4 +708,17 @@ impl AppOp { .unwrap(); } } + + pub fn set_language(&self, lang_code: String) { + if let Some(language) = &gspell::Language::lookup(&lang_code) { + let textview = self.ui.sventry.view.upcast_ref::(); + if let Some(gs_checker) = textview + .get_buffer() + .and_then(|gtk_buffer| TextBuffer::get_from_gtk_text_buffer(>k_buffer)) + .and_then(|gs_buffer| GspellTextBufferExt::get_spell_checker(&gs_buffer)) + { + CheckerExt::set_language(&gs_checker, Some(language)) + } + } + } } diff --git a/fractal-matrix-api/src/backend/mod.rs b/fractal-matrix-api/src/backend/mod.rs index 2e470fe3..6ac048b8 100644 --- a/fractal-matrix-api/src/backend/mod.rs +++ b/fractal-matrix-api/src/backend/mod.rs @@ -308,6 +308,10 @@ impl Backend { let r = room::invite(self, server, access_token, room, userid); bkerror!(r, tx, BKResponse::InviteError); } + Ok(BKCommand::ChangeLanguage(access_token, server, lang, room)) => { + let r = room::set_language(self, access_token, server, &room, &lang); + bkerror2!(r, tx, BKResponse::ChangeLanguage); + } // Media module Ok(BKCommand::GetThumbAsync(server, media, ctx)) => { diff --git a/fractal-matrix-api/src/backend/room.rs b/fractal-matrix-api/src/backend/room.rs index a38b9926..e3568300 100644 --- a/fractal-matrix-api/src/backend/room.rs +++ b/fractal-matrix-api/src/backend/room.rs @@ -30,6 +30,7 @@ use crate::backend::types::BackendData; use crate::backend::types::RoomType; use crate::r0::filter::RoomEventFilter; +use crate::r0::sync::sync_events::Language; use crate::r0::AccessToken; use crate::types::ExtraContent; use crate::types::Member; @@ -954,3 +955,34 @@ fn put_media(url: &str, file: Vec) -> Result { .json() .or(Err(Error::BackendError)) } + +pub fn set_language( + bk: &Backend, + access_token: AccessToken, + server: Url, + roomid: &str, + language_code: &str, +) -> Result<(), Error> { + let userid = bk.data.lock().unwrap().user_id.clone(); + let url = bk.url( + server, + &access_token, + &format!( + "user/{}/rooms/{}/account_data/org.gnome.fractal.language", + userid, + roomid.clone() + ), + vec![], + )?; + let body = json!(Language { + input_language: language_code.to_string(), + }); + + put!(url, &body, |_| {}, |err| { + error!( + "Matrix failed to set room language with error code: {:?}", + err + ) + }); + Ok(()) +} diff --git a/fractal-matrix-api/src/backend/types.rs b/fractal-matrix-api/src/backend/types.rs index 34a8909d..928381cf 100644 --- a/fractal-matrix-api/src/backend/types.rs +++ b/fractal-matrix-api/src/backend/types.rs @@ -86,6 +86,7 @@ pub enum BKCommand { ListStickers(AccessToken), SendSticker(Url, AccessToken, String, Sticker), PurchaseSticker(AccessToken, StickerGroup), + ChangeLanguage(AccessToken, Url, String, String), } #[derive(Debug)] @@ -144,6 +145,7 @@ pub enum BKResponse { SetRoomError(Error), GetFileAsyncError(Error), InviteError(Error), + ChangeLanguage(Result<(), Error>), } #[derive(Debug, Clone, Copy)] diff --git a/fractal-matrix-api/src/model/room.rs b/fractal-matrix-api/src/model/room.rs index 027ad176..7186d5a6 100644 --- a/fractal-matrix-api/src/model/room.rs +++ b/fractal-matrix-api/src/model/room.rs @@ -94,6 +94,7 @@ pub struct Room { pub direct: bool, pub prev_batch: Option, pub typing_users: Vec, + pub language: Option, /// Hashmap with the room users power levels /// the key will be the userid and the value will be the level @@ -131,6 +132,11 @@ impl Room { .find_map(|tag| tag["content"]["tags"]["m.favourite"].as_object()) .and(Some(RoomTag::Favourite)) .unwrap_or(RoomTag::None); + let room_lang = dataevs + .iter() + .filter(|x| x["type"] == "org.gnome.fractal.language") + .find_map(|entry| entry["content"]["input_language"].as_str()) + .map(|lang| lang.to_string()); let mut r = Self { name: calculate_room_name(stevents, userid), @@ -150,6 +156,7 @@ impl Room { .filter_map(parse_room_member) .map(|m| (m.uid.clone(), m)) .collect(), + language: room_lang, ..Self::new(k.clone(), RoomMembership::Joined(room_tag)) }; diff --git a/fractal-matrix-api/src/r0/sync/sync_events.rs b/fractal-matrix-api/src/r0/sync/sync_events.rs index e23c3298..95bae1ac 100644 --- a/fractal-matrix-api/src/r0/sync/sync_events.rs +++ b/fractal-matrix-api/src/r0/sync/sync_events.rs @@ -173,6 +173,11 @@ pub struct AccountData { pub events: Vec, } +#[derive(Clone, Debug, Serialize)] +pub struct Language { + pub input_language: String, +} + #[derive(Clone, Debug, Deserialize)] pub struct ToDevice { // TODO: Implement Event