android: capture current keyboard height
expose a new virtual_keyboard_height function under notedeck::platform::android which gets the current height of the virtual keyboard. We can use this to tranlate the view out of the way Signed-off-by: William Casarin <jb55@jb55.com>
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -2783,6 +2783,7 @@ dependencies = [
|
|||||||
"enostr",
|
"enostr",
|
||||||
"hex",
|
"hex",
|
||||||
"image",
|
"image",
|
||||||
|
"jni",
|
||||||
"mime_guess",
|
"mime_guess",
|
||||||
"nostrdb",
|
"nostrdb",
|
||||||
"poll-promise",
|
"poll-promise",
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ sha2 = "0.10.8"
|
|||||||
bincode = "1.3.3"
|
bincode = "1.3.3"
|
||||||
mime_guess = "2.0.5"
|
mime_guess = "2.0.5"
|
||||||
pretty_assertions = "1.4.1"
|
pretty_assertions = "1.4.1"
|
||||||
|
jni = "0.21.1"
|
||||||
|
|
||||||
[profile.small]
|
[profile.small]
|
||||||
inherits = 'release'
|
inherits = 'release'
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ description = "The APIs and data structures used by notedeck apps"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nostrdb = { workspace = true }
|
nostrdb = { workspace = true }
|
||||||
|
jni = { workspace = true }
|
||||||
url = { workspace = true }
|
url = { workspace = true }
|
||||||
strum = { workspace = true }
|
strum = { workspace = true }
|
||||||
strum_macros = { workspace = true }
|
strum_macros = { workspace = true }
|
||||||
@@ -35,5 +36,8 @@ tempfile = { workspace = true }
|
|||||||
[target.'cfg(target_os = "macos")'.dependencies]
|
[target.'cfg(target_os = "macos")'.dependencies]
|
||||||
security-framework = { workspace = true }
|
security-framework = { workspace = true }
|
||||||
|
|
||||||
|
[target.'cfg(target_os = "android")'.dependencies]
|
||||||
|
jni = { workspace = true }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
profiling = ["puffin", "puffin_egui"]
|
profiling = ["puffin", "puffin_egui"]
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ mod muted;
|
|||||||
pub mod note;
|
pub mod note;
|
||||||
mod notecache;
|
mod notecache;
|
||||||
mod persist;
|
mod persist;
|
||||||
|
pub mod platform;
|
||||||
pub mod relay_debug;
|
pub mod relay_debug;
|
||||||
pub mod relayspec;
|
pub mod relayspec;
|
||||||
mod result;
|
mod result;
|
||||||
|
|||||||
26
crates/notedeck/src/platform/android.rs
Normal file
26
crates/notedeck/src/platform/android.rs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
use std::sync::atomic::{AtomicI32, Ordering};
|
||||||
|
use tracing::debug;
|
||||||
|
|
||||||
|
// Thread-safe static global
|
||||||
|
static KEYBOARD_HEIGHT: AtomicI32 = AtomicI32::new(0);
|
||||||
|
|
||||||
|
/// This function is called by our main notedeck android activity when the
|
||||||
|
/// keyboard height changes. You can use [`virtual_keyboard_height`] to access
|
||||||
|
/// this
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn Java_com_damus_notedeck_KeyboardHeightHelper_nativeKeyboardHeightChanged(
|
||||||
|
_env: jni::JNIEnv,
|
||||||
|
_class: jni::objects::JClass,
|
||||||
|
height: jni::sys::jint,
|
||||||
|
) {
|
||||||
|
debug!("updating virtual keyboard height {}", height);
|
||||||
|
|
||||||
|
// Convert and store atomically
|
||||||
|
KEYBOARD_HEIGHT.store(height as i32, Ordering::SeqCst);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the current Android virtual keyboard height. Useful for transforming
|
||||||
|
/// the view
|
||||||
|
pub fn virtual_keyboard_height() -> i32 {
|
||||||
|
KEYBOARD_HEIGHT.load(Ordering::SeqCst)
|
||||||
|
}
|
||||||
2
crates/notedeck/src/platform/mod.rs
Normal file
2
crates/notedeck/src/platform/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
#[cfg(target_os = "android")]
|
||||||
|
pub mod android;
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package com.damus.notedeck;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.res.Configuration;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
public class KeyboardHeightHelper {
|
||||||
|
private static final String TAG = "KeyboardHeightHelper";
|
||||||
|
private KeyboardHeightProvider keyboardHeightProvider;
|
||||||
|
private Activity activity;
|
||||||
|
|
||||||
|
// Static JNI method not tied to any specific activity
|
||||||
|
private static native void nativeKeyboardHeightChanged(int height);
|
||||||
|
|
||||||
|
public KeyboardHeightHelper(Activity activity) {
|
||||||
|
this.activity = activity;
|
||||||
|
keyboardHeightProvider = new KeyboardHeightProvider(activity);
|
||||||
|
|
||||||
|
// Create observer implementation
|
||||||
|
KeyboardHeightObserver observer = (height, orientation) -> {
|
||||||
|
Log.d(TAG, "Keyboard height: " + height + "px, orientation: " +
|
||||||
|
(orientation == Configuration.ORIENTATION_PORTRAIT ? "portrait" : "landscape"));
|
||||||
|
|
||||||
|
// Call the generic native method
|
||||||
|
nativeKeyboardHeightChanged(height);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Set up the provider
|
||||||
|
keyboardHeightProvider.setKeyboardHeightObserver(observer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void start() {
|
||||||
|
// Start the keyboard height provider after the view is ready
|
||||||
|
final View contentView = activity.findViewById(android.R.id.content);
|
||||||
|
contentView.post(() -> {
|
||||||
|
keyboardHeightProvider.start();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stop() {
|
||||||
|
keyboardHeightProvider.setKeyboardHeightObserver(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close() {
|
||||||
|
keyboardHeightProvider.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of Siebe Projects samples.
|
||||||
|
*
|
||||||
|
* Siebe Projects samples is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the Lesser GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Siebe Projects samples is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* Lesser GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the Lesser GNU General Public License
|
||||||
|
* along with Siebe Projects samples. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.damus.notedeck;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The observer that will be notified when the height of
|
||||||
|
* the keyboard has changed
|
||||||
|
*/
|
||||||
|
public interface KeyboardHeightObserver {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the keyboard height has changed, 0 means keyboard is closed,
|
||||||
|
* >= 1 means keyboard is opened.
|
||||||
|
*
|
||||||
|
* @param height The height of the keyboard in pixels
|
||||||
|
* @param orientation The orientation either: Configuration.ORIENTATION_PORTRAIT or
|
||||||
|
* Configuration.ORIENTATION_LANDSCAPE
|
||||||
|
*/
|
||||||
|
void onKeyboardHeightChanged(int height, int orientation);
|
||||||
|
}
|
||||||
@@ -0,0 +1,174 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of Siebe Projects samples.
|
||||||
|
*
|
||||||
|
* Siebe Projects samples is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the Lesser GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Siebe Projects samples is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* Lesser GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the Lesser GNU General Public License
|
||||||
|
* along with Siebe Projects samples. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.damus.notedeck;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
|
||||||
|
import android.content.res.Configuration;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.graphics.Point;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.graphics.drawable.ColorDrawable;
|
||||||
|
import android.util.DisplayMetrics;
|
||||||
|
|
||||||
|
import android.view.Gravity;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
|
||||||
|
|
||||||
|
import android.view.WindowManager.LayoutParams;
|
||||||
|
|
||||||
|
import android.widget.PopupWindow;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The keyboard height provider, this class uses a PopupWindow
|
||||||
|
* to calculate the window height when the floating keyboard is opened and closed.
|
||||||
|
*/
|
||||||
|
public class KeyboardHeightProvider extends PopupWindow {
|
||||||
|
|
||||||
|
/** The tag for logging purposes */
|
||||||
|
private final static String TAG = "sample_KeyboardHeightProvider";
|
||||||
|
|
||||||
|
/** The keyboard height observer */
|
||||||
|
private KeyboardHeightObserver observer;
|
||||||
|
|
||||||
|
/** The cached landscape height of the keyboard */
|
||||||
|
private int keyboardLandscapeHeight;
|
||||||
|
|
||||||
|
/** The cached portrait height of the keyboard */
|
||||||
|
private int keyboardPortraitHeight;
|
||||||
|
|
||||||
|
/** The view that is used to calculate the keyboard height */
|
||||||
|
private View popupView;
|
||||||
|
|
||||||
|
/** The parent view */
|
||||||
|
private View parentView;
|
||||||
|
|
||||||
|
/** The root activity that uses this KeyboardHeightProvider */
|
||||||
|
private Activity activity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a new KeyboardHeightProvider
|
||||||
|
*
|
||||||
|
* @param activity The parent activity
|
||||||
|
*/
|
||||||
|
public KeyboardHeightProvider(Activity activity) {
|
||||||
|
super(activity);
|
||||||
|
this.activity = activity;
|
||||||
|
|
||||||
|
//LayoutInflater inflator = (LayoutInflater) activity.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
|
||||||
|
//this.popupView = inflator.inflate(android.R.layout.popupwindow, null, false);
|
||||||
|
this.popupView = new View(activity);
|
||||||
|
setContentView(popupView);
|
||||||
|
|
||||||
|
setSoftInputMode(LayoutParams.SOFT_INPUT_ADJUST_RESIZE | LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
|
||||||
|
setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
|
||||||
|
|
||||||
|
parentView = activity.findViewById(android.R.id.content);
|
||||||
|
|
||||||
|
setWidth(0);
|
||||||
|
setHeight(LayoutParams.MATCH_PARENT);
|
||||||
|
|
||||||
|
popupView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onGlobalLayout() {
|
||||||
|
if (popupView != null) {
|
||||||
|
handleOnGlobalLayout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start the KeyboardHeightProvider, this must be called after the onResume of the Activity.
|
||||||
|
* PopupWindows are not allowed to be registered before the onResume has finished
|
||||||
|
* of the Activity.
|
||||||
|
*/
|
||||||
|
public void start() {
|
||||||
|
|
||||||
|
if (!isShowing() && parentView.getWindowToken() != null) {
|
||||||
|
setBackgroundDrawable(new ColorDrawable(0));
|
||||||
|
showAtLocation(parentView, Gravity.NO_GRAVITY, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close the keyboard height provider,
|
||||||
|
* this provider will not be used anymore.
|
||||||
|
*/
|
||||||
|
public void close() {
|
||||||
|
this.observer = null;
|
||||||
|
dismiss();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the keyboard height observer to this provider. The
|
||||||
|
* observer will be notified when the keyboard height has changed.
|
||||||
|
* For example when the keyboard is opened or closed.
|
||||||
|
*
|
||||||
|
* @param observer The observer to be added to this provider.
|
||||||
|
*/
|
||||||
|
public void setKeyboardHeightObserver(KeyboardHeightObserver observer) {
|
||||||
|
this.observer = observer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Popup window itself is as big as the window of the Activity.
|
||||||
|
* The keyboard can then be calculated by extracting the popup view bottom
|
||||||
|
* from the activity window height.
|
||||||
|
*/
|
||||||
|
private void handleOnGlobalLayout() {
|
||||||
|
|
||||||
|
Point screenSize = new Point();
|
||||||
|
activity.getWindowManager().getDefaultDisplay().getSize(screenSize);
|
||||||
|
|
||||||
|
Rect rect = new Rect();
|
||||||
|
popupView.getWindowVisibleDisplayFrame(rect);
|
||||||
|
|
||||||
|
// REMIND, you may like to change this using the fullscreen size of the phone
|
||||||
|
// and also using the status bar and navigation bar heights of the phone to calculate
|
||||||
|
// the keyboard height. But this worked fine on a Nexus.
|
||||||
|
int orientation = getScreenOrientation();
|
||||||
|
int keyboardHeight = screenSize.y - rect.bottom;
|
||||||
|
|
||||||
|
if (keyboardHeight == 0) {
|
||||||
|
notifyKeyboardHeightChanged(0, orientation);
|
||||||
|
}
|
||||||
|
else if (orientation == Configuration.ORIENTATION_PORTRAIT) {
|
||||||
|
this.keyboardPortraitHeight = keyboardHeight;
|
||||||
|
notifyKeyboardHeightChanged(keyboardPortraitHeight, orientation);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.keyboardLandscapeHeight = keyboardHeight;
|
||||||
|
notifyKeyboardHeightChanged(keyboardLandscapeHeight, orientation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getScreenOrientation() {
|
||||||
|
return activity.getResources().getConfiguration().orientation;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notifyKeyboardHeightChanged(int height, int orientation) {
|
||||||
|
if (observer != null) {
|
||||||
|
observer.onKeyboardHeightChanged(height, orientation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,11 +20,48 @@ public class MainActivity extends GameActivity {
|
|||||||
System.loadLibrary("notedeck_chrome");
|
System.loadLibrary("notedeck_chrome");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private native void nativeOnKeyboardHeightChanged(int height);
|
||||||
|
private KeyboardHeightHelper keyboardHelper;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
// Shrink view so it does not get covered by insets.
|
// Shrink view so it does not get covered by insets.
|
||||||
|
|
||||||
View content = getWindow().getDecorView().findViewById(android.R.id.content);
|
setupInsets();
|
||||||
|
//setupFullscreen()
|
||||||
|
keyboardHelper = new KeyboardHeightHelper(this);
|
||||||
|
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupFullscreen() {
|
||||||
|
WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
|
||||||
|
|
||||||
|
WindowInsetsControllerCompat controller =
|
||||||
|
WindowCompat.getInsetsController(getWindow(), getWindow().getDecorView());
|
||||||
|
if (controller != null) {
|
||||||
|
controller.setSystemBarsBehavior(
|
||||||
|
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
||||||
|
);
|
||||||
|
controller.hide(WindowInsetsCompat.Type.systemBars());
|
||||||
|
}
|
||||||
|
|
||||||
|
//focus(getContent())
|
||||||
|
}
|
||||||
|
|
||||||
|
// not sure if this does anything
|
||||||
|
private void focus(View content) {
|
||||||
|
content.setFocusable(true);
|
||||||
|
content.setFocusableInTouchMode(true);
|
||||||
|
content.requestFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
private View getContent() {
|
||||||
|
return getWindow().getDecorView().findViewById(android.R.id.content);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupInsets() {
|
||||||
|
View content = getContent();
|
||||||
ViewCompat.setOnApplyWindowInsetsListener(content, (v, windowInsets) -> {
|
ViewCompat.setOnApplyWindowInsetsListener(content, (v, windowInsets) -> {
|
||||||
Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars());
|
Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars());
|
||||||
|
|
||||||
@@ -39,25 +76,24 @@ public class MainActivity extends GameActivity {
|
|||||||
});
|
});
|
||||||
|
|
||||||
WindowCompat.setDecorFitsSystemWindows(getWindow(), true);
|
WindowCompat.setDecorFitsSystemWindows(getWindow(), true);
|
||||||
|
}
|
||||||
|
|
||||||
//WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
keyboardHelper.start();
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
@Override
|
||||||
WindowInsetsControllerCompat controller =
|
public void onPause() {
|
||||||
WindowCompat.getInsetsController(getWindow(), getWindow().getDecorView());
|
super.onPause();
|
||||||
if (controller != null) {
|
keyboardHelper.stop();
|
||||||
controller.setSystemBarsBehavior(
|
}
|
||||||
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
|
||||||
);
|
|
||||||
controller.hide(WindowInsetsCompat.Type.systemBars());
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
content.setFocusable(true);
|
@Override
|
||||||
content.setFocusableInTouchMode(true);
|
public void onDestroy() {
|
||||||
content.requestFocus();
|
super.onDestroy();
|
||||||
|
keyboardHelper.close();
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
Reference in New Issue
Block a user