diff --git a/app/src/main/java/ru/ytkab0bp/slicebeam/fragment/BedFragment.java b/app/src/main/java/ru/ytkab0bp/slicebeam/fragment/BedFragment.java index 55765c8..fae032a 100644 --- a/app/src/main/java/ru/ytkab0bp/slicebeam/fragment/BedFragment.java +++ b/app/src/main/java/ru/ytkab0bp/slicebeam/fragment/BedFragment.java @@ -1,13 +1,16 @@ package ru.ytkab0bp.slicebeam.fragment; +import android.annotation.SuppressLint; import android.app.Activity; import android.content.Context; import android.os.Process; +import android.text.TextUtils; import android.util.DisplayMetrics; import android.util.Log; import android.util.SparseArray; import android.view.View; import android.view.ViewGroup; +import android.webkit.WebView; import android.widget.FrameLayout; import android.widget.LinearLayout; @@ -33,6 +36,7 @@ import ru.ytkab0bp.slicebeam.components.bed_menu.FileMenu; import ru.ytkab0bp.slicebeam.components.bed_menu.OrientationMenu; import ru.ytkab0bp.slicebeam.components.bed_menu.SliceMenu; import ru.ytkab0bp.slicebeam.components.bed_menu.TransformMenu; +import ru.ytkab0bp.slicebeam.config.ConfigObject; import ru.ytkab0bp.slicebeam.events.NeedSnackbarEvent; import ru.ytkab0bp.slicebeam.events.SlicingProgressEvent; import ru.ytkab0bp.slicebeam.navigation.Fragment; @@ -43,6 +47,7 @@ import ru.ytkab0bp.slicebeam.slic3r.Slic3rRuntimeError; import ru.ytkab0bp.slicebeam.theme.ThemesRepo; import ru.ytkab0bp.slicebeam.utils.Vec3d; import ru.ytkab0bp.slicebeam.utils.ViewUtils; +import ru.ytkab0bp.slicebeam.view.BedSwipeDownLayout; import ru.ytkab0bp.slicebeam.view.DividerView; import ru.ytkab0bp.slicebeam.view.GLView; import ru.ytkab0bp.slicebeam.view.ThemeBottomNavigationView; @@ -97,6 +102,9 @@ public class BedFragment extends Fragment { private GCodeProcessorResult gCodeResult; private UnfoldMenu currentUnfoldMenu; + private BedSwipeDownLayout swipeDownLayout; + private WebView panelWebView; + private static String tempFileName; private static File tempExportingFile; @@ -158,6 +166,9 @@ public class BedFragment extends Fragment { currentUnfoldMenu.dismiss(); return true; } + if (swipeDownLayout.onBackPressed()) { + return true; + } if (currentMenuSlot != 0) { navigationView.setSelectedItemId(0); return true; @@ -191,6 +202,24 @@ public class BedFragment extends Fragment { public void onResume() { super.onResume(); glView.onResume(); + ConfigObject cfg = SliceBeam.CONFIG.findPrinter(SliceBeam.CONFIG.presets.get("printer")); + boolean enable = cfg != null && cfg.get("host_type") != null && !TextUtils.isEmpty(cfg.get("print_host")); + swipeDownLayout.setEnableTop(enable); + if (enable) { + String host = cfg.get("print_host"); + if (host.contains(":")) { + try { + int port = Integer.parseInt(host.split(":")[1]); + if (port >= 7125 && port <= 7200) { + host = host.split(":")[0]; + } + } catch (Exception ignored) {} + } + if (!host.startsWith("http://")) { + host = "http://" + host; + } + panelWebView.loadUrl(host); + } } @Override @@ -199,6 +228,7 @@ public class BedFragment extends Fragment { glView.onPause(); } + @SuppressLint("SetJavaScriptEnabled") @Override public View onCreateView(Context ctx) { glView = new GLView(ctx); @@ -229,7 +259,23 @@ public class BedFragment extends Fragment { ll.addView(menuView, new LinearLayout.LayoutParams(ViewUtils.dp(MENU_SIZE_DP), ViewGroup.LayoutParams.MATCH_PARENT)); } - ll.addView(glView, new LinearLayout.LayoutParams(portrait ? ViewGroup.LayoutParams.MATCH_PARENT : 0, portrait ? 0 : ViewGroup.LayoutParams.MATCH_PARENT, 1f)); + swipeDownLayout = new BedSwipeDownLayout(ctx); + panelWebView = new WebView(ctx); + panelWebView.getSettings().setJavaScriptEnabled(true); + + if (portrait) { + LinearLayout inner = new LinearLayout(ctx); + inner.setOrientation(LinearLayout.VERTICAL); + ll = inner; + + inner.addView(glView, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0, 1f)); + swipeDownLayout.addView(inner); + swipeDownLayout.addView(panelWebView); + } else { + swipeDownLayout.addView(glView); + swipeDownLayout.addView(panelWebView); + ll.addView(swipeDownLayout, new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT, 1f)); + } if (portrait) { ll.addView(menuView, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(MENU_SIZE_DP))); @@ -328,7 +374,11 @@ public class BedFragment extends Fragment { return true; }); - overlayLayout.addView(contentView = ll); + if (portrait) { + overlayLayout.addView(contentView = swipeDownLayout); + } else { + overlayLayout.addView(contentView = ll); + } overlayLayout.addView(snackbarsLayout = new CoordinatorLayout(ctx), new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT) {{ if (portrait) { bottomMargin = ViewUtils.dp(80 * 2); diff --git a/app/src/main/java/ru/ytkab0bp/slicebeam/view/BedSwipeDownLayout.java b/app/src/main/java/ru/ytkab0bp/slicebeam/view/BedSwipeDownLayout.java new file mode 100644 index 0000000..53fd3d5 --- /dev/null +++ b/app/src/main/java/ru/ytkab0bp/slicebeam/view/BedSwipeDownLayout.java @@ -0,0 +1,241 @@ +package ru.ytkab0bp.slicebeam.view; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.CornerPathEffect; +import android.graphics.Paint; +import android.graphics.Path; +import android.view.GestureDetector; +import android.view.MotionEvent; +import android.view.View; +import android.widget.FrameLayout; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.graphics.ColorUtils; +import androidx.core.math.MathUtils; +import androidx.dynamicanimation.animation.FloatValueHolder; +import androidx.dynamicanimation.animation.SpringAnimation; +import androidx.dynamicanimation.animation.SpringForce; + +import ru.ytkab0bp.slicebeam.theme.IThemeView; +import ru.ytkab0bp.slicebeam.theme.ThemesRepo; +import ru.ytkab0bp.slicebeam.utils.ViewUtils; + +public class BedSwipeDownLayout extends FrameLayout implements IThemeView { + private final static int TOP_MARGIN_DP = 28; + private final static int ARROW_LENGTH_DP = 11; + + private Path path = new Path(); + private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); + private Paint dimmPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + + private float progress = 0; + private boolean openEnabled = true; + + private boolean processingSwipe; + private SpringAnimation springAnimation; + private float startProgress; + private GestureDetector gestureDetector; + + public BedSwipeDownLayout(@NonNull Context context) { + super(context); + + paint.setStyle(Paint.Style.STROKE); + paint.setStrokeWidth(ViewUtils.dp(3)); + paint.setStrokeCap(Paint.Cap.ROUND); + paint.setPathEffect(new CornerPathEffect(ViewUtils.dp(6))); + + gestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() { + @Override + public boolean onDown(@NonNull MotionEvent e) { + if (springAnimation != null) return false; + + processingSwipe = true; + startProgress = progress; + return true; + } + + @Override + public boolean onSingleTapUp(@NonNull MotionEvent e) { + if (springAnimation != null) return false; + + animateTo(1f); + return true; + } + + @Override + public boolean onScroll(@Nullable MotionEvent e1, @NonNull MotionEvent e2, float distanceX, float distanceY) { + if (springAnimation != null) return false; + + if (processingSwipe) { + progress = MathUtils.clamp(startProgress + (e2.getY() - e1.getY()) / getHeight(), 0, 1); + invalidateTranslation(); + return true; + } + return false; + } + + @Override + public boolean onFling(@Nullable MotionEvent e1, @NonNull MotionEvent e2, float velocityX, float velocityY) { + if (springAnimation != null) return false; + + if (velocityY >= 1500) { + animateTo(1f); + return true; + } + return false; + } + }); + + setWillNotDraw(false); + onApplyTheme(); + } + + public boolean onBackPressed() { + if (springAnimation != null) { + return true; + } + if (progress == 1f) { + animateTo(0f); + return true; + } + return false; + } + + @Override + public boolean dispatchTouchEvent(MotionEvent ev) { + if (openEnabled) { + if (processingSwipe) { + boolean d = gestureDetector.onTouchEvent(ev); + if (ev.getActionMasked() == MotionEvent.ACTION_UP || ev.getActionMasked() == MotionEvent.ACTION_CANCEL) { + processingSwipe = false; + + if (springAnimation == null && progress != 0 && progress != 1) { + animateTo(progress > 0.5f ? 1f : 0f); + } + } + return d; + } + + if (progress != 1f && springAnimation == null) { + if (ev.getY() < ViewUtils.dp(TOP_MARGIN_DP) + getHeight() * progress) { + return gestureDetector.onTouchEvent(ev); + } + } + } + return super.dispatchTouchEvent(ev); + } + + @Override + protected boolean drawChild(@NonNull Canvas canvas, View child, long drawingTime) { + int i = indexOfChild(child); + canvas.save(); + float tY = -getHeight() * (1f - progress); + if (openEnabled && i != 0) { + int clr = ThemesRepo.getColor(android.R.attr.windowBackground); + dimmPaint.setColor(clr); + canvas.drawRect(0, child.getTop() + tY, child.getWidth(), child.getTop() + tY + child.getHeight(), dimmPaint); + canvas.clipRect(0, child.getTop() + tY, child.getWidth(), child.getTop() + tY + child.getHeight()); + } + + boolean ch = super.drawChild(canvas, child, drawingTime); + if (openEnabled) { + if (i == 0) { + dimmPaint.setColor(Color.argb((int) (0x66 * progress), 0, 0, 0)); + canvas.drawPaint(dimmPaint); + } + } + canvas.restore(); + return ch; + } + + @Override + public void draw(@NonNull Canvas canvas) { + super.draw(canvas); + + if (openEnabled) { + path.rewind(); + float cx = getWidth() * 0.5f, cy = ViewUtils.dp(TOP_MARGIN_DP) * 0.5f + getHeight() * progress; + float angle = (float) Math.toRadians(25) * (1f - Math.min(progress, 0.5f) / 0.5f); + int len = ViewUtils.dp(ARROW_LENGTH_DP); + path.moveTo(cx, cy); + path.lineTo((float) (cx - Math.cos(angle) * len), (float) (cy - Math.sin(angle) * len)); + path.moveTo(cx, cy); + path.lineTo((float) (cx + Math.cos(angle) * len), (float) (cy - Math.sin(angle) * len)); + canvas.drawPath(path, paint); + } + } + + private void animateTo(float to) { + springAnimation = new SpringAnimation(new FloatValueHolder(progress)) + .setMinimumVisibleChange(1 / 500f) + .setSpring(new SpringForce(to) + .setStiffness(600f) + .setDampingRatio(1f)) + .addUpdateListener((animation, value, velocity) -> { + progress = value; + invalidateTranslation(); + }) + .addEndListener((animation, canceled, value, velocity) -> { + springAnimation = null; + processingSwipe = false; + }); + springAnimation.start(); + } + + private void invalidateTranslation() { + if (getChildCount() > 0) { + getChildAt(0).setTranslationY(getHeight() * progress * 0.25f); + for (int i = 1; i < getChildCount(); i++) { + View ch = getChildAt(i); + ch.setTranslationY(-getHeight() * (1f - progress) * 0.75f); + ch.setAlpha(progress); + } + } + invalidate(); + } + + public void setEnableTop(boolean enable) { + if (enable == openEnabled) { + return; + } + openEnabled = enable; + requestLayout(); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int width = MeasureSpec.getSize(widthMeasureSpec), height = MeasureSpec.getSize(heightMeasureSpec); + setMeasuredDimension(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); + + if (getChildCount() > 0) { + getChildAt(0).measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height - ViewUtils.dp(TOP_MARGIN_DP) * (openEnabled ? 1 : 0), MeasureSpec.EXACTLY)); + for (int i = 1; i < getChildCount(); i++) { + getChildAt(i).measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); + } + } + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + + if (openEnabled && getChildCount() > 0) { + View first = getChildAt(0); + first.layout(0, ViewUtils.dp(TOP_MARGIN_DP), first.getMeasuredWidth(), ViewUtils.dp(TOP_MARGIN_DP) + first.getMeasuredHeight()); + + for (int i = 1; i < getChildCount(); i++) { + View ch = getChildAt(i); + ch.layout(0, 0, ch.getMeasuredWidth(), ch.getMeasuredHeight()); + } + } + invalidateTranslation(); + } + + @Override + public void onApplyTheme() { + paint.setColor(ColorUtils.setAlphaComponent(ThemesRepo.getColor(android.R.attr.colorControlHighlight), 0x44)); + } +}