diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/BaseActivity.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/BaseActivity.java index c3bebc7caf..9fe3c09708 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/BaseActivity.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/BaseActivity.java @@ -47,6 +47,11 @@ protected void onResume() { protected void onPostResume() { super.onPostResume(); Tools.setFullscreen(this, setFullscreen()); - Tools.ignoreNotch(PREF_IGNORE_NOTCH,this); + Tools.ignoreNotch(shouldIgnoreNotch(),this); + } + + /** @return Whether or not the notch should be ignored */ + protected boolean shouldIgnoreNotch(){ + return PREF_IGNORE_NOTCH; } } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/FatalErrorActivity.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/FatalErrorActivity.java index 429d8e7641..925f6639bd 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/FatalErrorActivity.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/FatalErrorActivity.java @@ -1,10 +1,13 @@ package net.kdt.pojavlaunch; -import android.content.*; -import android.os.*; -import androidx.appcompat.app.*; -import android.util.*; +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; + import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.app.AppCompatActivity; public class FatalErrorActivity extends AppCompatActivity { @@ -13,8 +16,13 @@ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Bundle extras = getIntent().getExtras(); - boolean storageAllow = extras.getBoolean("storageAllow"); - final String stackTrace = Log.getStackTraceString((Throwable) extras.getSerializable("throwable")); + if(extras == null) { + finish(); + return; + } + boolean storageAllow = extras.getBoolean("storageAllow", false); + Throwable throwable = (Throwable) extras.getSerializable("throwable"); + final String stackTrace = throwable != null ? Tools.printToString(throwable) : ""; String strSavePath = extras.getString("savePath"); String errHeader = storageAllow ? "Crash stack trace saved to " + strSavePath + "." : diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/LauncherActivity.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/LauncherActivity.java index 294cbb85c2..73bd5977d9 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/LauncherActivity.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/LauncherActivity.java @@ -1,5 +1,6 @@ package net.kdt.pojavlaunch; +import static android.content.res.Configuration.ORIENTATION_PORTRAIT; import android.Manifest; import android.app.NotificationManager; import android.content.Context; @@ -23,7 +24,6 @@ import com.kdt.mcgui.ProgressLayout; import com.kdt.mcgui.mcAccountSpinner; -import net.kdt.pojavlaunch.lifecycle.ContextExecutor; import net.kdt.pojavlaunch.contracts.OpenDocumentWithExtension; import net.kdt.pojavlaunch.extra.ExtraConstants; import net.kdt.pojavlaunch.extra.ExtraCore; @@ -31,6 +31,8 @@ import net.kdt.pojavlaunch.fragments.MainMenuFragment; import net.kdt.pojavlaunch.fragments.MicrosoftLoginFragment; import net.kdt.pojavlaunch.fragments.SelectAuthFragment; +import net.kdt.pojavlaunch.lifecycle.ContextAwareDoneListener; +import net.kdt.pojavlaunch.lifecycle.ContextExecutor; import net.kdt.pojavlaunch.modloaders.modpacks.ModloaderInstallTracker; import net.kdt.pojavlaunch.modloaders.modpacks.imagecache.IconCacheJanitor; import net.kdt.pojavlaunch.prefs.LauncherPreferences; @@ -40,7 +42,6 @@ import net.kdt.pojavlaunch.services.ProgressServiceKeeper; import net.kdt.pojavlaunch.tasks.AsyncMinecraftDownloader; import net.kdt.pojavlaunch.tasks.AsyncVersionList; -import net.kdt.pojavlaunch.lifecycle.ContextAwareDoneListener; import net.kdt.pojavlaunch.tasks.MinecraftDownloader; import net.kdt.pojavlaunch.utils.NotificationUtils; import net.kdt.pojavlaunch.value.launcherprofiles.LauncherProfiles; @@ -153,17 +154,27 @@ public void onFragmentResumed(@NonNull FragmentManager fm, @NonNull Fragment f) private ActivityResultLauncher mRequestNotificationPermissionLauncher; private WeakReference mRequestNotificationPermissionRunnable; + @Override + protected boolean shouldIgnoreNotch() { + return getResources().getConfiguration().orientation == ORIENTATION_PORTRAIT || super.shouldIgnoreNotch(); + } + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_pojav_launcher); + FragmentManager fragmentManager = getSupportFragmentManager(); + // If we don't have a back stack root yet... + if(fragmentManager.getBackStackEntryCount() < 1) { + // Manually add the first fragment to the backstack to get easily back to it + // There must be a better way to handle the root though... + // (artDev: No, there is not. I've spent days researching this for another unrelated project.) + fragmentManager.beginTransaction() + .setReorderingAllowed(true) + .addToBackStack("ROOT") + .add(R.id.container_fragment, MainMenuFragment.class, null, "ROOT").commit(); + } - // Manually add the first fragment to the backstack to get easily back to it - // There must be a better way to handle the root though... - this.getSupportFragmentManager().beginTransaction() - .setReorderingAllowed(true) - .addToBackStack("ROOT") - .add(R.id.container_fragment, MainMenuFragment.class, null, "ROOT").commit(); IconCacheJanitor.runJanitor(); mRequestNotificationPermissionLauncher = registerForActivityResult( diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/MinecraftGLSurface.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/MinecraftGLSurface.java index 4c84864ddf..7ea6225126 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/MinecraftGLSurface.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/MinecraftGLSurface.java @@ -10,6 +10,7 @@ import android.app.Activity; import android.content.Context; import android.graphics.SurfaceTexture; +import android.os.Build; import android.util.AttributeSet; import android.util.Log; import android.view.InputDevice; @@ -28,6 +29,7 @@ import net.kdt.pojavlaunch.customcontrols.ControlLayout; import net.kdt.pojavlaunch.customcontrols.gamepad.Gamepad; import net.kdt.pojavlaunch.customcontrols.mouse.AbstractTouchpad; +import net.kdt.pojavlaunch.customcontrols.mouse.AndroidPointerCapture; import net.kdt.pojavlaunch.customcontrols.mouse.InGUIEventProcessor; import net.kdt.pojavlaunch.customcontrols.mouse.InGameEventProcessor; import net.kdt.pojavlaunch.customcontrols.mouse.TouchEventProcessor; @@ -76,6 +78,7 @@ public class MinecraftGLSurface extends View implements GrabListener { private final InGameEventProcessor mIngameProcessor = new InGameEventProcessor(mSensitivityFactor); private final InGUIEventProcessor mInGUIProcessor = new InGUIEventProcessor(mScaleFactor); private TouchEventProcessor mCurrentTouchProcessor = mInGUIProcessor; + private AndroidPointerCapture mPointerCapture; private boolean mLastGrabState = false; public MinecraftGLSurface(Context context) { @@ -87,6 +90,12 @@ public MinecraftGLSurface(Context context, AttributeSet attributeSet) { setFocusable(true); } + @RequiresApi(api = Build.VERSION_CODES.O) + private void setUpPointerCapture(AbstractTouchpad touchpad) { + if(mPointerCapture != null) mPointerCapture.detach(); + mPointerCapture = new AndroidPointerCapture(touchpad, this, mScaleFactor); + } + /** Initialize the view and all its settings * @param isAlreadyRunning set to true to tell the view that the game is already running * (only updates the window without calling the start listener) @@ -94,6 +103,7 @@ public MinecraftGLSurface(Context context, AttributeSet attributeSet) { * when the cursor is not grabbed */ public void start(boolean isAlreadyRunning, AbstractTouchpad touchpad){ + if(MainActivity.isAndroid8OrHigher()) setUpPointerCapture(touchpad); mInGUIProcessor.setAbstractTouchpad(touchpad); if(LauncherPreferences.PREF_USE_ALTERNATE_SURFACE){ SurfaceView surfaceView = new SurfaceView(getContext()); @@ -162,7 +172,6 @@ public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) {} } - /** * The touch event for both grabbed an non-grabbed mouse state on the touch screen * Does not cover the virtual mouse touchpad @@ -175,7 +184,14 @@ public boolean onTouchEvent(MotionEvent e) { // Looking for a mouse to handle, won't have an effect if no mouse exists. for (int i = 0; i < e.getPointerCount(); i++) { - if(e.getToolType(i) != MotionEvent.TOOL_TYPE_MOUSE && e.getToolType(i) != MotionEvent.TOOL_TYPE_STYLUS ) continue; + int toolType = e.getToolType(i); + if(toolType == MotionEvent.TOOL_TYPE_MOUSE) { + if(MainActivity.isAndroid8OrHigher() && + mPointerCapture != null) { + mPointerCapture.handleAutomaticCapture(); + return true; + } + }else if(toolType != MotionEvent.TOOL_TYPE_STYLUS) continue; // Mouse found if(CallbackBridge.isGrabbing()) return false; @@ -232,32 +248,6 @@ public boolean dispatchGenericMotionEvent(MotionEvent event) { } } - //TODO MOVE THIS SOMEWHERE ELSE - /** The input event for mouse with a captured pointer */ - @RequiresApi(26) - @Override - public boolean dispatchCapturedPointerEvent(MotionEvent e) { - CallbackBridge.mouseX += (e.getX()* mScaleFactor); - CallbackBridge.mouseY += (e.getY()* mScaleFactor); - - // Position is updated by many events, hence it is send regardless of the event value - CallbackBridge.sendCursorPos(CallbackBridge.mouseX, CallbackBridge.mouseY); - - switch (e.getActionMasked()) { - case MotionEvent.ACTION_MOVE: - return true; - case MotionEvent.ACTION_BUTTON_PRESS: - return sendMouseButtonUnconverted(e.getActionButton(), true); - case MotionEvent.ACTION_BUTTON_RELEASE: - return sendMouseButtonUnconverted(e.getActionButton(), false); - case MotionEvent.ACTION_SCROLL: - CallbackBridge.sendScroll(e.getAxisValue(MotionEvent.AXIS_HSCROLL), e.getAxisValue(MotionEvent.AXIS_VSCROLL)); - return true; - default: - return false; - } - } - /** The event for keyboard/ gamepad button inputs */ public boolean processKeyEvent(KeyEvent event) { //Log.i("KeyEvent", event.toString()); @@ -404,29 +394,6 @@ private void updateGrabState(boolean isGrabbing) { mCurrentTouchProcessor = pickEventProcessor(isGrabbing); mLastGrabState = isGrabbing; } - if(!MainActivity.isAndroid8OrHigher()) return; - - boolean hasPointerCapture = hasPointerCapture(); - if(isGrabbing){ - if(!hasPointerCapture) { - requestFocus(); - if(hasWindowFocus()) requestPointerCapture(); - // Otherwise, onWindowFocusChanged() would get called. - } - return; - } - - if(hasPointerCapture) { - releasePointerCapture(); - clearFocus(); - } - } - - @Override - public void onWindowFocusChanged(boolean hasWindowFocus) { - super.onWindowFocusChanged(hasWindowFocus); - if(hasWindowFocus && CallbackBridge.isGrabbing() && - MainActivity.isAndroid8OrHigher()) requestPointerCapture(); } /** A small interface called when the listener is ready for the first time */ diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/Tools.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/Tools.java index a087c3092a..00be246e88 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/Tools.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/Tools.java @@ -600,7 +600,7 @@ private static void showError(final Context ctx, final int titleId, final String .setNegativeButton(showMore ? R.string.error_show_less : R.string.error_show_more, (p1, p2) -> showError(ctx, titleId, rolledMessage, e, exitIfOk, !showMore)) .setNeutralButton(android.R.string.copy, (p1, p2) -> { ClipboardManager mgr = (ClipboardManager) ctx.getSystemService(Context.CLIPBOARD_SERVICE); - mgr.setPrimaryClip(ClipData.newPlainText("error", Log.getStackTraceString(e))); + mgr.setPrimaryClip(ClipData.newPlainText("error", printToString(e))); if(exitIfOk) { if (ctx instanceof MainActivity) { MainActivity.fullyExit(); diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/authenticator/microsoft/MicrosoftBackgroundLogin.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/authenticator/microsoft/MicrosoftBackgroundLogin.java index b41e037a07..297b4da75d 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/authenticator/microsoft/MicrosoftBackgroundLogin.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/authenticator/microsoft/MicrosoftBackgroundLogin.java @@ -40,6 +40,7 @@ public class MicrosoftBackgroundLogin { private static final String xstsAuthUrl = "https://xsts.auth.xboxlive.com/xsts/authorize"; private static final String mcLoginUrl = "https://api.minecraftservices.com/authentication/login_with_xbox"; private static final String mcProfileUrl = "https://api.minecraftservices.com/minecraft/profile"; + private static final String mcStoreUrl = "https://api.minecraftservices.com/entitlements/mcstore"; private final boolean mIsRefresh; private final String mAuthCode; @@ -81,6 +82,7 @@ public void performLogin(@Nullable final ProgressListener progressListener, notifyProgress(progressListener, 4); String mcToken = acquireMinecraftToken(xsts[0], xsts[1]); notifyProgress(progressListener, 5); + fetchOwnedItems(mcToken); checkMcProfile(mcToken); MinecraftAccount acc = MinecraftAccount.load(mcName); @@ -255,6 +257,21 @@ private String acquireMinecraftToken(String xblUhs, String xblXsts) throws IOExc } } + private void fetchOwnedItems(String mcAccessToken) throws IOException { + URL url = new URL(mcStoreUrl); + + HttpURLConnection conn = (HttpURLConnection)url.openConnection(); + conn.setRequestProperty("Authorization", "Bearer " + mcAccessToken); + conn.setUseCaches(false); + conn.connect(); + if(conn.getResponseCode() < 200 || conn.getResponseCode() >= 300) { + throw getResponseThrowable(conn); + } + // We don't need any data from this request, it just needs to happen in order for + // the MS servers to work properly. The data from this is practically useless + // as it does not indicate whether the user owns the game through Game Pass. + } + private void checkMcProfile(String mcAccessToken) throws IOException, JSONException { URL url = new URL(mcProfileUrl); diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/AbstractTouchpad.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/AbstractTouchpad.java index cac0a11720..17dbd7b447 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/AbstractTouchpad.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/AbstractTouchpad.java @@ -1,6 +1,39 @@ package net.kdt.pojavlaunch.customcontrols.mouse; public interface AbstractTouchpad { + /** + * Get the supposed display state of the mouse (whether it should be shown when the user is in a GUI) + * Note that this does *not* reflect the actual visibility state of the mouse + * @return current supposed enabled state + */ boolean getDisplayState(); - void applyMotionVector(float[] vector); + + /** + * Apply a motion vector to the mouse in form of a two-entry float array. This will move the mouse + * on the screen and send the new cursor position to the game. + * @param vector the array that contains the vector + */ + default void applyMotionVector(float[] vector) { + applyMotionVector(vector[0], vector[1]); + } + + /** + * Apply a motion vector to the mouse in form of the separate X/Y coordinates. This will move the mouse + * on the screen and send the new cursor position to the game. + * @param x the relative X coordinate of the vector + * @param y the relative Y coordinate for the vector + */ + void applyMotionVector(float x, float y); + + /** + * Sets the state of the touchpad to "enabled" + * @param supposed if set to true, this will set the supposed display state to enabled but may not + * affect the touchpad until internal conditions are met + * if set to false it will turn the touchpad on regardless of internal conditions + */ + void enable(boolean supposed); + /** + * Sets the state of the touchpad to "disabled". + */ + void disable(); } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/AndroidPointerCapture.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/AndroidPointerCapture.java new file mode 100644 index 0000000000..100b1b0093 --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/AndroidPointerCapture.java @@ -0,0 +1,102 @@ +package net.kdt.pojavlaunch.customcontrols.mouse; + +import android.os.Build; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewTreeObserver; + +import androidx.annotation.RequiresApi; + +import net.kdt.pojavlaunch.MainActivity; +import net.kdt.pojavlaunch.MinecraftGLSurface; +import net.kdt.pojavlaunch.Tools; + +import org.lwjgl.glfw.CallbackBridge; + +@RequiresApi(api = Build.VERSION_CODES.O) +public class AndroidPointerCapture implements ViewTreeObserver.OnWindowFocusChangeListener, View.OnCapturedPointerListener { + private static final float TOUCHPAD_SCROLL_THRESHOLD = 1; + private final AbstractTouchpad mTouchpad; + private final View mHostView; + private final float mScaleFactor; + private final float mMousePrescale = Tools.dpToPx(1); + private final Scroller mScroller = new Scroller(TOUCHPAD_SCROLL_THRESHOLD); + + public AndroidPointerCapture(AbstractTouchpad touchpad, View hostView, float scaleFactor) { + this.mScaleFactor = scaleFactor; + this.mTouchpad = touchpad; + this.mHostView = hostView; + hostView.setOnCapturedPointerListener(this); + hostView.getViewTreeObserver().addOnWindowFocusChangeListener(this); + } + + private void enableTouchpadIfNecessary() { + if(!mTouchpad.getDisplayState()) mTouchpad.enable(true); + } + + public void handleAutomaticCapture() { + if(!CallbackBridge.isGrabbing()) return; + if(mHostView.hasPointerCapture()) { + enableTouchpadIfNecessary(); + return; + } + if(!mHostView.hasWindowFocus()) { + mHostView.requestFocus(); + } else { + mHostView.requestPointerCapture(); + } + } + + @Override + public boolean onCapturedPointer(View view, MotionEvent event) { + // Yes, we actually not only receive relative mouse events here, but also absolute touchpad ones! + // Read from relative axis directly to work around. + float relX = event.getAxisValue(MotionEvent.AXIS_RELATIVE_X); + float relY = event.getAxisValue(MotionEvent.AXIS_RELATIVE_Y); + if(!CallbackBridge.isGrabbing()) { + enableTouchpadIfNecessary(); + // Yes, if the user's touchpad is multi-touch we will also receive events for that. + // So, handle the scrolling gesture ourselves. + relX *= mMousePrescale; + relY *= mMousePrescale; + if(event.getPointerCount() < 2) { + mTouchpad.applyMotionVector(relX, relY); + mScroller.resetScrollOvershoot(); + } else { + mScroller.performScroll(relX, relY); + } + } else { + // Position is updated by many events, hence it is send regardless of the event value + CallbackBridge.mouseX += (relX * mScaleFactor); + CallbackBridge.mouseY += (relY * mScaleFactor); + CallbackBridge.sendCursorPos(CallbackBridge.mouseX, CallbackBridge.mouseY); + } + + switch (event.getActionMasked()) { + case MotionEvent.ACTION_MOVE: + return true; + case MotionEvent.ACTION_BUTTON_PRESS: + return MinecraftGLSurface.sendMouseButtonUnconverted(event.getActionButton(), true); + case MotionEvent.ACTION_BUTTON_RELEASE: + return MinecraftGLSurface.sendMouseButtonUnconverted(event.getActionButton(), false); + case MotionEvent.ACTION_SCROLL: + CallbackBridge.sendScroll( + event.getAxisValue(MotionEvent.AXIS_HSCROLL), + event.getAxisValue(MotionEvent.AXIS_VSCROLL) + ); + return true; + default: + return false; + } + } + + @Override + public void onWindowFocusChanged(boolean hasFocus) { + if(hasFocus && MainActivity.isAndroid8OrHigher()) mHostView.requestPointerCapture(); + } + + public void detach() { + mHostView.setOnCapturedPointerListener(null); + mHostView.getViewTreeObserver().removeOnWindowFocusChangeListener(this); + } +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/InGUIEventProcessor.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/InGUIEventProcessor.java index 0bf7469157..e9ef9a41ea 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/InGUIEventProcessor.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/InGUIEventProcessor.java @@ -11,13 +11,13 @@ import org.lwjgl.glfw.CallbackBridge; public class InGUIEventProcessor implements TouchEventProcessor { + public static final float FINGER_SCROLL_THRESHOLD = Tools.dpToPx(6); private final PointerTracker mTracker = new PointerTracker(); private final GestureDetector mSingleTapDetector; private AbstractTouchpad mTouchpad; private boolean mIsMouseDown = false; private final float mScaleFactor; - private float mScrollOvershootH, mScrollOvershootV; - public static final float FINGER_SCROLL_THRESHOLD = Tools.dpToPx(6); + private final Scroller mScroller = new Scroller(FINGER_SCROLL_THRESHOLD); public InGUIEventProcessor(float scaleFactor) { mSingleTapDetector = new GestureDetector(null, new SingleTapConfirm()); mScaleFactor = scaleFactor; @@ -44,11 +44,11 @@ public boolean processTouchEvent(MotionEvent motionEvent) { sendTouchCoordinates(mainPointerX, mainPointerY); if(!mIsMouseDown) enableMouse(); } - } else performScroll(); + } else mScroller.performScroll(mTracker.getMotionVector()); break; case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: - resetScrollOvershoot(); + mScroller.resetScrollOvershoot(); mTracker.cancelTracking(); if(mIsMouseDown) disableMouse(); } @@ -64,20 +64,6 @@ public void setAbstractTouchpad(AbstractTouchpad touchpad) { mTouchpad = touchpad; } - private void performScroll() { - float[] motionVector = mTracker.getMotionVector(); - float hScroll = (motionVector[0] / FINGER_SCROLL_THRESHOLD) + mScrollOvershootH; - float vScroll = (motionVector[1] / FINGER_SCROLL_THRESHOLD) + mScrollOvershootV; - int hScrollRound = (int) hScroll, vScrollRound = (int) vScroll; - if(hScrollRound != 0 || vScrollRound != 0) CallbackBridge.sendScroll(hScroll, vScroll); - mScrollOvershootH = hScroll - hScrollRound; - mScrollOvershootV = vScroll - vScrollRound; - } - - private void resetScrollOvershoot() { - mScrollOvershootH = mScrollOvershootV = 0f; - } - private void sendTouchCoordinates(float x, float y) { CallbackBridge.sendCursorPos( x * mScaleFactor, y * mScaleFactor); } @@ -99,7 +85,7 @@ private void clickMouse() { @Override public void cancelPendingActions() { - resetScrollOvershoot(); + mScroller.resetScrollOvershoot(); disableMouse(); } } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/Scroller.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/Scroller.java new file mode 100644 index 0000000000..8792fcc784 --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/Scroller.java @@ -0,0 +1,43 @@ +package net.kdt.pojavlaunch.customcontrols.mouse; + +import org.lwjgl.glfw.CallbackBridge; + +public class Scroller { + + private float mScrollOvershootH, mScrollOvershootV; + private final float mScrollThreshold; + + public Scroller(float mScrollThreshold) { + this.mScrollThreshold = mScrollThreshold; + } + + /** + * Perform a scrolling gesture. + * @param dx the X coordinate of the primary pointer's vector + * @param dy the Y coordinate of the primary pointer's vector + */ + public void performScroll(float dx, float dy) { + float hScroll = (dx / mScrollThreshold) + mScrollOvershootH; + float vScroll = (dy / mScrollThreshold) + mScrollOvershootV; + int hScrollRound = (int) hScroll, vScrollRound = (int) vScroll; + if(hScrollRound != 0 || vScrollRound != 0) CallbackBridge.sendScroll(hScroll, vScroll); + mScrollOvershootH = hScroll - hScrollRound; + mScrollOvershootV = vScroll - vScrollRound; + } + + /** + * Perform a scrolling gesture. + * @param vector a 2-component vector that stores the relative position of the primary pointer. + */ + public void performScroll(float[] vector) { + performScroll(vector[0], vector[1]); + } + + /** + * Reset scroll overshoot values. Scroll overshoot makes the scrolling feel less + * choppy, but will cause anomailes if not reset on the end of a scrolling gesture. + */ + public void resetScrollOvershoot() { + mScrollOvershootH = mScrollOvershootV = 0f; + } +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/Touchpad.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/Touchpad.java index 897cfb6f94..1feee772be 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/Touchpad.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/Touchpad.java @@ -41,13 +41,13 @@ public Touchpad(@NonNull Context context, @Nullable AttributeSet attrs) { } /** Enable the touchpad */ - public void enable(){ + private void _enable(){ setVisibility(VISIBLE); placeMouseAt(currentDisplayMetrics.widthPixels / 2f, currentDisplayMetrics.heightPixels / 2f); } /** Disable the touchpad and hides the mouse */ - public void disable(){ + private void _disable(){ setVisibility(GONE); } @@ -55,8 +55,8 @@ public void disable(){ public boolean switchState(){ mDisplayState = !mDisplayState; if(!CallbackBridge.isGrabbing()) { - if(mDisplayState) enable(); - else disable(); + if(mDisplayState) _enable(); + else _disable(); } return mDisplayState; } @@ -112,10 +112,10 @@ public void onGrabState(boolean isGrabbing) { } private void updateGrabState(boolean isGrabbing) { if(!isGrabbing) { - if(mDisplayState && getVisibility() != VISIBLE) enable(); - if(!mDisplayState && getVisibility() == VISIBLE) disable(); + if(mDisplayState && getVisibility() != VISIBLE) _enable(); + if(!mDisplayState && getVisibility() == VISIBLE) _disable(); }else{ - if(getVisibility() != View.GONE) disable(); + if(getVisibility() != View.GONE) _disable(); } } @@ -125,9 +125,24 @@ public boolean getDisplayState() { } @Override - public void applyMotionVector(float[] vector) { - mMouseX = Math.max(0, Math.min(currentDisplayMetrics.widthPixels, mMouseX + vector[0] * LauncherPreferences.PREF_MOUSESPEED)); - mMouseY = Math.max(0, Math.min(currentDisplayMetrics.heightPixels, mMouseY + vector[1] * LauncherPreferences.PREF_MOUSESPEED)); + public void applyMotionVector(float x, float y) { + mMouseX = Math.max(0, Math.min(currentDisplayMetrics.widthPixels, mMouseX + x * LauncherPreferences.PREF_MOUSESPEED)); + mMouseY = Math.max(0, Math.min(currentDisplayMetrics.heightPixels, mMouseY + y * LauncherPreferences.PREF_MOUSESPEED)); updateMousePosition(); } + + @Override + public void enable(boolean supposed) { + if(mDisplayState) return; + mDisplayState = true; + if(supposed && CallbackBridge.isGrabbing()) return; + _enable(); + } + + @Override + public void disable() { + if(!mDisplayState) return; + mDisplayState = false; + _disable(); + } } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/FileSelectorFragment.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/FileSelectorFragment.java index 7cb262746d..8492acf1ef 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/FileSelectorFragment.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/FileSelectorFragment.java @@ -25,6 +25,7 @@ public class FileSelectorFragment extends Fragment { public static final String TAG = "FileSelectorFragment"; public static final String BUNDLE_SELECT_FOLDER = "select_folder"; + public static final String BUNDLE_SELECT_FILE = "select_file"; public static final String BUNDLE_SHOW_FILE = "show_file"; public static final String BUNDLE_SHOW_FOLDER = "show_folder"; public static final String BUNDLE_ROOT_PATH = "root_path"; diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/MicrosoftLoginFragment.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/MicrosoftLoginFragment.java index 51bc251a08..b95d07a77c 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/MicrosoftLoginFragment.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/MicrosoftLoginFragment.java @@ -25,6 +25,8 @@ public class MicrosoftLoginFragment extends Fragment { public static final String TAG = "MICROSOFT_LOGIN_FRAGMENT"; private WebView mWebview; + // Technically the client is blank (or there is none) when the fragment is initialized + private boolean mBlankClient = true; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -43,6 +45,7 @@ private void setWebViewSettings() { WebSettings settings = mWebview.getSettings(); settings.setJavaScriptEnabled(true); mWebview.setWebViewClient(new WebViewTrackClient()); + mBlankClient = false; } private void startNewSession() { @@ -69,14 +72,25 @@ private void restoreWebViewState(Bundle savedInstanceState) { } } + @Override + public void onStart() { + super.onStart(); + // If we have switched to a blank client and haven't fully gone though the lifecycle callbacks to restore it, + // restore it here. + if(mBlankClient) mWebview.setWebViewClient(new WebViewTrackClient()); + } + @Override public void onSaveInstanceState(@NonNull Bundle outState) { - // Since the value cannot be null, just creaqte a "blank" client. This is done to not let Android - // kill us if something happens after onSaveInstanceState + // Since the value cannot be null, just create a "blank" client. This is done to not let Android + // kill us if something happens after the state gets saved, when we can't do fragment transitions mWebview.setWebViewClient(new WebViewClient()); - mWebview.saveState(outState); + // For some dumb reason state is saved even when Android won't actually destroy the activity. + // Let the fragment know that the client is blank so that we can restore it in onStart() + // (it was the earliest lifecycle call actually invoked in this case) + mBlankClient = true; super.onSaveInstanceState(outState); - // if something happens after this, well, too bad + mWebview.saveState(outState); } /* Expose webview actions to others */ diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/ProfileEditorFragment.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/ProfileEditorFragment.java index 831e8b780f..d0e5709626 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/ProfileEditorFragment.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/ProfileEditorFragment.java @@ -122,6 +122,8 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat Bundle bundle = new Bundle(3); bundle.putBoolean(FileSelectorFragment.BUNDLE_SELECT_FOLDER, false); bundle.putString(FileSelectorFragment.BUNDLE_ROOT_PATH, Tools.CTRLMAP_PATH); + mValueToConsume = FileSelectorFragment.BUNDLE_SELECT_FILE; + Tools.swapFragment(requireActivity(), FileSelectorFragment.class, FileSelectorFragment.TAG, bundle); }); diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/SearchModFragment.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/SearchModFragment.java index ace5d14afc..a4e2293178 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/SearchModFragment.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/SearchModFragment.java @@ -89,7 +89,8 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat mSearchProgressBar.setVisibility(View.VISIBLE); mSearchFilters.name = mSearchEditText.getText().toString(); mModItemAdapter.performSearchQuery(mSearchFilters); - return true; + mSearchEditText.clearFocus(); + return false; }); mOverlay.post(()->{ diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/modpacks/api/ModrinthApi.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/modpacks/api/ModrinthApi.java index be345d28f1..0e76723bc0 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/modpacks/api/ModrinthApi.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/modpacks/api/ModrinthApi.java @@ -30,9 +30,19 @@ public ModrinthApi(){ @Override public SearchResult searchMod(SearchFilters searchFilters, SearchResult previousPageResult) { ModrinthSearchResult modrinthSearchResult = (ModrinthSearchResult) previousPageResult; - HashMap params = new HashMap<>(); + + // Fixes an issue where the offset being equal or greater than total_hits is ignored + if (modrinthSearchResult != null && modrinthSearchResult.previousOffset >= modrinthSearchResult.totalResultCount) { + ModrinthSearchResult emptyResult = new ModrinthSearchResult(); + emptyResult.results = new ModItem[0]; + emptyResult.totalResultCount = modrinthSearchResult.totalResultCount; + emptyResult.previousOffset = modrinthSearchResult.previousOffset; + return emptyResult; + } + // Build the facets filters + HashMap params = new HashMap<>(); StringBuilder facetString = new StringBuilder(); facetString.append("["); facetString.append(String.format("[\"project_type:%s\"]", searchFilters.isModpack ? "modpack" : "mod")); diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/screens/LauncherPreferenceFragment.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/screens/LauncherPreferenceFragment.java index b57eacd589..7275f86912 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/screens/LauncherPreferenceFragment.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/screens/LauncherPreferenceFragment.java @@ -3,7 +3,6 @@ import android.app.Activity; import android.content.SharedPreferences; -import android.graphics.Color; import android.os.Bundle; import android.view.View; @@ -24,7 +23,7 @@ public class LauncherPreferenceFragment extends PreferenceFragmentCompat impleme @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - view.setBackgroundColor(Color.parseColor("#232323")); + view.setBackgroundColor(getResources().getColor(R.color.background_app)); super.onViewCreated(view, savedInstanceState); } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/screens/LauncherPreferenceJavaFragment.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/screens/LauncherPreferenceJavaFragment.java index dbcf3666d2..13c5b26a92 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/screens/LauncherPreferenceJavaFragment.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/screens/LauncherPreferenceJavaFragment.java @@ -35,7 +35,7 @@ public void onCreatePreferences(Bundle b, String str) { int maxRAM; int deviceRam = getTotalDeviceMemory(seek7.getContext()); - if(is32BitsDevice() || deviceRam < 2048) maxRAM = Math.min(1000, deviceRam); + if(is32BitsDevice() || deviceRam < 2048) maxRAM = Math.min(1024, deviceRam); else maxRAM = deviceRam - (deviceRam < 3064 ? 800 : 1024); //To have a minimum for the device to breathe seek7.setMin(256); diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/DownloadUtils.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/DownloadUtils.java index 367b415d1e..b4df71409a 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/DownloadUtils.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/DownloadUtils.java @@ -137,7 +137,11 @@ private static boolean verifyFile(File file, String sha1) { public static T ensureSha1(File outputFile, @Nullable String sha1, Callable downloadFunction) throws IOException { // Skip if needed - if(sha1 == null) return downloadFile(downloadFunction); + if(sha1 == null) { + // If the file exists and we don't know it's SHA1, don't try to redownload it. + if(outputFile.exists()) return null; + else return downloadFile(downloadFunction); + } int attempts = 0; boolean fileOkay = verifyFile(outputFile, sha1); diff --git a/app_pojavlauncher/src/main/res/drawable/background_line_selected.xml b/app_pojavlauncher/src/main/res/drawable/background_line_selected.xml index f962a81899..dc9926671e 100644 --- a/app_pojavlauncher/src/main/res/drawable/background_line_selected.xml +++ b/app_pojavlauncher/src/main/res/drawable/background_line_selected.xml @@ -12,5 +12,4 @@ android:color="@color/minebutton_color"/> - diff --git a/app_pojavlauncher/src/main/res/drawable/background_line_unselected.xml b/app_pojavlauncher/src/main/res/drawable/background_line_unselected.xml index 1c95d5431e..223fad4a17 100644 --- a/app_pojavlauncher/src/main/res/drawable/background_line_unselected.xml +++ b/app_pojavlauncher/src/main/res/drawable/background_line_unselected.xml @@ -7,9 +7,6 @@ - + + + + + + diff --git a/app_pojavlauncher/src/main/res/layout/fragment_launcher.xml b/app_pojavlauncher/src/main/res/layout/fragment_launcher.xml index 6cb31bd994..c68eecd712 100644 --- a/app_pojavlauncher/src/main/res/layout/fragment_launcher.xml +++ b/app_pojavlauncher/src/main/res/layout/fragment_launcher.xml @@ -10,51 +10,60 @@ android:orientation="vertical" android:background="@color/background_app" > - - - - - - - - - + app:layout_constraintBottom_toTopOf="@id/mc_version_spinner"> + + + + + + + + + + false @null @color/background_status_bar - @android:color/transparent + @color/background_app @style/PreferenceThemeOverlay.v14.Material @dimen/_14ssp diff --git a/app_pojavlauncher/src/main/res/values/styles.xml b/app_pojavlauncher/src/main/res/values/styles.xml index 58aaeb681a..8171c88146 100644 --- a/app_pojavlauncher/src/main/res/values/styles.xml +++ b/app_pojavlauncher/src/main/res/values/styles.xml @@ -6,7 +6,7 @@ false @null @color/background_status_bar - @android:color/transparent + @color/background_app @style/PreferenceThemeOverlay.v14.Material @dimen/_12ssp diff --git a/app_pojavlauncher/src/main/res/xml/pref_java.xml b/app_pojavlauncher/src/main/res/xml/pref_java.xml index 198c63f5f5..46616ae4d8 100644 --- a/app_pojavlauncher/src/main/res/xml/pref_java.xml +++ b/app_pojavlauncher/src/main/res/xml/pref_java.xml @@ -25,7 +25,7 @@ android:summary="@string/mcl_memory_allocation_subtitle" android:title="@string/mcl_memory_allocation" app2:showSeekBarValue="true" - app2:seekBarIncrement="10" + app2:seekBarIncrement="8" app2:selectable="false"/>