diff --git a/Cargo.lock b/Cargo.lock
index 80113f47..00aa9dc8 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -77,6 +77,15 @@ dependencies = [
"memchr",
]
+[[package]]
+name = "android-build"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a133d38cebf328adaea4bc1891d9568e14a394b50e4f4ba5f63dc14e8beaaee9"
+dependencies = [
+ "windows-sys 0.52.0",
+]
+
[[package]]
name = "android-tzdata"
version = "0.1.1"
@@ -328,9 +337,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
-version = "2.4.1"
+version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
+checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
dependencies = [
"serde",
]
@@ -391,6 +400,15 @@ dependencies = [
"objc2",
]
+[[package]]
+name = "block2"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f"
+dependencies = [
+ "objc2",
+]
+
[[package]]
name = "bs58"
version = "0.5.0"
@@ -1360,7 +1378,7 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fb69199826926eb864697bddd27f73d9fddcffc004f5733131e15b465e30642"
dependencies = [
- "block2",
+ "block2 0.4.0",
"objc2",
]
@@ -1603,7 +1621,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4"
dependencies = [
"cfg-if",
- "windows-targets 0.52.6",
+ "windows-targets 0.48.5",
]
[[package]]
@@ -1612,7 +1630,7 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
dependencies = [
- "bitflags 2.4.1",
+ "bitflags 2.6.0",
"libc",
]
@@ -1959,7 +1977,7 @@ name = "makepad-zune-core"
version = "0.2.14"
source = "git+https://github.com/kevinaboos/makepad?branch=apple_bundle_resource_path#7e70055ab2fda14746374d02ca7ae70f804c40de"
dependencies = [
- "bitflags 2.4.1",
+ "bitflags 2.6.0",
]
[[package]]
@@ -2084,7 +2102,7 @@ source = "git+https://github.com/matrix-org/matrix-rust-sdk#dc055c632c106c6392ee
dependencies = [
"as_variant",
"async-trait",
- "bitflags 2.4.1",
+ "bitflags 2.6.0",
"eyeball",
"eyeball-im",
"futures-util",
@@ -2341,7 +2359,7 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad5a3bbb2ae61f345b8c11776f2e79fc2bb71d1901af9a5f81f03c9238a05d86"
dependencies = [
- "bitflags 2.4.1",
+ "bitflags 2.6.0",
"ctor",
"napi-sys-ohos",
"once_cell",
@@ -2413,12 +2431,47 @@ dependencies = [
"objc2-encode",
]
+[[package]]
+name = "objc2-contacts"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889"
+dependencies = [
+ "block2 0.5.1",
+ "objc2",
+ "objc2-foundation",
+]
+
+[[package]]
+name = "objc2-core-location"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781"
+dependencies = [
+ "block2 0.5.1",
+ "objc2",
+ "objc2-contacts",
+ "objc2-foundation",
+]
+
[[package]]
name = "objc2-encode"
version = "4.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7891e71393cd1f227313c9379a26a584ff3d7e6e7159e988851f0934c993f0f8"
+[[package]]
+name = "objc2-foundation"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8"
+dependencies = [
+ "bitflags 2.6.0",
+ "block2 0.5.1",
+ "libc",
+ "objc2",
+]
+
[[package]]
name = "object"
version = "0.32.1"
@@ -2747,7 +2800,7 @@ version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "666f0f59e259aea2d72e6012290c09877a780935cc3c18b1ceded41f3890d59c"
dependencies = [
- "bitflags 2.4.1",
+ "bitflags 2.6.0",
"memchr",
"pulldown-cmark-escape",
"unicase",
@@ -3040,6 +3093,21 @@ dependencies = [
"robius-android-env",
]
+[[package]]
+name = "robius-location"
+version = "0.1.0"
+source = "git+https://github.com/project-robius/robius-location#d47bd115f50247b98787efc72236dc65ab9c53bc"
+dependencies = [
+ "android-build",
+ "cfg-if",
+ "jni",
+ "objc2",
+ "objc2-core-location",
+ "objc2-foundation",
+ "robius-android-env",
+ "windows",
+]
+
[[package]]
name = "robius-open"
version = "0.1.0"
@@ -3084,6 +3152,7 @@ dependencies = [
"rand",
"rangemap",
"robius-directories",
+ "robius-location",
"robius-open",
"robius-use-makepad",
"serde",
@@ -3253,7 +3322,7 @@ version = "0.31.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b838eba278d213a8beaf485bd313fd580ca4505a00d5871caeb1457c55322cae"
dependencies = [
- "bitflags 2.4.1",
+ "bitflags 2.6.0",
"fallible-iterator",
"fallible-streaming-iterator",
"hashlink",
@@ -3288,7 +3357,7 @@ version = "0.38.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3"
dependencies = [
- "bitflags 2.4.1",
+ "bitflags 2.6.0",
"errno",
"libc",
"linux-raw-sys",
@@ -4361,6 +4430,16 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+[[package]]
+name = "windows"
+version = "0.57.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143"
+dependencies = [
+ "windows-core 0.57.0",
+ "windows-targets 0.52.6",
+]
+
[[package]]
name = "windows-core"
version = "0.51.1"
@@ -4378,17 +4457,60 @@ dependencies = [
"windows-targets 0.48.5",
]
+[[package]]
+name = "windows-core"
+version = "0.57.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d"
+dependencies = [
+ "windows-implement",
+ "windows-interface",
+ "windows-result 0.1.2",
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-implement"
+version = "0.57.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.75",
+]
+
+[[package]]
+name = "windows-interface"
+version = "0.57.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.75",
+]
+
[[package]]
name = "windows-registry"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0"
dependencies = [
- "windows-result",
+ "windows-result 0.2.0",
"windows-strings",
"windows-targets 0.52.6",
]
+[[package]]
+name = "windows-result"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8"
+dependencies = [
+ "windows-targets 0.52.6",
+]
+
[[package]]
name = "windows-result"
version = "0.2.0"
@@ -4404,7 +4526,7 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10"
dependencies = [
- "windows-result",
+ "windows-result 0.2.0",
"windows-targets 0.52.6",
]
diff --git a/Cargo.toml b/Cargo.toml
index a1b0301e..ed016d84 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -25,6 +25,7 @@ robius-use-makepad = "0.1.0"
robius-open = "0.1.0"
## A fork of the `directories` crate that adds support for Android by using our `robius-android-env` crate.
robius-directories = { git = "https://github.com/project-robius/robius-directories", branch = "robius"}
+robius-location = { git = "https://github.com/project-robius/robius-location" }
anyhow = "1.0"
chrono = "0.4"
@@ -175,6 +176,8 @@ section = "utils"
[package.metadata.packager.macos]
minimum_system_version = "11.0"
frameworks = [ ]
+info_plist_path = "./packaging/Info.plist"
+entitlements = "./packaging/Entitlements.plist"
signing_identity = "Developer ID Application: AppChef Inc. (SFVQ5V48GD)"
diff --git a/README.md b/README.md
index a30550b3..f0b4ac9a 100644
--- a/README.md
+++ b/README.md
@@ -37,11 +37,11 @@ The following table shows which host systems can currently be used to build Robr
2. If you're building on **Linux** or **WSL** on Windows, install the required dependencies. Otherwise, proceed to step 3.
* `openssl`, `clang`/`libclang`, `binfmt`, `Xcursor`/`X11`, `asound`/`pulse`.
-
+
On a Debian-like Linux distro (e.g., Ubuntu), run the following:
```sh
sudo apt-get update
- sudo apt-get install libssl-dev libsqlite3-dev pkg-config llvm clang libclang-dev binfmt-support libxcursor-dev libx11-dev libasound2-dev libpulse-dev
+ sudo apt-get install libssl-dev libsqlite3-dev pkg-config binfmt-support libxcursor-dev libx11-dev libasound2-dev libpulse-dev
```
3. Then, build and run Robrix (you can optionally add `--release` after `run`):
@@ -78,7 +78,7 @@ cargo run -- 'USERNAME' 'PASSWORD' ['HOMESERVER_URL']
* API version 33 or higher is required, which is Android 13 and up.
-## Feature status tracker
+## Feature status tracker
These are generally sorted in order of priority. If you're interested in helping out with anything here, please reach out via a GitHub issue or on our Robius matrix channel.
@@ -93,7 +93,7 @@ These are generally sorted in order of priority. If you're interested in helping
- [ ] Loading animation while waiting for pagination request: https://github.com/project-robius/robrix/issues/109
- [x] Stable positioning of events during simple timeline update
- [x] Stable positioning of events during complex/multi-part timeline update
-- [ ] Re-spawn timeline as focused on an old event after a full timeline clear: https://github.com/project-robius/robrix/issues/103
+- [ ] Re-spawn timeline as focused on an old event after a full timeline clear: https://github.com/project-robius/robrix/issues/103
- [x] Display simple text-only messages
- [x] Display image messages (PNG, JPEG)
- [x] Rich text formatting for message bodies
diff --git a/packaging/Entitlements.plist b/packaging/Entitlements.plist
new file mode 100644
index 00000000..09fb4c40
--- /dev/null
+++ b/packaging/Entitlements.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ com.apple.security.personal-information.location
+
+
+
diff --git a/packaging/Info.plist b/packaging/Info.plist
new file mode 100644
index 00000000..48031619
--- /dev/null
+++ b/packaging/Info.plist
@@ -0,0 +1,61 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ English
+ CFBundleDisplayName
+ Robrix
+ CFBundleExecutable
+ robrix
+ CFBundleIconFile
+ Robrix.icns
+ CFBundleIdentifier
+ org.robius.robrix
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ Robrix
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ 0.0.1-pre-alpha
+ CFBundleVersion
+ 20241009.225157
+ CSResourcesFileMapped
+
+ LSApplicationCategoryType
+ public.app-category.social-networking
+ LSMinimumSystemVersion
+ 11.0
+ CFBundleURLTypes
+
+
+ CFBundleURLSchemes
+
+ robrix
+ matrix
+
+ CFBundleURLName
+ org.robius.robrix robrix
+ CFBundleTypeRole
+ Viewer
+
+
+ LSRequiresCarbon
+
+ NSHighResolutionCapable
+
+ NSHumanReadableCopyright
+ Copyright 2023-2024, Project Robius
+ NSLocationAlwaysAndWhenInUseUsageDescription
+ Robrix needs permission to share your current location to a Matrix room.
+ NSLocationWhenInUseUsageDescription
+ Robrix needs permission to share your current location to a Matrix room.
+ NSLocationUsageDescription
+ Robrix needs permission to share your current location to a Matrix room.
+ NSLocationDefaultAccuracyReduced
+
+
+
+
\ No newline at end of file
diff --git a/resources/icons/location-person.svg b/resources/icons/location-person.svg
new file mode 100644
index 00000000..981447a4
--- /dev/null
+++ b/resources/icons/location-person.svg
@@ -0,0 +1,21 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/src/app.rs b/src/app.rs
index 1c9685f9..5e206fe1 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -2,7 +2,10 @@ use makepad_widgets::*;
use matrix_sdk::ruma::OwnedRoomId;
use crate::{
- home::rooms_list::RoomListAction, login::login_screen::LoginAction, verification::VerificationAction, verification_modal::{VerificationModalAction, VerificationModalWidgetRefExt}
+ home::{main_desktop_ui::RoomsPanelAction, rooms_list::RoomListAction},
+ verification::VerificationAction,
+ verification_modal::{VerificationModalAction, VerificationModalWidgetRefExt},
+ login::login_screen::LoginAction,
};
live_design! {
@@ -11,9 +14,7 @@ live_design! {
import makepad_draw::shader::std::*;
import crate::shared::styles::*;
- import crate::shared::clickable_view::ClickableView;
import crate::home::home_screen::HomeScreen;
- import crate::home::room_screen::RoomScreen;
import crate::profile::my_profile_screen::MyProfileScreen;
import crate::verification_modal::VerificationModal;
import crate::login::login_screen::LoginScreen;
@@ -212,6 +213,19 @@ impl MatchEvent for App {
RoomListAction::None => { }
}
+ match action.as_widget_action().cast() {
+ RoomsPanelAction::RoomFocused(room_id) => {
+ self.app_state.rooms_panel.selected_room = Some(SelectedRoom {
+ id: room_id.clone(),
+ name: None
+ });
+ }
+ RoomsPanelAction::FocusNone => {
+ self.app_state.rooms_panel.selected_room = None;
+ }
+ _ => { }
+ }
+
// `VerificationAction`s come from a background thread, so they are NOT widget actions.
// Therefore, we cannot use `as_widget_action().cast()` to match them.
match action.downcast_ref() {
diff --git a/src/home/home_screen.rs b/src/home/home_screen.rs
index e72c3ad3..dc935da8 100644
--- a/src/home/home_screen.rs
+++ b/src/home/home_screen.rs
@@ -5,11 +5,13 @@ live_design! {
import makepad_widgets::theme_desktop_dark::*;
import makepad_draw::shader::std::*;
- import crate::home::main_content::MainContent;
+ import crate::home::main_mobile_ui::MainMobileUI;
import crate::home::rooms_sidebar::RoomsSideBar;
import crate::home::spaces_dock::SpacesDock;
import crate::shared::styles::*;
import crate::shared::adaptive_view::AdaptiveView;
+ import crate::shared::search_bar::SearchBar;
+ import crate::home::main_desktop_ui::MainDesktopUI;
NavigationWrapper = {{NavigationWrapper}} {
view_stack = {}
@@ -24,10 +26,15 @@ live_design! {
width: Fill, height: Fill
padding: 0, margin: 0, align: {x: 0.0, y: 0.0}
flow: Right
-
+
spaces = {}
- rooms_sidebar = {}
- main_content = {}
+
+ {
+ flow: Down
+ width: Fill, height: Fill
+ {}
+ {}
+ }
}
Mobile = {
@@ -47,11 +54,11 @@ live_design! {
sidebar = {}
spaces = {}
}
-
+
main_content_view = {
width: Fill, height: Fill
body = {
- main_content = {}
+ main_content = {}
}
}
}
@@ -63,7 +70,7 @@ live_design! {
#[derive(Live, LiveHook, Widget)]
pub struct NavigationWrapper {
#[deref]
- view: View
+ view: View,
}
impl Widget for NavigationWrapper {
@@ -77,7 +84,8 @@ impl Widget for NavigationWrapper {
}
impl MatchEvent for NavigationWrapper {
- fn handle_actions(&mut self, cx: &mut Cx, actions:&Actions) {
- self.stack_navigation(id!(view_stack)).handle_stack_view_actions(cx, actions);
+ fn handle_actions(&mut self, cx: &mut Cx, actions: &Actions) {
+ self.stack_navigation(id!(view_stack))
+ .handle_stack_view_actions(cx, actions);
}
}
diff --git a/src/home/light_themed_dock.rs b/src/home/light_themed_dock.rs
new file mode 100644
index 00000000..2dd60a49
--- /dev/null
+++ b/src/home/light_themed_dock.rs
@@ -0,0 +1,298 @@
+use makepad_widgets::*;
+
+live_design! {
+ import makepad_widgets::base::*;
+ import makepad_widgets::theme_desktop_dark::*;
+ import makepad_draw::shader::std::*;
+
+ import crate::shared::styles::*;
+
+ Splitter = {
+ draw_splitter: {
+ uniform border_radius: 1.0
+ uniform splitter_pad: 1.0
+ uniform splitter_grabber: 110.0
+
+ instance pressed: 0.0
+ instance hover: 0.0
+
+ fn pixel(self) -> vec4 {
+ let sdf = Sdf2d::viewport(self.pos * self.rect_size);
+ sdf.clear(#ef);
+
+ if self.is_vertical > 0.5 {
+ sdf.box(
+ self.splitter_pad,
+ self.rect_size.y * 0.5 - self.splitter_grabber * 0.5,
+ self.rect_size.x - 2.0 * self.splitter_pad,
+ self.splitter_grabber,
+ self.border_radius
+ );
+ }
+ else {
+ sdf.box(
+ self.rect_size.x * 0.5 - self.splitter_grabber * 0.5,
+ self.splitter_pad,
+ self.splitter_grabber,
+ self.rect_size.y - 2.0 * self.splitter_pad,
+ self.border_radius
+ );
+ }
+ return sdf.fill_keep(mix(
+ THEME_COLOR_D_HIDDEN,
+ mix(
+ THEME_COLOR_CTRL_SCROLLBAR_HOVER,
+ THEME_COLOR_CTRL_SCROLLBAR_HOVER * 1.2,
+ self.pressed
+ ),
+ self.hover
+ ));
+ }
+ }
+ split_bar_size: (THEME_SPLITTER_SIZE)
+ min_horizontal: (THEME_SPLITTER_MIN_HORIZONTAL)
+ max_horizontal: (THEME_SPLITTER_MAX_HORIZONTAL)
+ min_vertical: (THEME_SPLITTER_MIN_VERTICAL)
+ max_vertical: (THEME_SPLITTER_MAX_VERTICAL)
+
+ animator: {
+ hover = {
+ default: off
+ off = {
+ from: {all: Forward {duration: 0.1}}
+ apply: {
+ draw_splitter: {pressed: 0.0, hover: 0.0}
+ }
+ }
+
+ on = {
+ from: {
+ all: Forward {duration: 0.1}
+ state_down: Forward {duration: 0.01}
+ }
+ apply: {
+ draw_splitter: {
+ pressed: 0.0,
+ hover: [{time: 0.0, value: 1.0}],
+ }
+ }
+ }
+
+ pressed = {
+ from: { all: Forward { duration: 0.1 }}
+ apply: {
+ draw_splitter: {
+ pressed: [{time: 0.0, value: 1.0}],
+ hover: 1.0,
+ }
+ }
+ }
+ }
+ }
+ }
+
+ TabCloseButton = {
+ // TODO: NEEDS FOCUS STATE
+ height: 10.0, width: 10.0,
+ margin: { right: (THEME_SPACE_2), left: -3.5 },
+ draw_button: {
+
+ instance hover: float;
+ instance selected: float;
+
+ fn pixel(self) -> vec4 {
+ let sdf = Sdf2d::viewport(self.pos * self.rect_size);
+ let mid = self.rect_size / 2.0;
+ let size = (self.hover * 0.25 + 0.5) * 0.25 * length(self.rect_size);
+ let min = mid - vec2(size);
+ let max = mid + vec2(size);
+ sdf.move_to(min.x, min.y);
+ sdf.line_to(max.x, max.y);
+ sdf.move_to(min.x, max.y);
+ sdf.line_to(max.x, min.y);
+ return sdf.stroke(mix(
+ #f,
+ #4,
+ self.hover
+ ), 1.0);
+ }
+ }
+
+ animator: {
+ hover = {
+ default: off
+ off = {
+ from: {all: Forward {duration: 0.1}}
+ apply: {
+ draw_button: {hover: 0.0}
+ }
+ }
+
+ on = {
+ cursor: Hand,
+ from: {all: Snap}
+ apply: {
+ draw_button: {hover: 1.0}
+ }
+ }
+ }
+ }
+ }
+
+ Tab = {
+ width: Fit, height: Fill, //Fixed((THEME_TAB_HEIGHT)),
+
+ align: {x: 0.0, y: 0.5}
+ padding: { }
+
+ close_button: {}
+ draw_name: {
+ text_style: {}
+ instance hover: 0.0
+ instance selected: 0.0
+ fn get_color(self) -> vec4 {
+ return mix(
+ mix(
+ #x0, // THEME_COLOR_TEXT_INACTIVE,
+ #xf, // THEME_COLOR_TEXT_SELECTED,
+ self.selected
+ ),
+ THEME_COLOR_TEXT_HOVER,
+ self.hover
+ )
+ }
+ }
+
+ draw_bg: {
+ instance hover: float
+ instance selected: float
+
+ fn pixel(self) -> vec4 {
+ let sdf = Sdf2d::viewport(self.pos * self.rect_size);
+ sdf.box(
+ -1.,
+ -1.,
+ self.rect_size.x + 2,
+ self.rect_size.y + 2,
+ 1.
+ )
+ sdf.fill_keep(
+ mix(
+ (COLOR_SECONDARY) * 0.95,
+ (COLOR_SELECTED_PRIMARY),
+ self.selected
+ )
+ )
+ return sdf.result
+ }
+ }
+
+ animator: {
+ hover = {
+ default: off
+ off = {
+ from: {all: Forward {duration: 0.2}}
+ apply: {
+ draw_bg: {hover: 0.0}
+ draw_name: {hover: 0.0}
+ }
+ }
+
+ on = {
+ cursor: Hand,
+ from: {all: Forward {duration: 0.1}}
+ apply: {
+ draw_bg: {hover: [{time: 0.0, value: 1.0}]}
+ draw_name: {hover: [{time: 0.0, value: 1.0}]}
+ }
+ }
+ }
+
+ selected = {
+ default: off
+ off = {
+ from: {all: Forward {duration: 0.3}}
+ apply: {
+ close_button: {draw_button: {selected: 0.0}}
+ draw_bg: {selected: 0.0}
+ draw_name: {selected: 0.0}
+ }
+ }
+
+ on = {
+ from: {all: Snap}
+ apply: {
+ close_button: {draw_button: {selected: 1.0}}
+ draw_bg: {selected: 1.0}
+ draw_name: {selected: 1.0}
+ }
+ }
+ }
+ }
+ }
+
+ TabBar = {
+ CloseableTab = {closeable:true}
+ PermanentTab = {closeable:false}
+
+ draw_drag: {
+ draw_depth: 10
+ color: #x0
+ }
+ draw_fill: {
+ color: (COLOR_SECONDARY)
+ }
+
+ width: Fill, height: (THEME_TAB_HEIGHT)
+
+ scroll_bars: {
+ show_scroll_x: true
+ show_scroll_y: false
+ scroll_bar_x: {
+ draw_bar: {bar_width: 3.0}
+ bar_size: 4
+ use_vertical_finger_scroll: true
+ }
+ }
+ }
+
+ Dock = {
+ flow: Down,
+
+ round_corner: {
+ draw_depth: 20.0
+ border_radius: 20.
+ fn pixel(self) -> vec4 {
+ let pos = vec2(
+ mix(self.pos.x, 1.0 - self.pos.x, self.flip.x),
+ mix(self.pos.y, 1.0 - self.pos.y, self.flip.y)
+ )
+
+ let sdf = Sdf2d::viewport(pos * self.rect_size);
+ sdf.rect(-10., -10., self.rect_size.x * 2.0, self.rect_size.y * 2.0);
+ sdf.box(
+ 0.25,
+ 0.25,
+ self.rect_size.x * 2.0,
+ self.rect_size.y * 2.0,
+ 4.0
+ );
+
+ sdf.subtract()
+ // sdf.fill(THEME_COLOR_BG_APP)
+ sdf.fill(COLOR_SECONDARY)
+ return sdf.result
+ }
+ }
+ border_size: (THEME_DOCK_BORDER_SIZE)
+
+ padding: {left: (THEME_DOCK_BORDER_SIZE), top: 0, right: (THEME_DOCK_BORDER_SIZE), bottom: (THEME_DOCK_BORDER_SIZE)}
+ padding_fill: {color: (THEME_COLOR_BG_APP)} // TODO: unclear what this does
+ drag_quad: {
+ draw_depth: 10.0
+ color: (mix((COLOR_SECONDARY), #FFFFFF00, pow(0.25, THEME_COLOR_CONTRAST)))
+ }
+ tab_bar: {}
+ splitter: {}
+ }
+}
\ No newline at end of file
diff --git a/src/home/main_desktop_ui.rs b/src/home/main_desktop_ui.rs
new file mode 100644
index 00000000..bae31bf0
--- /dev/null
+++ b/src/home/main_desktop_ui.rs
@@ -0,0 +1,260 @@
+use makepad_widgets::*;
+use matrix_sdk::ruma::OwnedRoomId;
+use std::collections::HashMap;
+
+use crate::app::{AppState, SelectedRoom};
+
+use super::room_screen::RoomScreenWidgetRefExt;
+live_design! {
+ import makepad_widgets::base::*;
+ import makepad_widgets::theme_desktop_dark::*;
+ import makepad_draw::shader::std::*;
+
+ import crate::shared::styles::*;
+ import crate::home::light_themed_dock::*;
+ import crate::home::welcome_screen::WelcomeScreen;
+ import crate::home::rooms_sidebar::RoomsSideBar;
+ import crate::home::room_screen::RoomScreen;
+
+ MainDesktopUI = {{MainDesktopUI}} {
+ dock = {
+ width: Fill,
+ height: Fill,
+ padding: 0,
+ spacing: 0,
+
+ root = Splitter {
+ axis: Horizontal,
+ align: FromA(300.0),
+ a: rooms_sidebar_tab,
+ b: main
+ }
+
+ // Not really a tab, but it needs to be one to be used in the dock
+ rooms_sidebar_tab = Tab {
+ name: "" // show no tab header
+ kind: rooms_sidebar
+ }
+
+ main = Tabs{tabs:[home_tab], selected:0}
+
+ home_tab = Tab {
+ name: "Home"
+ kind: welcome_screen
+ template: PermanentTab
+ }
+
+ rooms_sidebar = {}
+ welcome_screen = {}
+ room_screen = {}
+ }
+ }
+}
+
+#[derive(Live, LiveHook, Widget)]
+pub struct MainDesktopUI {
+ #[deref]
+ view: View,
+
+ /// The rooms that are currently open
+ #[rust]
+ open_rooms: HashMap,
+
+ /// The tab that should be closed in the next draw event
+ #[rust]
+ tab_to_close: Option,
+
+ /// The order in which the rooms were opened
+ #[rust]
+ room_order: Vec,
+}
+
+impl Widget for MainDesktopUI {
+ fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) {
+ self.match_event(cx, event);
+ self.view.handle_event(cx, event, scope);
+ }
+
+ fn draw_walk(&mut self, cx: &mut Cx2d, scope: &mut Scope, walk: Walk) -> DrawStep {
+ let dock = self.view.dock(id!(dock));
+ let app_state = scope.data.get_mut::().unwrap();
+
+ // In case a room is closed, this will be the newly selected room if there is one
+ let mut new_selected_room: Option = None;
+ let mut focus_reset = false;
+
+ if let Some(tab_id) = self.tab_to_close {
+ if let Some(room_id) = self.open_rooms.get(&tab_id) {
+ self.room_order.remove(
+ self.room_order.iter().position(|id| id == room_id).unwrap()
+ );
+
+ if self.open_rooms.len() > 1 {
+ // if the closing tab is the active one, then focus the next room
+ let active_room = app_state.rooms_panel.selected_room.as_ref();
+ if let Some(active_room) = active_room {
+ if active_room.id == *room_id {
+ if let Some(new_focused_room_id) = self.room_order.last() {
+ // notify the app state about the new focused room
+ cx.widget_action(
+ self.widget_uid(),
+ &scope.path,
+ RoomsPanelAction::RoomFocused(new_focused_room_id.clone()),
+ );
+
+ // set the new selected room to be used in the current draw
+ new_selected_room = Some(SelectedRoom {
+ id: new_focused_room_id.clone(),
+ name: None
+ });
+ }
+ }
+ }
+ } else {
+ // if there is no room to focus, reset the selected room in the app state
+ // app_state.rooms_panel.selected_room = None;
+ cx.widget_action(
+ self.widget_uid(),
+ &scope.path,
+ RoomsPanelAction::FocusNone,
+ );
+
+ focus_reset = true;
+ dock.select_tab(cx, live_id!(home_tab));
+ }
+ }
+ dock.close_tab(cx, tab_id);
+ self.tab_to_close = None;
+ self.open_rooms.remove(&tab_id);
+ }
+
+ // if the focus was not reset, then focus the new selected room or the previously selected one
+ if !focus_reset {
+ if let Some(room) = new_selected_room {
+ self.focus_or_create_tab(cx, &room);
+ } else if let Some(room) = app_state.rooms_panel.selected_room.as_ref() {
+ self.focus_or_create_tab(cx, room);
+ }
+ }
+ self.view.draw_walk(cx, scope, walk)
+ }
+}
+
+impl MainDesktopUI {
+ /// Focuses on a room if it is already open, otherwise creates a new tab for the room
+ fn focus_or_create_tab(&mut self, cx: &mut Cx2d, room: &SelectedRoom) {
+ let dock = self.view.dock(id!(dock));
+
+ // if the room is already open, select its tab
+ let room_id_as_live_id = LiveId::from_str(&room.id.to_string());
+ if self.open_rooms.contains_key(&room_id_as_live_id) {
+ dock.select_tab(cx, room_id_as_live_id);
+ return;
+ }
+
+ self.open_rooms.insert(room_id_as_live_id, room.id.clone());
+
+ let displayed_room_name = room
+ .name
+ .clone()
+ .unwrap_or_else(|| format!("Room ID {}", &room.id));
+
+ // create a new tab for the room
+ let (tab_bar, _pos) = dock.find_tab_bar_of_tab(live_id!(home_tab)).unwrap();
+ let kind = live_id!(room_screen);
+
+ let result = dock.create_and_select_tab(
+ cx,
+ tab_bar,
+ room_id_as_live_id,
+ kind,
+ displayed_room_name.clone(),
+ live_id!(CloseableTab),
+ // `None` will insert the tab at the end
+ None,
+ );
+
+ // if the tab was created, set the room screen and add the room to the room order
+ if let Some(widget) = result {
+ self.room_order.push(room.id.clone());
+ widget.as_room_screen().set_displayed_room(
+ cx,
+ displayed_room_name,
+ room.id.clone(),
+ );
+ }
+ }
+}
+
+impl MatchEvent for MainDesktopUI {
+ fn handle_action(&mut self, cx: &mut Cx, action: &Action) {
+ let dock = self.view.dock(id!(dock));
+
+ if let Some(action) = action.as_widget_action() {
+ match action.cast() {
+ // Whenever a tab is pressed notify the app state about it, except for the home tab
+ DockAction::TabWasPressed(tab_id) => {
+ if tab_id == live_id!(home_tab) {
+ cx.widget_action(
+ self.widget_uid(),
+ &HeapLiveIdPath::default(),
+ RoomsPanelAction::FocusNone,
+ );
+ } else if let Some(room_id) = self.open_rooms.get(&tab_id) {
+ cx.widget_action(
+ self.widget_uid(),
+ &HeapLiveIdPath::default(),
+ RoomsPanelAction::RoomFocused(room_id.clone()),
+ );
+ }
+ }
+ // Whenever a tab is closed, defer the close to the next event loop to prevent closing the tab while the app state
+ // still has the room as selected
+ DockAction::TabCloseWasPressed(tab_id) => {
+ self.tab_to_close = Some(tab_id);
+ self.redraw(cx);
+ }
+ // When dragging a tab, allow it to be dragged
+ DockAction::ShouldTabStartDrag(tab_id) => {
+ dock.tab_start_drag(
+ cx,
+ tab_id,
+ DragItem::FilePath {
+ path: "".to_string(),
+ internal_id: Some(tab_id),
+ },
+ );
+ }
+ // When dragging a tab, allow it to be dragged
+ DockAction::Drag(drag_event) => {
+ if drag_event.items.len() == 1 {
+ dock.accept_drag(cx, drag_event, DragResponse::Move);
+ }
+ }
+ // When dropping a tab, move it to the new position
+ DockAction::Drop(drop_event) => {
+ if let DragItem::FilePath {
+ path: _,
+ internal_id,
+ } = &drop_event.items[0]
+ {
+ // from inside the dock, otherwise it's an external file
+ if let Some(internal_id) = internal_id {
+ dock.drop_move(cx, drop_event.abs, *internal_id);
+ }
+ }
+ }
+ _ => (),
+ }
+ }
+ }
+}
+
+#[derive(Clone, DefaultNone, Debug)]
+pub enum RoomsPanelAction {
+ None,
+ /// Notifies that a room was focused
+ RoomFocused(OwnedRoomId),
+ /// Resets the focus on the rooms panel
+ FocusNone,
+}
diff --git a/src/home/main_content.rs b/src/home/main_mobile_ui.rs
similarity index 86%
rename from src/home/main_content.rs
rename to src/home/main_mobile_ui.rs
index faf7356d..d515dc63 100644
--- a/src/home/main_content.rs
+++ b/src/home/main_mobile_ui.rs
@@ -10,13 +10,12 @@ live_design! {
import makepad_draw::shader::std::*;
import crate::shared::styles::*;
- import crate::shared::search_bar::SearchBar;
import crate::shared::cached_widget::CachedWidget;
import crate::home::room_screen::RoomScreen;
import crate::home::welcome_screen::WelcomeScreen;
- MainContent = {{MainContent}} {
+ MainMobileUI = {{MainMobileUI}} {
width: Fill, height: Fill
flow: Down,
show_bg: true
@@ -25,32 +24,28 @@ live_design! {
}
align: {x: 0.0, y: 0.5}
- {}
welcome = {}
rooms = {
align: {x: 0.5, y: 0.5}
width: Fill, height: Fill
- {
- room_screen = {}
- }
+ room_screen = {}
}
}
}
#[derive(Live, LiveHook, Widget)]
-pub struct MainContent {
+pub struct MainMobileUI {
#[deref]
view: View,
}
-impl Widget for MainContent {
+impl Widget for MainMobileUI {
fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) {
self.view.handle_event(cx, event, scope);
}
fn draw_walk(&mut self, cx: &mut Cx2d, scope: &mut Scope, walk: Walk) -> DrawStep {
- // log!("Redrawing MainContent");
let app_state = scope.data.get::().unwrap();
if let Some(room) = app_state.rooms_panel.selected_room.as_ref() {
diff --git a/src/home/mod.rs b/src/home/mod.rs
index f4e0a250..6e03136d 100644
--- a/src/home/mod.rs
+++ b/src/home/mod.rs
@@ -1,13 +1,15 @@
use makepad_widgets::Cx;
pub mod home_screen;
-pub mod main_content;
+pub mod main_mobile_ui;
+pub mod main_desktop_ui;
pub mod room_preview;
pub mod room_screen;
pub mod rooms_list;
pub mod rooms_sidebar;
pub mod spaces_dock;
pub mod welcome_screen;
+pub mod light_themed_dock;
pub fn live_design(cx: &mut Cx) {
home_screen::live_design(cx);
@@ -15,7 +17,9 @@ pub fn live_design(cx: &mut Cx) {
room_preview::live_design(cx);
room_screen::live_design(cx);
rooms_sidebar::live_design(cx);
- main_content::live_design(cx);
+ main_mobile_ui::live_design(cx);
+ main_desktop_ui::live_design(cx);
spaces_dock::live_design(cx);
welcome_screen::live_design(cx);
+ light_themed_dock::live_design(cx);
}
diff --git a/src/home/room_preview.rs b/src/home/room_preview.rs
index cb559e39..4d0f6d2a 100644
--- a/src/home/room_preview.rs
+++ b/src/home/room_preview.rs
@@ -2,7 +2,8 @@ use makepad_widgets::*;
use crate::{
shared::{
- adaptive_view::{AdaptiveViewWidgetExt, DisplayContext}, avatar::AvatarWidgetExt,
+ adaptive_view::{AdaptiveViewWidgetExt, DisplayContext},
+ avatar::AvatarWidgetExt,
html_or_plaintext::HtmlOrPlaintextWidgetExt,
},
utils::{self, relative_format},
@@ -101,7 +102,7 @@ live_design! {
}
}
}
-
+
RoomPreview = {{RoomPreview}} {
// Wraps the RoomPreviewContent in an AdaptiveView
// to change the displayed content (and its layout) based on the available space in the sidebar.
@@ -155,12 +156,10 @@ impl LiveHook for RoomPreview {
// Adapt the preview based on the available space.
self.view
.adaptive_view(id!(adaptive_preview))
- .set_variant_selector(cx, |_cx, parent_size| {
- match parent_size.x {
- x if x <= 100. => live_id!(OnlyIcon),
- x if x <= 250. => live_id!(IconAndName),
- _ => live_id!(FullPreview),
- }
+ .set_variant_selector(cx, |_cx, parent_size| match parent_size.x {
+ x if x <= 100. => live_id!(OnlyIcon),
+ x if x <= 250. => live_id!(IconAndName),
+ _ => live_id!(FullPreview),
});
}
}
@@ -242,7 +241,7 @@ impl Widget for RoomPreviewContent {
// Mobile doesn't have a selected state. Always use the default colors.
// We call the update in case the app was resized from desktop to mobile while the room was selected.
// This can be optimized by only calling this when the app is resized.
- self.update_preview_colors(cx, false);
+ self.update_preview_colors(cx, false);
}
}
self.view.draw_walk(cx, scope, walk)
@@ -279,44 +278,51 @@ impl RoomPreviewContent {
),
);
- self.view.label(id!(room_name)).apply_over(
- cx,
- live!(
+ // We check that the UI elements exist to avoid unnecessary updates, and prevent error logs.
+ if !self.view.label(id!(room_name)).is_empty() {
+ self.view.label(id!(room_name)).apply_over(
+ cx,
+ live!(
draw_text: {
color: (room_name_color)
}
- ),
- );
+ ),
+ );
+ }
- self.view.label(id!(timestamp)).apply_over(
- cx,
- live!(
- draw_text: {
- color: (timestamp_color)
- }
- ),
- );
+ if !self.view.label(id!(timestamp)).is_empty() {
+ self.view.label(id!(timestamp)).apply_over(
+ cx,
+ live!(
+ draw_text: {
+ color: (timestamp_color)
+ }
+ ),
+ );
+ }
- self.html_or_plaintext(id!(latest_message)).apply_over(
- cx,
- live!(
- html_view = {
- html = {
- font_color: (message_text_color),
- draw_normal: { color: (message_text_color) },
- draw_italic: { color: (message_text_color) },
- draw_bold: { color: (message_text_color) },
- draw_bold_italic: { color: (message_text_color) },
- }
+ if !self.view.html_or_plaintext(id!(latest_message)).is_empty() {
+ self.view.html_or_plaintext(id!(latest_message)).apply_over(
+ cx,
+ live!(
+ html_view = {
+ html = {
+ font_color: (message_text_color),
+ draw_normal: { color: (message_text_color) },
+ draw_italic: { color: (message_text_color) },
+ draw_bold: { color: (message_text_color) },
+ draw_bold_italic: { color: (message_text_color) },
}
- plaintext_view = {
- pt_label = {
- draw_text: {
- color: (message_text_color)
- }
+ }
+ plaintext_view = {
+ pt_label = {
+ draw_text: {
+ color: (message_text_color)
}
}
- ),
- );
+ }
+ ),
+ );
+ }
}
}
diff --git a/src/home/room_screen.rs b/src/home/room_screen.rs
index 37aaa5fd..fb0faa56 100644
--- a/src/home/room_screen.rs
+++ b/src/home/room_screen.rs
@@ -1,7 +1,7 @@
//! A room screen is the UI page that displays a single Room's timeline of events/messages
//! along with a message input bar at the bottom.
-use std::{borrow::Cow, collections::{BTreeMap, HashMap}, ops::{DerefMut, Range}, sync::{Arc, Mutex}, time::Instant};
+use std::{borrow::Cow, collections::{BTreeMap, HashMap}, ops::{DerefMut, Range}, sync::{Arc, Mutex}, time::{Instant, SystemTime}};
use imbl::Vector;
use makepad_widgets::*;
@@ -9,7 +9,7 @@ use matrix_sdk::{
ruma::{
events::room::{
message::{
- FormattedBody, ImageMessageEventContent, MessageFormat, MessageType, NoticeMessageEventContent, RoomMessageEventContent, TextMessageEventContent
+ FormattedBody, ImageMessageEventContent, LocationMessageEventContent, MessageFormat, MessageType, NoticeMessageEventContent, RoomMessageEventContent, TextMessageEventContent
},
MediaSource,
},
@@ -22,9 +22,10 @@ use matrix_sdk_ui::timeline::{
RoomMembershipChange, TimelineDetails, TimelineItem, TimelineItemContent, TimelineItemKind,
VirtualTimelineItem,
};
+use robius_location::Coordinates;
use crate::{
- avatar_cache::{self, AvatarCacheEntry}, event_preview::{text_preview_of_member_profile_change, text_preview_of_other_state, text_preview_of_redacted_message, text_preview_of_room_membership_change, text_preview_of_timeline_item}, media_cache::{MediaCache, MediaCacheEntry}, profile::{
+ avatar_cache::{self, AvatarCacheEntry}, event_preview::{text_preview_of_member_profile_change, text_preview_of_other_state, text_preview_of_redacted_message, text_preview_of_room_membership_change, text_preview_of_timeline_item}, location::{get_latest_location, init_location_subscriber, request_location_update, LocationAction, LocationRequest, LocationUpdate}, media_cache::{MediaCache, MediaCacheEntry}, profile::{
user_profile::{AvatarState, ShowUserProfileAction, UserProfile, UserProfileAndRoomId, UserProfilePaneInfo, UserProfileSlidingPaneRef, UserProfileSlidingPaneWidgetExt},
user_profile_cache,
}, shared::{
@@ -32,11 +33,12 @@ use crate::{
html_or_plaintext::{HtmlOrPlaintextRef, HtmlOrPlaintextWidgetRefExt},
text_or_image::{TextOrImageRef, TextOrImageWidgetRefExt},
typing_animation::TypingAnimationWidgetExt,
- },
- sliding_sync::{get_client, submit_async_request, take_timeline_update_receiver, MatrixRequest}, utils::{self, unix_time_millis_to_datetime, MediaFormatConst}
+ }, sliding_sync::{get_client, submit_async_request, take_timeline_update_receiver, MatrixRequest}, utils::{self, unix_time_millis_to_datetime, MediaFormatConst}
};
use rangemap::RangeSet;
+const GEO_URI_SCHEME: &str = "geo:";
+
live_design! {
import makepad_draw::shader::std::*;
import makepad_widgets::base::*;
@@ -50,6 +52,7 @@ live_design! {
import crate::shared::html_or_plaintext::*;
import crate::profile::user_profile::UserProfileSlidingPane;
import crate::shared::typing_animation::TypingAnimation;
+ import crate::shared::icon_button::RobrixIconButton;
IMG_DEFAULT_AVATAR = dep("crate://self/resources/img/default_avatar.png")
ICO_FAV = dep("crate://self/resources/icon_favorite.svg")
@@ -62,16 +65,7 @@ live_design! {
ICO_CLOSE = dep("crate://self/resources/icons/close.svg")
ICO_JUMP_TO_BOTTOM = dep("crate://self/resources/icon_jump_to_bottom.svg")
- TEXT_SUB = {
- font_size: (10),
- font: {path: dep("crate://makepad-widgets/resources/IBMPlexSans-Text.ttf")}
- }
-
- TEXT_P = {
- font_size: (12),
- height_factor: 1.65,
- font: {path: dep("crate://makepad-widgets/resources/IBMPlexSans-Text.ttf")}
- }
+ ICO_LOCATION_PERSON = dep("crate://self/resources/icons/location-person.svg")
COLOR_BG = #xfff8ee
COLOR_BRAND = #x5
@@ -678,39 +672,100 @@ live_design! {
}
- IMG_SMILEY_FACE_BW = dep("crate://self/resources/img/smiley_face_bw.png")
- IMG_PLUS = dep("crate://self/resources/img/plus.png")
- IMG_KEYBOARD_ICON = dep("crate://self/resources/img/keyboard_icon.png")
+ LocationPreview = {{LocationPreview}} {
+ visible: false
+ width: Fill
+ height: Fit
+ flow: Down
+ padding: {left: 12.0, top: 12.0, bottom: 12.0, right: 10.0}
+ spacing: 15
- RoomScreen = {{RoomScreen}} {
- width: Fill, height: Fill,
show_bg: true,
draw_bg: {
- color: (COLOR_SECONDARY)
+ color: #xF0F5FF,
}
- flow: Down, spacing: 0.0
+