mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-25 18:52:39 +00:00
fix(app): keep expanded composer above keyboard (#4051)
Allow the composer constraint chain to shrink inside KeyboardDock so overlong drafts scroll internally without hiding controls behind the IME. Extend the Android keyboard invariant to cover the maximum-height case.
This commit is contained in:
@@ -2,9 +2,9 @@
|
||||
set -euo pipefail
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../../../.." && pwd)"
|
||||
STATE_DIR="${REPO_ROOT}/.dev/agent-device-composer-keyboard"
|
||||
STATE_DIR="${PASEO_COMPOSER_KEYBOARD_STATE_DIR:-${REPO_ROOT}/.dev/agent-device-composer-keyboard}"
|
||||
ARTIFACTS_DIR="${REPO_ROOT}/.dev/agent-device-artifacts/composer-keyboard-android"
|
||||
SESSION="composer-keyboard-android"
|
||||
SESSION="${PASEO_COMPOSER_KEYBOARD_SESSION:-composer-keyboard-android}"
|
||||
APP_ID="${PASEO_COMPOSER_KEYBOARD_APP_ID:-sh.paseo.debug}"
|
||||
DEVICE="${PASEO_COMPOSER_KEYBOARD_DEVICE:-paseo-api35}"
|
||||
HELPER_IME="com.callstack.agentdevice.imehelper/.TestInputMethodService"
|
||||
@@ -16,6 +16,7 @@ DAEMON_HOST="${PASEO_COMPOSER_KEYBOARD_DAEMON_HOST:-127.0.0.1:6770}"
|
||||
DAEMON_HOME="${PASEO_COMPOSER_KEYBOARD_DAEMON_HOME:-${REPO_ROOT}/.dev/composer-e2e-home}"
|
||||
SERVER_ID="${PASEO_COMPOSER_KEYBOARD_SERVER_ID:-}"
|
||||
MESSAGE=$'keyboard invariant line one\nline two\nline three\nline four'
|
||||
LONG_MESSAGE="$(node -e 'process.stdout.write(Array.from({ length: 180 }, (_, index) => `line${index + 1}`).join(" "))')"
|
||||
AGENT_TITLE="Keyboard dismiss QA $(date +%s)"
|
||||
|
||||
if [[ -z "${SERVER_ID}" ]]; then
|
||||
@@ -40,6 +41,18 @@ snapshot_json() {
|
||||
ad snapshot -i --json >"${output_path}"
|
||||
}
|
||||
|
||||
capture_ui_xml() {
|
||||
local output_path="$1"
|
||||
local device_path="/sdcard/paseo-composer-keyboard-window.xml"
|
||||
# Android exposes one UI Automation connection at a time. Release the
|
||||
# persistent agent-device snapshot helper before asking uiautomator for the
|
||||
# app and IME windows; agent-device reconnects it on the next interaction.
|
||||
adb shell am force-stop com.callstack.agentdevice.snapshothelper
|
||||
sleep 0.2
|
||||
adb shell uiautomator dump "${device_path}" >/dev/null
|
||||
adb pull "${device_path}" "${output_path}" >/dev/null
|
||||
}
|
||||
|
||||
clear_input() {
|
||||
ad fill 'editable=true' "x" --settle
|
||||
adb shell input keycombination 113 29
|
||||
@@ -108,6 +121,11 @@ read_keyboard_shift() {
|
||||
printf '%s\n' "$((navigation_top - ime_top))"
|
||||
}
|
||||
|
||||
read_ime_top() {
|
||||
adb shell dumpsys window | sed -n \
|
||||
's/.*type=ime frame=\[0,\([0-9][0-9]*\)\].*visible=true.*/\1/p' | head -1
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
adb shell ime set "${HELPER_IME}" >/dev/null 2>&1 || true
|
||||
AGENT_DEVICE_STATE_DIR="${STATE_DIR}" agent-device daemon stop --clean >/dev/null 2>&1 || true
|
||||
@@ -248,6 +266,20 @@ node "${ASSERT}" same-header \
|
||||
"${ARTIFACTS_DIR}/keyboard-open.png" \
|
||||
"$((header_y + 48))"
|
||||
|
||||
adb shell ime set "${HELPER_IME}" >/dev/null
|
||||
ad fill 'editable=true focused=true' "${LONG_MESSAGE}" --settle
|
||||
open_gboard "${input_x}" "${input_y}"
|
||||
capture_ui_xml "${ARTIFACTS_DIR}/long-draft-keyboard-open.xml"
|
||||
capture_screen "${ARTIFACTS_DIR}/long-draft-keyboard-open.png"
|
||||
node "${ASSERT}" xml-above-y \
|
||||
"${ARTIFACTS_DIR}/long-draft-keyboard-open.xml" \
|
||||
"Add attachment" \
|
||||
"$(read_ime_top)"
|
||||
adb shell ime set "${HELPER_IME}" >/dev/null
|
||||
ad fill 'editable=true' "${MESSAGE}" --settle
|
||||
open_gboard "${input_x}" "${input_y}"
|
||||
keyboard_shift="$(read_keyboard_shift)"
|
||||
|
||||
read -r changes_x changes_y _ < <(
|
||||
node "${ASSERT}" rect "${ARTIFACTS_DIR}/multiline-second.json" "Open Changes tab"
|
||||
)
|
||||
|
||||
@@ -27,6 +27,21 @@ if (command === "rect") {
|
||||
`${upperIdentifier} extends below the composer top: ${upperBottom} > ${lowerY}`,
|
||||
);
|
||||
}
|
||||
} else if (command === "xml-above-y") {
|
||||
const [snapshotPath, contentDescription, lowerYArgument] = args;
|
||||
const snapshot = await fs.readFile(snapshotPath, "utf8");
|
||||
const escapedDescription = contentDescription.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
const match = snapshot.match(
|
||||
new RegExp(
|
||||
`<node[^>]*content-desc="${escapedDescription}"[^>]*bounds="\\[(\\d+),(\\d+)\\]\\[(\\d+),(\\d+)\\]"`,
|
||||
),
|
||||
);
|
||||
if (!match) throw new Error(`Missing node: ${contentDescription}`);
|
||||
const upperBottom = Number(match[4]);
|
||||
const lowerY = Number(lowerYArgument);
|
||||
if (upperBottom > lowerY) {
|
||||
throw new Error(`${contentDescription} extends below the keyboard: ${upperBottom} > ${lowerY}`);
|
||||
}
|
||||
} else if (command === "same-header") {
|
||||
const [baselinePath, keyboardPath, headerBottomArgument] = args;
|
||||
const headerBottom = Number(headerBottomArgument);
|
||||
|
||||
@@ -2368,6 +2368,9 @@ function ComposerContentImpl({
|
||||
const animatedStaticStyles = RNStyleSheet.create({
|
||||
container: {
|
||||
flexDirection: "column",
|
||||
// KeyboardDock reduces the available column height with bottom padding.
|
||||
// Keep the composer's constraint chain shrinkable down to its scrolling input.
|
||||
flexShrink: 1,
|
||||
position: "relative",
|
||||
},
|
||||
});
|
||||
@@ -2378,6 +2381,7 @@ const styles = StyleSheet.create((theme: Theme) => ({
|
||||
backgroundColor: theme.colors.border,
|
||||
},
|
||||
inputAreaContainer: {
|
||||
flexShrink: 1,
|
||||
position: "relative",
|
||||
minHeight: FOOTER_HEIGHT,
|
||||
marginHorizontal: "auto",
|
||||
@@ -2391,11 +2395,13 @@ const styles = StyleSheet.create((theme: Theme) => ({
|
||||
opacity: 0.6,
|
||||
},
|
||||
inputAreaContent: {
|
||||
flexShrink: 1,
|
||||
width: "100%",
|
||||
maxWidth: MAX_CONTENT_WIDTH,
|
||||
gap: theme.spacing[3],
|
||||
},
|
||||
messageInputContainer: {
|
||||
flexShrink: 1,
|
||||
position: "relative",
|
||||
width: "100%",
|
||||
gap: theme.spacing[3],
|
||||
|
||||
@@ -1901,9 +1901,11 @@ export const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(
|
||||
|
||||
const styles = StyleSheet.create((theme: Theme) => ({
|
||||
container: {
|
||||
flexShrink: 1,
|
||||
position: "relative",
|
||||
},
|
||||
inputWrapper: {
|
||||
flexShrink: 1,
|
||||
flexDirection: "column",
|
||||
gap: theme.spacing[3],
|
||||
backgroundColor: theme.colors.surface1,
|
||||
@@ -1932,6 +1934,7 @@ const styles = StyleSheet.create((theme: Theme) => ({
|
||||
borderStyle: "dotted",
|
||||
},
|
||||
textInputScrollWrapper: {
|
||||
flexShrink: 1,
|
||||
position: "relative",
|
||||
},
|
||||
focusHintText: {
|
||||
@@ -1943,6 +1946,8 @@ const styles = StyleSheet.create((theme: Theme) => ({
|
||||
opacity: 0.5,
|
||||
},
|
||||
textInput: {
|
||||
// Preserve the controls when an ancestor constrains an overlong draft.
|
||||
flexShrink: 1,
|
||||
width: "100%",
|
||||
color: theme.colors.foreground,
|
||||
fontSize: theme.fontSize.content,
|
||||
@@ -1964,6 +1969,7 @@ const styles = StyleSheet.create((theme: Theme) => ({
|
||||
color: theme.colors.foregroundMuted,
|
||||
},
|
||||
buttonRow: {
|
||||
flexShrink: 0,
|
||||
flexDirection: "row",
|
||||
alignItems: "flex-end",
|
||||
justifyContent: "space-between",
|
||||
|
||||
@@ -1815,6 +1815,7 @@ const animatedStaticStyles = RNStyleSheet.create({
|
||||
},
|
||||
inputAreaWrapper: {
|
||||
width: "100%",
|
||||
flexShrink: 1,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user