From 13c753e16a51054fc11675ac1ecd47f4a3578212 Mon Sep 17 00:00:00 2001 From: Eduardo Pereira de Sousa Date: Thu, 6 May 2021 19:42:11 -0300 Subject: [PATCH 01/27] Update gradle plugin to version 4.1.3 (#32) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index ea46394..0321ce5 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:4.0.0' + classpath 'com.android.tools.build:gradle:4.1.3' } } From 0f4cbe3ecc84790362ad1035fdc95758db0f5d14 Mon Sep 17 00:00:00 2001 From: agnostic-apollo <31106828+agnostic-apollo@users.noreply.github.com> Date: Fri, 2 Jul 2021 09:33:28 +0500 Subject: [PATCH 02/27] Update debug_build.yml --- .github/workflows/debug_build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/debug_build.yml b/.github/workflows/debug_build.yml index 457424c..b13d07a 100644 --- a/.github/workflows/debug_build.yml +++ b/.github/workflows/debug_build.yml @@ -12,7 +12,7 @@ jobs: run: | ./gradlew assembleDebug - name: Store generated APK file - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v2 with: name: termux-float path: ./app/build/outputs/apk/debug/app-debug.apk From 17a76b2d6f6201662f67c139d9d1a8e6563660c9 Mon Sep 17 00:00:00 2001 From: David Kramer Date: Mon, 30 Aug 2021 17:13:54 -0500 Subject: [PATCH 03/27] Implement floating "chat head" bubble (#35) * Implement floating bubble for TermuxFloatView * Add Window Controls toolbar w/ icons * Don't show the long press toast if moving while minimized * Remove focus when displaying as bubble * Focus window if top control bar is tapped --- .../termux/window/FloatingBubbleManager.java | 135 ++++++++++++++++++ .../com/termux/window/TermuxFloatView.java | 58 +++++++- app/src/main/res/drawable/ic_exit_icon.xml | 12 ++ .../main/res/drawable/ic_minimize_icon.xml | 15 ++ app/src/main/res/drawable/round_button.xml | 8 ++ .../drawable/round_button_with_outline.xml | 10 ++ app/src/main/res/layout/activity_main.xml | 30 ++++ 7 files changed, 266 insertions(+), 2 deletions(-) create mode 100644 app/src/main/java/com/termux/window/FloatingBubbleManager.java create mode 100644 app/src/main/res/drawable/ic_exit_icon.xml create mode 100644 app/src/main/res/drawable/ic_minimize_icon.xml create mode 100644 app/src/main/res/drawable/round_button.xml create mode 100644 app/src/main/res/drawable/round_button_with_outline.xml diff --git a/app/src/main/java/com/termux/window/FloatingBubbleManager.java b/app/src/main/java/com/termux/window/FloatingBubbleManager.java new file mode 100644 index 0000000..11e0ce3 --- /dev/null +++ b/app/src/main/java/com/termux/window/FloatingBubbleManager.java @@ -0,0 +1,135 @@ +package com.termux.window; + +import android.graphics.drawable.Drawable; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; + +import com.termux.view.TerminalView; + +/** + * Handles displaying our TermuxFloatView as a collapsed bubble and restoring back + * to its original display. + */ +public class FloatingBubbleManager { + private static final int BUBBLE_SIZE = 200; + + private TermuxFloatView mTermuxFloatView; + + private boolean mIsMinimized; + + // preserve original layout values so we can restore to normal window + // from our bubble + private int mOriginalLayoutWidth; + private int mOriginalLayoutHeight; + private boolean mDidCaptureOriginalValues; + private Drawable mOriginalTerminalViewBackground; + private Drawable mOriginalFloatViewBackground; + + + public FloatingBubbleManager(TermuxFloatView termuxFloatView) { + mTermuxFloatView = termuxFloatView; + } + + public void toggleBubble() { + if (isMinimized()) { + displayAsFloatingWindow(); + } else { + displayAsFloatingBubble(); + } + } + + public void updateLongPressBackgroundResource(boolean isInLongPressState) { + if (isMinimized()) { + return; + } + mTermuxFloatView.setBackgroundResource(isInLongPressState ? R.drawable.floating_window_background_resize : R.drawable.floating_window_background); + } + + public void displayAsFloatingBubble() { + captureOriginalLayoutValues(); + + WindowManager.LayoutParams layoutParams = getLayoutParams(); + layoutParams.width = BUBBLE_SIZE; + layoutParams.height = BUBBLE_SIZE; + + TerminalView terminalView = getTerminalView(); + terminalView.setBackgroundResource(R.drawable.round_button); + terminalView.setClipToOutline(true); + + TermuxFloatView termuxFloatView = getTermuxFloatView(); + termuxFloatView.setBackgroundResource(R.drawable.round_button_with_outline); + termuxFloatView.setClipToOutline(true); + termuxFloatView.hideTouchKeyboard(); + termuxFloatView.changeFocus(false); + + ViewGroup windowControls = termuxFloatView.findViewById(R.id.window_controls); + windowControls.setVisibility(View.GONE); + + getWindowManager().updateViewLayout(termuxFloatView, layoutParams); + mIsMinimized = true; + } + + public void displayAsFloatingWindow() { + WindowManager.LayoutParams layoutParams = getLayoutParams(); + + // restore back to previous values + layoutParams.width = mOriginalLayoutWidth; + layoutParams.height = mOriginalLayoutHeight; + + TerminalView terminalView = getTerminalView(); + terminalView.setBackground(mOriginalTerminalViewBackground); + terminalView.setClipToOutline(false); + + TermuxFloatView termuxFloatView = getTermuxFloatView(); + termuxFloatView.setBackground(mOriginalFloatViewBackground); + termuxFloatView.setClipToOutline(false); + + ViewGroup windowControls = termuxFloatView.findViewById(R.id.window_controls); + windowControls.setVisibility(View.VISIBLE); + + getWindowManager().updateViewLayout(termuxFloatView, layoutParams); + mIsMinimized = false; + + // clear so we can capture proper values on next minimize + mDidCaptureOriginalValues = false; + } + + public boolean isMinimized() { + return mIsMinimized; + } + + private void captureOriginalLayoutValues() { + if (!mDidCaptureOriginalValues) { + WindowManager.LayoutParams layoutParams = getLayoutParams(); + mOriginalLayoutWidth = layoutParams.width; + mOriginalLayoutHeight = layoutParams.height; + + mOriginalTerminalViewBackground = getTerminalView().getBackground(); + mOriginalFloatViewBackground = getTermuxFloatView().getBackground(); + mDidCaptureOriginalValues = true; + } + } + + public void cleanup() { + mTermuxFloatView = null; + mOriginalFloatViewBackground = null; + mOriginalTerminalViewBackground = null; + } + + private TermuxFloatView getTermuxFloatView() { + return mTermuxFloatView; + } + + private TerminalView getTerminalView() { + return mTermuxFloatView.mTerminalView; + } + + private WindowManager getWindowManager() { + return mTermuxFloatView.mWindowManager; + } + + private WindowManager.LayoutParams getLayoutParams() { + return (WindowManager.LayoutParams) mTermuxFloatView.getLayoutParams(); + } +} diff --git a/app/src/main/java/com/termux/window/TermuxFloatView.java b/app/src/main/java/com/termux/window/TermuxFloatView.java index 88da566..5b0f4df 100644 --- a/app/src/main/java/com/termux/window/TermuxFloatView.java +++ b/app/src/main/java/com/termux/window/TermuxFloatView.java @@ -2,6 +2,7 @@ import android.annotation.SuppressLint; import android.content.Context; +import android.content.Intent; import android.graphics.PixelFormat; import android.graphics.Point; import android.graphics.Typeface; @@ -12,8 +13,11 @@ import android.view.MotionEvent; import android.view.ScaleGestureDetector; import android.view.ScaleGestureDetector.OnScaleGestureListener; +import android.view.View; +import android.view.ViewGroup; import android.view.WindowManager; import android.view.inputmethod.InputMethodManager; +import android.widget.Button; import android.widget.LinearLayout; import android.widget.Toast; @@ -35,6 +39,8 @@ public class TermuxFloatView extends LinearLayout { InputMethodManager imm; TerminalView mTerminalView; + ViewGroup mWindowControls; + FloatingBubbleManager mFloatingBubbleManager; private boolean withFocus = true; int initialX; @@ -46,6 +52,8 @@ public class TermuxFloatView extends LinearLayout { final int[] location = new int[2]; + final int[] windowControlsLocation = new int[2]; + final ScaleGestureDetector mScaleDetector = new ScaleGestureDetector(getContext(), new OnScaleGestureListener() { private static final int MIN_SIZE = 50; @@ -89,6 +97,19 @@ private static int computeLayoutFlags(boolean withFocus) { public void initializeFloatingWindow() { mTerminalView = findViewById(R.id.terminal_view); mTerminalView.setOnKeyListener(new TermuxFloatViewClient(this)); + mFloatingBubbleManager = new FloatingBubbleManager(this); + initWindowControls(); + } + + private void initWindowControls() { + mWindowControls = findViewById(R.id.window_controls); + mWindowControls.setOnClickListener(v -> changeFocus(true)); + + Button minimizeButton = findViewById(R.id.minimize_button); + minimizeButton.setOnClickListener(v -> mFloatingBubbleManager.toggleBubble()); + + Button exitButton = findViewById(R.id.exit_button); + exitButton.setOnClickListener(v -> exit()); } @Override @@ -147,6 +168,13 @@ public boolean onInterceptTouchEvent(MotionEvent event) { int y = location[1]; float touchX = event.getRawX(); float touchY = event.getRawY(); + + if (didClickInsideWindowControls(touchX, touchY)) { + // avoid unintended focus event if we are tapping on our window controls + // so that keyboard doesn't possibly show briefly + return false; + } + boolean clickedInside = (touchX >= x) && (touchX <= (x + layoutParams.width)) && (touchY >= y) && (touchY <= (y + layoutParams.height)); switch (event.getAction()) { @@ -163,15 +191,31 @@ public boolean onInterceptTouchEvent(MotionEvent event) { return false; } + private boolean didClickInsideWindowControls(float touchX, float touchY) { + if (mWindowControls.getVisibility() == View.GONE) { + return false; + } + mWindowControls.getLocationOnScreen(windowControlsLocation); + int controlsX = windowControlsLocation[0]; + int controlsY = windowControlsLocation[1]; + + return (touchX >= controlsX && touchX <= controlsX + mWindowControls.getWidth()) && + (touchY >= controlsY && touchY <= controlsY + mWindowControls.getHeight()); + } + void showTouchKeyboard() { mTerminalView.post(() -> imm.showSoftInput(mTerminalView, InputMethodManager.SHOW_IMPLICIT)); } + void hideTouchKeyboard() { + mTerminalView.post(() -> imm.hideSoftInputFromWindow(mTerminalView.getWindowToken(), InputMethodManager.HIDE_IMPLICIT_ONLY)); + } + void updateLongPressMode(boolean newValue) { isInLongPressState = newValue; - setBackgroundResource(newValue ? R.drawable.floating_window_background_resize : R.drawable.floating_window_background); + mFloatingBubbleManager.updateLongPressBackgroundResource(isInLongPressState); setAlpha(newValue ? ALPHA_MOVING : (withFocus ? ALPHA_FOCUS : ALPHA_NOT_FOCUS)); - if (newValue) { + if (newValue && !mFloatingBubbleManager.isMinimized()) { Toast toast = Toast.makeText(getContext(), R.string.after_long_press, Toast.LENGTH_SHORT); toast.setGravity(Gravity.CENTER, 0, 0); toast.show(); @@ -207,6 +251,9 @@ public boolean onTouchEvent(MotionEvent event) { * Visually indicate focus and show the soft input as needed. */ void changeFocus(boolean newFocus) { + if (newFocus && mFloatingBubbleManager.isMinimized()) { + mFloatingBubbleManager.displayAsFloatingWindow(); + } if (newFocus == withFocus) { if (newFocus) showTouchKeyboard(); return; @@ -219,5 +266,12 @@ void changeFocus(boolean newFocus) { public void closeFloatingWindow() { mWindowManager.removeView(this); + mFloatingBubbleManager.cleanup(); + mFloatingBubbleManager = null; + } + + private void exit() { + Intent intent = new Intent(getContext(), TermuxFloatService.class); + getContext().stopService(intent); } } diff --git a/app/src/main/res/drawable/ic_exit_icon.xml b/app/src/main/res/drawable/ic_exit_icon.xml new file mode 100644 index 0000000..12f9875 --- /dev/null +++ b/app/src/main/res/drawable/ic_exit_icon.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_minimize_icon.xml b/app/src/main/res/drawable/ic_minimize_icon.xml new file mode 100644 index 0000000..918d3a3 --- /dev/null +++ b/app/src/main/res/drawable/ic_minimize_icon.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/round_button.xml b/app/src/main/res/drawable/round_button.xml new file mode 100644 index 0000000..c5f59c9 --- /dev/null +++ b/app/src/main/res/drawable/round_button.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/round_button_with_outline.xml b/app/src/main/res/drawable/round_button_with_outline.xml new file mode 100644 index 0000000..0a14446 --- /dev/null +++ b/app/src/main/res/drawable/round_button_with_outline.xml @@ -0,0 +1,10 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 8acbdd3..65b0b10 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -4,8 +4,38 @@ android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="@drawable/floating_window_background" + android:orientation="vertical" android:padding="1px" > + +