Implement soft keyboard visibility on Android

- Added `SoftKeyboardContext` enum and support for calculating keyboard
  insets from both virtual and platform sources

- Updated `AppContext` to provide `soft_keyboard_rect` for determining
  visible keyboard area

- Adjusted UI rendering to shift content when input boxes intersect with
  the soft keyboard, preventing overlap

- Modified `MainActivity` and Android manifest to use
  `windowSoftInputMode="adjustResize"` and updated window inset handling

- Introduced helper functions (`include_input`, `input_rect`,
  `clear_input_rect`) in `notedeck_ui` for tracking focused input boxes

- Fixed Android JNI keyboard height reporting to clamp negative values

Together, these changes allow the app to correctly detect and respond
to soft keyboard visibility on Android, ensuring input fields remain
accessible when typing.

Fixes: https://github.com/damus-io/notedeck/issues/946
Fixes: https://github.com/damus-io/notedeck/issues/1043
This commit is contained in:
William Casarin
2025-08-07 11:25:41 -07:00
parent 3aa4d00053
commit 77ac91e810
24 changed files with 276 additions and 94 deletions

View File

@@ -10,6 +10,7 @@
android:name=".MainActivity"
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden|uiMode|fontScale|smallestScreenSize"
android:exported="true"
android:windowSoftInputMode="adjustResize"
android:launchMode="singleTask"
>
<intent-filter>
@@ -37,4 +38,4 @@
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
</manifest>
</manifest>

View File

@@ -26,12 +26,15 @@ public class MainActivity extends GameActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
// Shrink view so it does not get covered by insets.
super.onCreate(savedInstanceState);
setupInsets();
//setupFullscreen()
keyboardHelper = new KeyboardHeightHelper(this);
//keyboardHelper = new KeyboardHeightHelper(this);
super.onCreate(savedInstanceState);
}
private void setupFullscreen() {
@@ -61,6 +64,18 @@ public class MainActivity extends GameActivity {
}
private void setupInsets() {
// NOTE(jb55): This is needed for keyboard visibility. Without this the
// window still gets the right insets, but theyre consumed before they
// reach the NDK side.
//WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
// NOTE(jb55): This is needed for keyboard visibility. If the bars are
// permanently gone, Android routes the keyboard over the GL surface and
// doesnt change insets.
//WindowInsetsControllerCompat ic = WindowCompat.getInsetsController(getWindow(), getWindow().getDecorView());
//ic.setSystemBarsBehavior(WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
View content = getContent();
ViewCompat.setOnApplyWindowInsetsListener(content, (v, windowInsets) -> {
Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars());
@@ -72,12 +87,13 @@ public class MainActivity extends GameActivity {
mlp.rightMargin = insets.right;
v.setLayoutParams(mlp);
return WindowInsetsCompat.CONSUMED;
return windowInsets;
});
WindowCompat.setDecorFitsSystemWindows(getWindow(), true);
WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
}
/*
@Override
public void onResume() {
super.onResume();
@@ -95,6 +111,7 @@ public class MainActivity extends GameActivity {
super.onDestroy();
keyboardHelper.close();
}
*/
@Override
public boolean onTouchEvent(MotionEvent event) {