Merge 'Initial android support'
This gets android into a somewhat usable state.
Still news a few follow ups.
William Casarin (9):
nix: add $ANDROID_JAR helper to shell
add input context menu helper
thread: enable selectable text in threads
universe: add full tabs
android: fix build
dave: initial android fixes
android: arboard clipboard support
android: add initial ci
Merge 'Initial android support'
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
# set to false if you don't care to include android stuff
|
# set to false if you don't care to include android stuff
|
||||||
export use_android=true
|
export use_android=true
|
||||||
export android_emulator=false
|
export android_emulator=false
|
||||||
|
export ANDROID_DIR=crates/notedeck_chrome/android
|
||||||
|
|
||||||
use nix --arg use_android $use_android --arg android_emulator $android_emulator
|
use nix --arg use_android $use_android --arg android_emulator $android_emulator
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,28 @@ jobs:
|
|||||||
cargo fmt --all -- --check
|
cargo fmt --all -- --check
|
||||||
cargo clippy
|
cargo clippy
|
||||||
|
|
||||||
|
android:
|
||||||
|
name: Check (android)
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
|
with:
|
||||||
|
components: rustfmt,clippy
|
||||||
|
- name: Setup Java JDK
|
||||||
|
uses: actions/setup-java@v4.5.0
|
||||||
|
with:
|
||||||
|
java-version: '17'
|
||||||
|
distribution: 'temurin'
|
||||||
|
- name: Setup Android SDK
|
||||||
|
uses: android-actions/setup-android@v3
|
||||||
|
- name: Add android rust target
|
||||||
|
run: rustup target add aarch64-linux-android
|
||||||
|
- name: Install Cargo NDK
|
||||||
|
run: cargo install cargo-ndk
|
||||||
|
- name: Run tests
|
||||||
|
run: make jni-check
|
||||||
|
|
||||||
linux-test:
|
linux-test:
|
||||||
name: Test (Linux)
|
name: Test (Linux)
|
||||||
uses: ./.github/workflows/build-and-test.yml
|
uses: ./.github/workflows/build-and-test.yml
|
||||||
|
|||||||
Generated
+814
-603
File diff suppressed because it is too large
Load Diff
+7
-7
@@ -90,15 +90,15 @@ strip = true # Strip symbols from binary*
|
|||||||
#egui_extras = { path = "/home/jb55/dev/github/emilk/egui/crates/egui_extras" }
|
#egui_extras = { path = "/home/jb55/dev/github/emilk/egui/crates/egui_extras" }
|
||||||
#epaint = { path = "/home/jb55/dev/github/emilk/egui/crates/epaint" }
|
#epaint = { path = "/home/jb55/dev/github/emilk/egui/crates/epaint" }
|
||||||
|
|
||||||
egui = { git = "https://github.com/damus-io/egui", rev = "93cd1cedc1e8eed2b055e317226838e37a845aad" }
|
egui = { git = "https://github.com/damus-io/egui", rev = "73a831ed43d3a8592611e2948b505add88d8aba2" }
|
||||||
eframe = { git = "https://github.com/damus-io/egui", rev = "93cd1cedc1e8eed2b055e317226838e37a845aad" }
|
eframe = { git = "https://github.com/damus-io/egui", rev = "73a831ed43d3a8592611e2948b505add88d8aba2" }
|
||||||
egui-winit = { git = "https://github.com/damus-io/egui", rev = "93cd1cedc1e8eed2b055e317226838e37a845aad" }
|
egui-winit = { git = "https://github.com/damus-io/egui", rev = "73a831ed43d3a8592611e2948b505add88d8aba2" }
|
||||||
egui-wgpu = { git = "https://github.com/damus-io/egui", rev = "93cd1cedc1e8eed2b055e317226838e37a845aad" }
|
egui-wgpu = { git = "https://github.com/damus-io/egui", rev = "73a831ed43d3a8592611e2948b505add88d8aba2" }
|
||||||
egui_extras = { git = "https://github.com/damus-io/egui", rev = "93cd1cedc1e8eed2b055e317226838e37a845aad" }
|
egui_extras = { git = "https://github.com/damus-io/egui", rev = "73a831ed43d3a8592611e2948b505add88d8aba2" }
|
||||||
epaint = { git = "https://github.com/damus-io/egui", rev = "93cd1cedc1e8eed2b055e317226838e37a845aad" }
|
epaint = { git = "https://github.com/damus-io/egui", rev = "73a831ed43d3a8592611e2948b505add88d8aba2" }
|
||||||
puffin = { git = "https://github.com/jb55/puffin", package = "puffin", rev = "c6a6242adaf90b6292c0f462d2acd34d96d224d2" }
|
puffin = { git = "https://github.com/jb55/puffin", package = "puffin", rev = "c6a6242adaf90b6292c0f462d2acd34d96d224d2" }
|
||||||
puffin_egui = { git = "https://github.com/jb55/puffin", package = "puffin_egui", rev = "c6a6242adaf90b6292c0f462d2acd34d96d224d2" }
|
puffin_egui = { git = "https://github.com/jb55/puffin", package = "puffin_egui", rev = "c6a6242adaf90b6292c0f462d2acd34d96d224d2" }
|
||||||
#winit = { git = "https://github.com/damus-io/winit", rev = "14d61a74bee0c9863abe7ef28efae2c4d8bd3743" }
|
#winit = { git = "https://github.com/damus-io/winit", rev = "14d61a74bee0c9863abe7ef28efae2c4d8bd3743" }
|
||||||
#winit = { path = "/home/jb55/dev/github/rust-windowing/winit" }
|
#winit = { path = "/home/jb55/dev/github/rust-windowing/winit" }
|
||||||
#android-activity = { git = "https://github.com/damus-io/android-activity", rev = "da17773852312a58c3445422dfe477162f2f1265" }
|
android-activity = { git = "https://github.com/damus-io/android-activity", rev = "a8948332c7c551303d32eb26a59d0abd676e47a5" }
|
||||||
#android-activity = { path = "/home/jb55/dev/github/rust-mobile/android-activity/android-activity" }
|
#android-activity = { path = "/home/jb55/dev/github/rust-mobile/android-activity/android-activity" }
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ jni: fake
|
|||||||
cargo ndk --target arm64-v8a -o $(ANDROID_DIR)/app/src/main/jniLibs/ build --profile release
|
cargo ndk --target arm64-v8a -o $(ANDROID_DIR)/app/src/main/jniLibs/ build --profile release
|
||||||
|
|
||||||
jni-check: fake
|
jni-check: fake
|
||||||
cargo ndk --target arm64-v8a -o $(ANDROID_DIR)/app/src/main/jniLibs/ check --profile release
|
cargo ndk --target arm64-v8a check
|
||||||
|
|
||||||
apk: jni
|
apk: jni
|
||||||
cd $(ANDROID_DIR) && ./gradlew build
|
cd $(ANDROID_DIR) && ./gradlew build
|
||||||
@@ -27,4 +27,4 @@ push-android-config:
|
|||||||
android: jni
|
android: jni
|
||||||
cd $(ANDROID_DIR) && ./gradlew installDebug
|
cd $(ANDROID_DIR) && ./gradlew installDebug
|
||||||
adb shell am start -n com.damus.notedeck/.MainActivity
|
adb shell am start -n com.damus.notedeck/.MainActivity
|
||||||
adb logcat -v color -s RustStdoutStderr | tee logcat.txt
|
adb logcat -v color -s RustStdoutStderr -s threaded_app | tee logcat.txt
|
||||||
|
|||||||
@@ -24,5 +24,5 @@ android {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation "com.google.android.material:material:1.5.0"
|
implementation "com.google.android.material:material:1.5.0"
|
||||||
implementation "androidx.games:games-activity:2.0.2"
|
implementation "androidx.games:games-activity:4.0.0"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use egui_winit::winit::platform::android::activity::AndroidApp;
|
|||||||
use notedeck_columns::Damus;
|
use notedeck_columns::Damus;
|
||||||
use notedeck_dave::Dave;
|
use notedeck_dave::Dave;
|
||||||
|
|
||||||
use crate::{chrome::Chrome, setup::setup_chrome};
|
use crate::{app::NotedeckApp, chrome::Chrome, setup::setup_chrome};
|
||||||
use notedeck::Notedeck;
|
use notedeck::Notedeck;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
@@ -18,7 +18,12 @@ pub async fn android_main(app: AndroidApp) {
|
|||||||
use tracing_subscriber::{prelude::*, EnvFilter};
|
use tracing_subscriber::{prelude::*, EnvFilter};
|
||||||
|
|
||||||
std::env::set_var("RUST_BACKTRACE", "full");
|
std::env::set_var("RUST_BACKTRACE", "full");
|
||||||
std::env::set_var("RUST_LOG", "egui=debug,egui-winit=debug,notedeck=debug,notedeck_columns=debug,notedeck_chrome=debug,enostr=debug,android_activity=debug");
|
//std::env::set_var("DAVE_ENDPOINT", "http://ollama.jb55.com/v1");
|
||||||
|
//std::env::set_var("DAVE_MODEL", "hhao/qwen2.5-coder-tools:latest");
|
||||||
|
std::env::set_var(
|
||||||
|
"RUST_LOG",
|
||||||
|
"egui=debug,egui-winit=debug,notedeck=debug,notedeck_columns=debug,notedeck_chrome=debug,enostr=debug,android_activity=debug",
|
||||||
|
);
|
||||||
|
|
||||||
//std::env::set_var(
|
//std::env::set_var(
|
||||||
// "RUST_LOG",
|
// "RUST_LOG",
|
||||||
@@ -84,8 +89,8 @@ pub async fn android_main(app: AndroidApp) {
|
|||||||
completely_unrecognized
|
completely_unrecognized
|
||||||
);
|
);
|
||||||
|
|
||||||
chrome.add_app(columns);
|
chrome.add_app(NotedeckApp::Columns(columns));
|
||||||
chrome.add_app(dave);
|
chrome.add_app(NotedeckApp::Dave(dave));
|
||||||
|
|
||||||
// test dav
|
// test dav
|
||||||
chrome.set_active(1);
|
chrome.set_active(1);
|
||||||
|
|||||||
@@ -34,7 +34,9 @@ pub fn setup_chrome(ctx: &egui::Context, args: ¬edeck::Args, theme: ThemePref
|
|||||||
pub fn setup_cc(ctx: &egui::Context, is_mobile: bool) {
|
pub fn setup_cc(ctx: &egui::Context, is_mobile: bool) {
|
||||||
fonts::setup_fonts(ctx);
|
fonts::setup_fonts(ctx);
|
||||||
|
|
||||||
//ctx.set_pixels_per_point(ctx.pixels_per_point() + UI_SCALE_FACTOR);
|
if notedeck::ui::is_compiled_as_mobile() {
|
||||||
|
ctx.set_pixels_per_point(ctx.pixels_per_point() + 0.2);
|
||||||
|
}
|
||||||
//ctx.set_pixels_per_point(1.0);
|
//ctx.set_pixels_per_point(1.0);
|
||||||
//
|
//
|
||||||
//
|
//
|
||||||
@@ -45,8 +47,6 @@ pub fn setup_cc(ctx: &egui::Context, is_mobile: bool) {
|
|||||||
ctx.all_styles_mut(|style| theme::add_custom_style(is_mobile, style));
|
ctx.all_styles_mut(|style| theme::add_custom_style(is_mobile, style));
|
||||||
}
|
}
|
||||||
|
|
||||||
//pub const UI_SCALE_FACTOR: f32 = 0.2;
|
|
||||||
|
|
||||||
pub fn generate_native_options(paths: DataPath) -> NativeOptions {
|
pub fn generate_native_options(paths: DataPath) -> NativeOptions {
|
||||||
let window_builder = Box::new(move |builder: egui::ViewportBuilder| {
|
let window_builder = Box::new(move |builder: egui::ViewportBuilder| {
|
||||||
let builder = builder
|
let builder = builder
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ use crate::{
|
|||||||
accounts::{AccountsView, AccountsViewResponse},
|
accounts::{AccountsView, AccountsViewResponse},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
use egui_winit::clipboard::Clipboard;
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
mod route;
|
mod route;
|
||||||
@@ -31,6 +32,7 @@ pub fn render_accounts_route(
|
|||||||
accounts: &mut Accounts,
|
accounts: &mut Accounts,
|
||||||
decks: &mut DecksCache,
|
decks: &mut DecksCache,
|
||||||
login_state: &mut AcquireKeyState,
|
login_state: &mut AcquireKeyState,
|
||||||
|
clipboard: &mut Clipboard,
|
||||||
route: AccountsRoute,
|
route: AccountsRoute,
|
||||||
) -> AddAccountAction {
|
) -> AddAccountAction {
|
||||||
let resp = match route {
|
let resp = match route {
|
||||||
@@ -39,7 +41,7 @@ pub fn render_accounts_route(
|
|||||||
.inner
|
.inner
|
||||||
.map(AccountsRouteResponse::Accounts),
|
.map(AccountsRouteResponse::Accounts),
|
||||||
|
|
||||||
AccountsRoute::AddAccount => AccountLoginView::new(login_state)
|
AccountsRoute::AddAccount => AccountLoginView::new(login_state, clipboard)
|
||||||
.ui(ui)
|
.ui(ui)
|
||||||
.inner
|
.inner
|
||||||
.map(AccountsRouteResponse::AddAccount),
|
.map(AccountsRouteResponse::AddAccount),
|
||||||
|
|||||||
@@ -28,6 +28,10 @@ impl<'a> AcquireKeyState {
|
|||||||
textedit_closure(&mut self.desired_key)
|
textedit_closure(&mut self.desired_key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn input_buffer(&mut self) -> &mut String {
|
||||||
|
&mut self.desired_key
|
||||||
|
}
|
||||||
|
|
||||||
/// User pressed the 'acquire' button
|
/// User pressed the 'acquire' button
|
||||||
pub fn apply_acquire(&'a mut self) {
|
pub fn apply_acquire(&'a mut self) {
|
||||||
let new_promise = match &self.promise_query {
|
let new_promise = match &self.promise_query {
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
#![cfg_attr(target_os = "android", allow(dead_code, unused_variables))]
|
||||||
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use base64::{prelude::BASE64_URL_SAFE, Engine};
|
use base64::{prelude::BASE64_URL_SAFE, Engine};
|
||||||
|
|||||||
@@ -423,6 +423,7 @@ fn render_nav_body(
|
|||||||
ctx.accounts,
|
ctx.accounts,
|
||||||
&mut app.decks_cache,
|
&mut app.decks_cache,
|
||||||
&mut app.view_state.login,
|
&mut app.view_state.login,
|
||||||
|
ctx.clipboard,
|
||||||
*amr,
|
*amr,
|
||||||
);
|
);
|
||||||
let txn = Transaction::new(ctx.ndb).expect("txn");
|
let txn = Transaction::new(ctx.ndb).expect("txn");
|
||||||
|
|||||||
@@ -507,7 +507,7 @@ impl TimelineKind {
|
|||||||
TimelineKind::Universe => Some(Timeline::new(
|
TimelineKind::Universe => Some(Timeline::new(
|
||||||
TimelineKind::Universe,
|
TimelineKind::Universe,
|
||||||
FilterState::ready(universe_filter()),
|
FilterState::ready(universe_filter()),
|
||||||
TimelineTab::no_replies(),
|
TimelineTab::full_tabs(),
|
||||||
)),
|
)),
|
||||||
|
|
||||||
TimelineKind::Thread(root_id) => Some(Timeline::thread(root_id)),
|
TimelineKind::Thread(root_id) => Some(Timeline::thread(root_id)),
|
||||||
|
|||||||
@@ -80,6 +80,9 @@ pub fn render_timeline_route(
|
|||||||
// default truncated everywher eelse
|
// default truncated everywher eelse
|
||||||
note_options.set_truncate(false);
|
note_options.set_truncate(false);
|
||||||
|
|
||||||
|
// text is selectable in threads
|
||||||
|
note_options.set_selectable_text(true);
|
||||||
|
|
||||||
ui::ThreadView::new(
|
ui::ThreadView::new(
|
||||||
timeline_cache,
|
timeline_cache,
|
||||||
unknown_ids,
|
unknown_ids,
|
||||||
|
|||||||
@@ -4,11 +4,14 @@ use egui::{
|
|||||||
Align, Button, Color32, Frame, Image, InnerResponse, Margin, RichText, TextBuffer, Vec2,
|
Align, Button, Color32, Frame, Image, InnerResponse, Margin, RichText, TextBuffer, Vec2,
|
||||||
};
|
};
|
||||||
use egui::{Layout, TextEdit};
|
use egui::{Layout, TextEdit};
|
||||||
|
use egui_winit::clipboard::Clipboard;
|
||||||
use enostr::Keypair;
|
use enostr::Keypair;
|
||||||
use notedeck::{fonts::get_font_size, AppAction, NotedeckTextStyle};
|
use notedeck::{fonts::get_font_size, AppAction, NotedeckTextStyle};
|
||||||
|
use notedeck_ui::context_menu::{input_context, PasteBehavior};
|
||||||
|
|
||||||
pub struct AccountLoginView<'a> {
|
pub struct AccountLoginView<'a> {
|
||||||
manager: &'a mut AcquireKeyState,
|
manager: &'a mut AcquireKeyState,
|
||||||
|
clipboard: &'a mut Clipboard,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum AccountLoginResponse {
|
pub enum AccountLoginResponse {
|
||||||
@@ -17,8 +20,8 @@ pub enum AccountLoginResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> AccountLoginView<'a> {
|
impl<'a> AccountLoginView<'a> {
|
||||||
pub fn new(state: &'a mut AcquireKeyState) -> Self {
|
pub fn new(manager: &'a mut AcquireKeyState, clipboard: &'a mut Clipboard) -> Self {
|
||||||
AccountLoginView { manager: state }
|
AccountLoginView { manager, clipboard }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ui(&mut self, ui: &mut egui::Ui) -> InnerResponse<Option<AccountLoginResponse>> {
|
pub fn ui(&mut self, ui: &mut egui::Ui) -> InnerResponse<Option<AccountLoginResponse>> {
|
||||||
@@ -42,7 +45,9 @@ impl<'a> AccountLoginView<'a> {
|
|||||||
let button_width = 32.0;
|
let button_width = 32.0;
|
||||||
let text_edit_width = available_width - button_width;
|
let text_edit_width = available_width - button_width;
|
||||||
|
|
||||||
ui.add_sized([text_edit_width, 40.0], login_textedit(self.manager));
|
let textedit_resp = ui.add_sized([text_edit_width, 40.0], login_textedit(self.manager));
|
||||||
|
input_context(&textedit_resp, self.clipboard, self.manager.input_buffer(), PasteBehavior::Clear);
|
||||||
|
|
||||||
if eye_button(ui, self.manager.password_visible()).clicked() {
|
if eye_button(ui, self.manager.password_visible()).clicked() {
|
||||||
self.manager.toggle_password_visibility();
|
self.manager.toggle_password_visibility();
|
||||||
}
|
}
|
||||||
@@ -154,12 +159,8 @@ mod preview {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl App for AccountLoginPreview {
|
impl App for AccountLoginPreview {
|
||||||
fn update(
|
fn update(&mut self, ctx: &mut AppContext<'_>, ui: &mut egui::Ui) -> Option<AppAction> {
|
||||||
&mut self,
|
AccountLoginView::new(&mut self.manager, ctx.clipboard).ui(ui);
|
||||||
_app_ctx: &mut AppContext<'_>,
|
|
||||||
ui: &mut egui::Ui,
|
|
||||||
) -> Option<AppAction> {
|
|
||||||
AccountLoginView::new(&mut self.manager).ui(ui);
|
|
||||||
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use crate::draft::{Draft, Drafts, MentionHint};
|
use crate::draft::{Draft, Drafts, MentionHint};
|
||||||
|
#[cfg(not(target_os = "android"))]
|
||||||
use crate::media_upload::{nostrbuild_nip96_upload, MediaPath};
|
use crate::media_upload::{nostrbuild_nip96_upload, MediaPath};
|
||||||
use crate::post::{downcast_post_buffer, MentionType, NewPost};
|
use crate::post::{downcast_post_buffer, MentionType, NewPost};
|
||||||
use crate::ui::search_results::SearchResultsView;
|
use crate::ui::search_results::SearchResultsView;
|
||||||
|
|||||||
@@ -6,7 +6,12 @@ use crate::{timeline::TimelineTab, ui::timeline::TimelineTabView};
|
|||||||
use egui_winit::clipboard::Clipboard;
|
use egui_winit::clipboard::Clipboard;
|
||||||
use nostrdb::{Filter, Ndb, Transaction};
|
use nostrdb::{Filter, Ndb, Transaction};
|
||||||
use notedeck::{MuteFun, NoteAction, NoteContext, NoteRef};
|
use notedeck::{MuteFun, NoteAction, NoteContext, NoteRef};
|
||||||
use notedeck_ui::{icons::search_icon, jobs::JobsCache, padding, NoteOptions};
|
use notedeck_ui::{
|
||||||
|
context_menu::{input_context, PasteBehavior},
|
||||||
|
icons::search_icon,
|
||||||
|
jobs::JobsCache,
|
||||||
|
padding, NoteOptions,
|
||||||
|
};
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
use tracing::{error, info, warn};
|
use tracing::{error, info, warn};
|
||||||
|
|
||||||
@@ -296,21 +301,7 @@ fn search_box(
|
|||||||
.frame(false),
|
.frame(false),
|
||||||
);
|
);
|
||||||
|
|
||||||
response.context_menu(|ui| {
|
input_context(&response, clipboard, input, PasteBehavior::Append);
|
||||||
if ui.button("paste").clicked() {
|
|
||||||
if let Some(text) = clipboard.get() {
|
|
||||||
input.clear();
|
|
||||||
input.push_str(&text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if response.middle_clicked() {
|
|
||||||
if let Some(text) = clipboard.get() {
|
|
||||||
input.clear();
|
|
||||||
input.push_str(&text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut requested_focus = false;
|
let mut requested_focus = false;
|
||||||
if focus_state == FocusState::ShouldRequestFocus {
|
if focus_state == FocusState::ShouldRequestFocus {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ edition = "2021"
|
|||||||
version.workspace = true
|
version.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
async-openai = "0.28.0"
|
async-openai = { version = "0.28.0", features = ["rustls-webpki-roots"] }
|
||||||
egui = { workspace = true }
|
egui = { workspace = true }
|
||||||
sha2 = { workspace = true }
|
sha2 = { workspace = true }
|
||||||
notedeck = { workspace = true }
|
notedeck = { workspace = true }
|
||||||
|
|||||||
@@ -305,6 +305,11 @@ impl<'a> DaveUi<'a> {
|
|||||||
//ui.add_space(Self::chat_margin(ui.ctx()) as f32);
|
//ui.add_space(Self::chat_margin(ui.ctx()) as f32);
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.with_layout(Layout::right_to_left(Align::Max), |ui| {
|
ui.with_layout(Layout::right_to_left(Align::Max), |ui| {
|
||||||
|
let mut dave_response = DaveResponse::none();
|
||||||
|
if ui.add(egui::Button::new("Ask")).clicked() {
|
||||||
|
dave_response = DaveResponse::send();
|
||||||
|
}
|
||||||
|
|
||||||
let r = ui.add(
|
let r = ui.add(
|
||||||
egui::TextEdit::multiline(self.input)
|
egui::TextEdit::multiline(self.input)
|
||||||
.desired_width(f32::INFINITY)
|
.desired_width(f32::INFINITY)
|
||||||
@@ -322,7 +327,7 @@ impl<'a> DaveUi<'a> {
|
|||||||
if r.has_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter)) {
|
if r.has_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter)) {
|
||||||
DaveResponse::send()
|
DaveResponse::send()
|
||||||
} else {
|
} else {
|
||||||
DaveResponse::none()
|
dave_response
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.inner
|
.inner
|
||||||
|
|||||||
@@ -6,6 +6,9 @@ version.workspace = true
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
egui = { workspace = true }
|
egui = { workspace = true }
|
||||||
egui_extras = { workspace = true }
|
egui_extras = { workspace = true }
|
||||||
|
egui-winit = { workspace = true }
|
||||||
|
strum_macros = { workspace = true }
|
||||||
|
strum = { workspace = true }
|
||||||
ehttp = { workspace = true }
|
ehttp = { workspace = true }
|
||||||
nostrdb = { workspace = true }
|
nostrdb = { workspace = true }
|
||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
/// Context menu helpers (paste, etc)
|
||||||
|
use egui_winit::clipboard::Clipboard;
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||||
|
pub enum PasteBehavior {
|
||||||
|
Clear,
|
||||||
|
Append,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_paste(clipboard: &mut Clipboard, input: &mut String, paste_behavior: PasteBehavior) {
|
||||||
|
if let Some(text) = clipboard.get() {
|
||||||
|
// if called with clearing_input_context, then we clear before
|
||||||
|
// we paste. Useful for certain fields like passwords, etc
|
||||||
|
match paste_behavior {
|
||||||
|
PasteBehavior::Clear => input.clear(),
|
||||||
|
PasteBehavior::Append => {}
|
||||||
|
}
|
||||||
|
input.push_str(&text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn input_context(
|
||||||
|
response: &egui::Response,
|
||||||
|
clipboard: &mut Clipboard,
|
||||||
|
input: &mut String,
|
||||||
|
paste_behavior: PasteBehavior,
|
||||||
|
) {
|
||||||
|
response.context_menu(|ui| {
|
||||||
|
if ui.button("Paste").clicked() {
|
||||||
|
handle_paste(clipboard, input, paste_behavior);
|
||||||
|
ui.close_menu();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ui.button("Copy").clicked() {
|
||||||
|
clipboard.set_text(input.to_owned());
|
||||||
|
ui.close_menu();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ui.button("Cut").clicked() {
|
||||||
|
clipboard.set_text(input.to_owned());
|
||||||
|
input.clear();
|
||||||
|
ui.close_menu();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if response.middle_clicked() {
|
||||||
|
handle_paste(clipboard, input, paste_behavior)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ pub mod blur;
|
|||||||
pub mod colors;
|
pub mod colors;
|
||||||
pub mod constants;
|
pub mod constants;
|
||||||
pub mod contacts;
|
pub mod contacts;
|
||||||
|
pub mod context_menu;
|
||||||
pub mod gif;
|
pub mod gif;
|
||||||
pub mod icons;
|
pub mod icons;
|
||||||
pub mod images;
|
pub mod images;
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ mkShell ({
|
|||||||
android-nixpkgs = callPackage (fetchTarball android) { };
|
android-nixpkgs = callPackage (fetchTarball android) { };
|
||||||
#ndk-version = "24.0.8215888";
|
#ndk-version = "24.0.8215888";
|
||||||
ndk-version = "27.2.12479018";
|
ndk-version = "27.2.12479018";
|
||||||
|
android-version = "31";
|
||||||
|
|
||||||
android-sdk = android-nixpkgs.sdk (sdkPkgs: with sdkPkgs; [
|
android-sdk = android-nixpkgs.sdk (sdkPkgs: with sdkPkgs; [
|
||||||
cmdline-tools-latest
|
cmdline-tools-latest
|
||||||
@@ -67,6 +68,7 @@ mkShell ({
|
|||||||
{
|
{
|
||||||
buildInputs = [ android-sdk ];
|
buildInputs = [ android-sdk ];
|
||||||
ANDROID_NDK_ROOT = android-ndk-path;
|
ANDROID_NDK_ROOT = android-ndk-path;
|
||||||
|
ANDROID_JAR = "${android-sdk-path}/platforms/android-${android-version}/android.jar";
|
||||||
GRADLE_OPTS = "-Dorg.gradle.project.android.aapt2FromMavenOverride=${aapt}/bin/aapt2";
|
GRADLE_OPTS = "-Dorg.gradle.project.android.aapt2FromMavenOverride=${aapt}/bin/aapt2";
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user