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",
|
||||
"hex",
|
||||
"image",
|
||||
"jni",
|
||||
"mime_guess",
|
||||
"nostrdb",
|
||||
"poll-promise",
|
||||
|
||||
@@ -61,6 +61,7 @@ sha2 = "0.10.8"
|
||||
bincode = "1.3.3"
|
||||
mime_guess = "2.0.5"
|
||||
pretty_assertions = "1.4.1"
|
||||
jni = "0.21.1"
|
||||
|
||||
[profile.small]
|
||||
inherits = 'release'
|
||||
|
||||
@@ -6,6 +6,7 @@ description = "The APIs and data structures used by notedeck apps"
|
||||
|
||||
[dependencies]
|
||||
nostrdb = { workspace = true }
|
||||
jni = { workspace = true }
|
||||
url = { workspace = true }
|
||||
strum = { workspace = true }
|
||||
strum_macros = { workspace = true }
|
||||
@@ -35,5 +36,8 @@ tempfile = { workspace = true }
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
security-framework = { workspace = true }
|
||||
|
||||
[target.'cfg(target_os = "android")'.dependencies]
|
||||
jni = { workspace = true }
|
||||
|
||||
[features]
|
||||
profiling = ["puffin", "puffin_egui"]
|
||||
|
||||
@@ -11,6 +11,7 @@ mod muted;
|
||||
pub mod note;
|
||||
mod notecache;
|
||||
mod persist;
|
||||
pub mod platform;
|
||||
pub mod relay_debug;
|
||||
pub mod relayspec;
|
||||
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");
|
||||
}
|
||||
|
||||
private native void nativeOnKeyboardHeightChanged(int height);
|
||||
private KeyboardHeightHelper keyboardHelper;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
// 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) -> {
|
||||
Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars());
|
||||
|
||||
@@ -39,25 +76,24 @@ public class MainActivity extends GameActivity {
|
||||
});
|
||||
|
||||
WindowCompat.setDecorFitsSystemWindows(getWindow(), true);
|
||||
|
||||
//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());
|
||||
}
|
||||
*/
|
||||
|
||||
content.setFocusable(true);
|
||||
content.setFocusableInTouchMode(true);
|
||||
content.requestFocus();
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
keyboardHelper.start();
|
||||
}
|
||||
|
||||
super.onCreate(savedInstanceState);
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
keyboardHelper.stop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
keyboardHelper.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
Reference in New Issue
Block a user