diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml index eaa059df..c4a8f7c8 100644 --- a/.idea/copyright/profiles_settings.xml +++ b/.idea/copyright/profiles_settings.xml @@ -1,7 +1,7 @@ - + \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index d96312dc..d5e1e304 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,10 +6,11 @@ - - + + + diff --git a/app/src/main/java/xyz/quaver/pupil/adapters/GalleryBlockAdapter.kt b/app/src/main/java/xyz/quaver/pupil/adapters/GalleryBlockAdapter.kt index efcfbb11..a36053fe 100644 --- a/app/src/main/java/xyz/quaver/pupil/adapters/GalleryBlockAdapter.kt +++ b/app/src/main/java/xyz/quaver/pupil/adapters/GalleryBlockAdapter.kt @@ -54,12 +54,6 @@ import java.io.File class GalleryBlockAdapter(private val galleries: List) : RecyclerSwipeAdapter(), SwipeAdapterInterface { - enum class ViewType { - NEXT, - GALLERY, - PREV - } - var updateAll = true var thin: Boolean = Preferences["thin"] @@ -282,51 +276,20 @@ class GalleryBlockAdapter(private val galleries: List) : RecyclerSwipeAdapt } } } - class NextViewHolder(view: LinearLayout) : RecyclerView.ViewHolder(view) - class PrevViewHolder(view: LinearLayout) : RecyclerView.ViewHolder(view) - - class ViewHolderFactory { - companion object { - fun getLayoutID(type: Int): Int { - return when(ViewType.values()[type]) { - ViewType.NEXT -> R.layout.item_next - ViewType.PREV -> R.layout.item_prev - ViewType.GALLERY -> R.layout.item_galleryblock - } - } - } - } val onChipClickedHandler = ArrayList<((Tag) -> Unit)>() var onDownloadClickedHandler: ((Int) -> Unit)? = null var onDeleteClickedHandler: ((Int) -> Unit)? = null - var showNext = false - var showPrev = false - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { - - fun getViewHolder(type: Int, view: View): RecyclerView.ViewHolder { - return when(ViewType.values()[type]) { - ViewType.NEXT -> NextViewHolder(view as LinearLayout) - ViewType.PREV -> PrevViewHolder(view as LinearLayout) - ViewType.GALLERY -> GalleryViewHolder(view as ProgressCard) - } - } - - return getViewHolder( - viewType, - LayoutInflater.from(parent.context).inflate( - ViewHolderFactory.getLayoutID(viewType), - parent, - false - ) + return GalleryViewHolder( + LayoutInflater.from(parent.context).inflate(R.layout.item_galleryblock, parent, false) ) } override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { if (holder is GalleryViewHolder) { - val galleryID = galleries[position-(if (showPrev) 1 else 0)] + val galleryID = galleries[position] holder.bind(galleryID) @@ -360,18 +323,7 @@ class GalleryBlockAdapter(private val galleries: List) : RecyclerSwipeAdapt } } - override fun getItemCount() = - galleries.size + - (if (showNext) 1 else 0) + - (if (showPrev) 1 else 0) - - override fun getItemViewType(position: Int): Int { - return when { - showPrev && position == 0 -> ViewType.PREV - showNext && position == galleries.size+(if (showPrev) 1 else 0) -> ViewType.NEXT - else -> ViewType.GALLERY - }.ordinal - } + override fun getItemCount() = galleries.size override fun getSwipeLayoutResourceId(position: Int) = R.id.swipe_layout } \ No newline at end of file diff --git a/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt b/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt index a0d1b088..c37bec02 100644 --- a/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt +++ b/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt @@ -26,18 +26,16 @@ import android.text.InputType import android.text.util.Linkify import android.view.KeyEvent import android.view.MenuItem -import android.view.MotionEvent import android.view.View +import android.view.animation.DecelerateInterpolator import android.widget.EditText -import android.widget.ImageView -import android.widget.LinearLayout import android.widget.TextView import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatDelegate import androidx.cardview.widget.CardView import androidx.core.view.GravityCompat +import androidx.core.view.ViewCompat import androidx.recyclerview.widget.RecyclerView -import com.google.android.material.appbar.AppBarLayout import com.google.android.material.navigation.NavigationView import com.google.android.material.snackbar.Snackbar import com.google.firebase.crashlytics.FirebaseCrashlytics @@ -57,6 +55,7 @@ import xyz.quaver.pupil.services.DownloadService import xyz.quaver.pupil.types.* import xyz.quaver.pupil.ui.dialog.DownloadLocationDialogFragment import xyz.quaver.pupil.ui.dialog.GalleryDialog +import xyz.quaver.pupil.ui.view.MainView import xyz.quaver.pupil.ui.view.ProgressCard import xyz.quaver.pupil.util.ItemClickSupport import xyz.quaver.pupil.util.Preferences @@ -65,10 +64,7 @@ import xyz.quaver.pupil.util.downloader.Cache import xyz.quaver.pupil.util.downloader.DownloadManager import xyz.quaver.pupil.util.restore import java.util.regex.Pattern -import kotlin.math.abs -import kotlin.math.ceil -import kotlin.math.min -import kotlin.math.roundToInt +import kotlin.math.* class MainActivity : BaseActivity(), @@ -192,22 +188,22 @@ class MainActivity : } private fun initView() { - var prevP1 = 0 - main_appbar_layout.addOnOffsetChangedListener( - AppBarLayout.OnOffsetChangedListener { _, p1 -> - main_searchview.translationY = p1.toFloat() - main_recyclerview.scrollBy(0, prevP1 - p1) + main_recyclerview.addOnScrollListener(object: RecyclerView.OnScrollListener() { + override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { + // -height of the search view < translationY < 0 + main_searchview.translationY = + min( + max( + main_searchview.translationY - dy, + -main_searchview.findViewById(R.id.search_query_section).height.toFloat() + ), 0F) - with(main_fab) { - if (prevP1 > p1) - hideMenuButton(true) - else if (prevP1 < p1) - showMenuButton(true) - } - - prevP1 = p1 + if (dy > 0) + main_fab.hideMenuButton(true) + else if (dy < 0) + main_fab.showMenuButton(true) } - ) + }) Linkify.addLinks(main_noresult, Pattern.compile(getString(R.string.https_text)), null, null, { _, _ -> getString(R.string.https) }) @@ -312,6 +308,44 @@ class MainActivity : } } + with(main_view) { + setOnPageTurnListener(object: MainView.OnPageTurnListener { + override fun onPrev(page: Int) { + currentPage-- + + // disable pageturn until the contents are loaded + setCurrentPage(1, false) + + ViewCompat.animate(main_searchview) + .setDuration(100) + .setInterpolator(DecelerateInterpolator()) + .translationY(0F) + + cancelFetch() + clearGalleries() + fetchGalleries(query, sortMode) + loadBlocks() + } + + override fun onNext(page: Int) { + currentPage++ + + // disable pageturn until the contents are loaded + setCurrentPage(1, false) + + ViewCompat.animate(main_searchview) + .setDuration(100) + .setInterpolator(DecelerateInterpolator()) + .translationY(0F) + + cancelFetch() + clearGalleries() + fetchGalleries(query, sortMode) + loadBlocks() + } + }) + } + setupSearchBar() setupRecyclerView() fetchGalleries(query, sortMode) @@ -400,207 +434,6 @@ class MainActivity : true } } - - var origin = 0f - var target = -1 - val perPage = Preferences["per_page", "25"].toInt() - setOnTouchListener { _, event -> - when(event.action) { - MotionEvent.ACTION_UP -> { - origin = 0f - - with(main_recyclerview.adapter as GalleryBlockAdapter) { - if(showPrev) { - showPrev = false - - val prev = main_recyclerview.layoutManager?.getChildAt(0) - - if (prev is LinearLayout) { - val icon = prev.findViewById(R.id.icon_prev) - prev.layoutParams.height = 1 - icon.layoutParams.height = 1 - icon.rotation = 180f - } - - prev?.requestLayout() - - notifyItemRemoved(0) - } - - if(showNext) { - showNext = false - - val next = main_recyclerview.layoutManager?.let { - getChildAt(childCount-1) - } - - if (next is LinearLayout) { - val icon = next.findViewById(R.id.icon_next) - next.layoutParams.height = 1 - icon.layoutParams.height = 1 - icon.rotation = 0f - } - - next?.requestLayout() - - notifyItemRemoved(itemCount) - } - } - - if (target != -1) { - currentPage = target - - runOnUiThread { - cancelFetch() - clearGalleries() - loadBlocks() - } - - target = -1 - } - } - MotionEvent.ACTION_DOWN -> origin = event.y - MotionEvent.ACTION_MOVE -> { - if (origin == 0f) - origin = event.y - - val dist = event.y - origin - - when { - !canScrollVertically(-1) -> { - //TOP - - //Scrolling UP - if (dist > 0 && currentPage != 0) { - with(main_recyclerview.adapter as GalleryBlockAdapter) { - if(!showPrev) { - showPrev = true - notifyItemInserted(0) - } - } - - val prev = main_recyclerview.layoutManager?.getChildAt(0) - - if (prev is LinearLayout) { - val icon = prev.findViewById(R.id.icon_prev) - val text = prev.findViewById(R.id.text_prev).apply { - text = getString(R.string.main_move, currentPage) - } - if (dist < 360) { - prev.layoutParams.height = (dist/2).roundToInt() - icon.layoutParams.height = (dist/2).roundToInt() - icon.rotation = dist+180 - text.layoutParams.width = dist.roundToInt() - - target = -1 - } - else { - prev.layoutParams.height = 180 - icon.layoutParams.height = 180 - icon.rotation = 180f - text.layoutParams.width = LinearLayout.LayoutParams.WRAP_CONTENT - - target = currentPage-1 - } - } - - prev?.requestLayout() - - return@setOnTouchListener true - } else { - with(main_recyclerview.adapter as GalleryBlockAdapter) { - if(showPrev) { - showPrev = false - - val prev = main_recyclerview.layoutManager?.getChildAt(0) - - if (prev is LinearLayout) { - val icon = prev.findViewById(R.id.icon_prev) - prev.layoutParams.height = 1 - icon.layoutParams.height = 1 - icon.rotation = 180f - } - - prev?.requestLayout() - - notifyItemRemoved(0) - } - } - } - } - !canScrollVertically(1) -> { - //BOTTOM - - //Scrolling DOWN - if (dist < 0 && currentPage != ceil(totalItems.toDouble()/perPage).roundToInt()-1) { - with(main_recyclerview.adapter as GalleryBlockAdapter) { - if(!showNext) { - showNext = true - notifyItemInserted(itemCount-1) - } - } - - val next = main_recyclerview.layoutManager?.let { - getChildAt(childCount-1) - } - - val absDist = abs(dist) - - if (next is LinearLayout) { - val icon = next.findViewById(R.id.icon_next) - val text = next.findViewById(R.id.text_next).apply { - text = getString(R.string.main_move, currentPage+2) - } - - if (absDist < 360) { - next.layoutParams.height = (absDist/2).roundToInt() - icon.layoutParams.height = (absDist/2).roundToInt() - icon.rotation = -absDist - text.layoutParams.width = absDist.roundToInt() - - target = -1 - } else { - next.layoutParams.height = 180 - icon.layoutParams.height = 180 - icon.rotation = 0f - text.layoutParams.width = LinearLayout.LayoutParams.WRAP_CONTENT - - target = currentPage+1 - } - } - - next?.requestLayout() - - return@setOnTouchListener true - } else { - with(main_recyclerview.adapter as GalleryBlockAdapter) { - if(showNext) { - showNext = false - - val next = main_recyclerview.layoutManager?.let { - getChildAt(childCount-1) - } - - if (next is LinearLayout) { - val icon = next.findViewById(R.id.icon_next) - next.layoutParams.height = 1 - icon.layoutParams.height = 1 - icon.rotation = 180f - } - - next?.requestLayout() - - notifyItemRemoved(itemCount) - } - } - } - } - } - } - } - - false - } } } @@ -829,7 +662,7 @@ class MainActivity : loadingJob?.cancel() } - private fun clearGalleries() { + private fun clearGalleries() = CoroutineScope(Dispatchers.Main).launch { galleries.clear() with(main_recyclerview.adapter as GalleryBlockAdapter?) { @@ -838,7 +671,6 @@ class MainActivity : this.notifyDataSetChanged() } - main_appbar_layout.setExpanded(true) main_noresult.visibility = View.INVISIBLE main_progressbar.show() } @@ -959,6 +791,10 @@ class MainActivity : return@launch } + launch(Dispatchers.Main) { + main_view.setCurrentPage(currentPage + 1, galleryIDs.size > (currentPage+1)*perPage) + } + galleryIDs.slice(currentPage*perPage until min(currentPage*perPage+perPage, galleryIDs.size)).chunked(5).let { chunks -> for (chunk in chunks) chunk.map { galleryID -> diff --git a/app/src/main/java/xyz/quaver/pupil/ui/view/FloatingSearchView.kt b/app/src/main/java/xyz/quaver/pupil/ui/view/FloatingSearchView.kt index 48af83f9..064f3d0c 100644 --- a/app/src/main/java/xyz/quaver/pupil/ui/view/FloatingSearchView.kt +++ b/app/src/main/java/xyz/quaver/pupil/ui/view/FloatingSearchView.kt @@ -22,7 +22,6 @@ import android.content.Context import android.graphics.PorterDuff import android.graphics.PorterDuffColorFilter import android.graphics.drawable.Animatable -import android.os.Parcelable import android.text.Editable import android.text.TextWatcher import android.util.AttributeSet @@ -38,8 +37,6 @@ import androidx.swiperefreshlayout.widget.CircularProgressDrawable import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat import xyz.quaver.floatingsearchview.FloatingSearchView import xyz.quaver.floatingsearchview.suggestions.model.SearchSuggestion -import xyz.quaver.floatingsearchview.util.MenuPopupHelper -import xyz.quaver.floatingsearchview.util.view.MenuView import xyz.quaver.floatingsearchview.util.view.SearchInputView import xyz.quaver.pupil.R import xyz.quaver.pupil.favoriteTags diff --git a/app/src/main/java/xyz/quaver/pupil/ui/view/MainView.java b/app/src/main/java/xyz/quaver/pupil/ui/view/MainView.java new file mode 100644 index 00000000..2c37eb83 --- /dev/null +++ b/app/src/main/java/xyz/quaver/pupil/ui/view/MainView.java @@ -0,0 +1,462 @@ +/* + * Pupil, Hitomi.la viewer for Android + * Copyright (C) 2020 tom5079 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package xyz.quaver.pupil.ui.view; + +import android.animation.ValueAnimator; +import android.content.Context; +import android.content.res.Configuration; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; +import android.os.Vibrator; +import android.util.AttributeSet; +import android.util.DisplayMetrics; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.DecelerateInterpolator; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatDelegate; +import androidx.appcompat.content.res.AppCompatResources; +import androidx.core.content.ContextCompat; +import androidx.core.view.NestedScrollingChild; +import androidx.core.view.NestedScrollingChildHelper; +import androidx.core.view.NestedScrollingParent; +import androidx.core.view.NestedScrollingParentHelper; +import androidx.core.view.ViewCompat; +import androidx.core.widget.TextViewCompat; + +import xyz.quaver.pupil.R; + +@SuppressWarnings("NullableProblems") +public class MainView extends ViewGroup implements NestedScrollingChild, NestedScrollingParent { + + private static final int PAGE_TURN_LAYOUT_SIZE = 48; + private static final int PAGE_TURN_ANIM_DURATION = 500; + private static final int PREV_OFFSET = 64; + private static final int RIPPLE_GIVE = 4; + + private final float adjustedPageTurnLayoutSize; + private final float adjustedPrevOffset; + private final float adjustedRippleGive; + + final private NestedScrollingParentHelper mNestedScrollingParentHelper; + final private NestedScrollingChildHelper mNestedScrollingChildHelper; + + final private Vibrator mVibrator; + + private View mTarget; + + private TextView mPrev; + private TextView mNext; + + private final Paint mRipplePaint = new Paint(); + private final Rect mRippleBound = new Rect(); + + private int mRippleSize = 0; + private final int mRippleTargetSize; + private final ValueAnimator mRippleAnimator = new ValueAnimator(); + + private int mCurrentOverScroll = 0; + + private int mCurrentPage = 1; + private boolean mShowPrev; + private boolean mShowNext; + + private OnPageTurnListener mOnPageTurnListener; + + public MainView(@NonNull Context context) { + this(context, null); + } + + public MainView(@NonNull Context context, AttributeSet attr) { + this(context, attr, 0); + } + + public MainView(@NonNull Context context, AttributeSet attr, int defStyle) { + super(context, attr, defStyle); + + setWillNotDraw(false); + + DisplayMetrics metrics = getResources().getDisplayMetrics(); + + adjustedPageTurnLayoutSize = PAGE_TURN_LAYOUT_SIZE * metrics.density; + adjustedPrevOffset = PREV_OFFSET * metrics.density; + adjustedRippleGive = RIPPLE_GIVE * metrics.density; + + mRippleTargetSize = metrics.widthPixels; + + mNestedScrollingParentHelper = new NestedScrollingParentHelper(this); + mNestedScrollingChildHelper = new NestedScrollingChildHelper(this); + + mVibrator = (Vibrator) getContext().getSystemService(Context.VIBRATOR_SERVICE); + + mRippleAnimator.addUpdateListener(animation -> { + mRippleSize = (int) animation.getAnimatedValue(); + invalidate(); + }); + mRippleAnimator.setDuration(PAGE_TURN_ANIM_DURATION); + + initPageTurnView(); + } + + public void setCurrentPage(int currentPage, boolean showNext) { + mCurrentPage = currentPage; + + mShowPrev = currentPage > 1; + mShowNext = showNext; + + mPrev.setText(getContext().getString(R.string.main_move_to_page, mCurrentPage-1)); + mNext.setText(getContext().getString(R.string.main_move_to_page, mCurrentPage+1)); + } + + public void setOnPageTurnListener(OnPageTurnListener listener) { + mOnPageTurnListener = listener; + } + + private void initPageTurnView() { + TextView prev = new TextView(getContext()); + TextView next = new TextView(getContext()); + + prev.setGravity(Gravity.CENTER_VERTICAL); + next.setGravity(Gravity.CENTER_VERTICAL); + + prev.setCompoundDrawablesWithIntrinsicBounds(R.drawable.navigate_prev, 0, 0, 0); + next.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.navigate_next, 0); + + TextViewCompat.setCompoundDrawableTintList(prev, AppCompatResources.getColorStateList(getContext(), R.color.colorAccent)); + TextViewCompat.setCompoundDrawableTintList(next, AppCompatResources.getColorStateList(getContext(), R.color.colorAccent)); + + prev.setVisibility(View.INVISIBLE); + next.setVisibility(View.INVISIBLE); + + mPrev = prev; + mNext = next; + + addView(mPrev); + addView(mNext); + + setCurrentPage(1, false); + } + + private void ensureTarget() { + if (mTarget == null) { + for (int i = 0; i < getChildCount(); i++) { + View child = getChildAt(i); + + if (!child.equals(mNext) && !child.equals(mPrev)) { + mTarget = child; + break; + } + } + } + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + final int width = getMeasuredWidth(); + final int height = getMeasuredHeight(); + + if (getChildCount() == 0) + return; + if (mTarget == null) + ensureTarget(); + if (mTarget == null) + return; + + mTarget.layout( + getPaddingLeft(), + getPaddingTop(), + width - getPaddingRight(), + height - getPaddingBottom() + ); + + final int prevWidth = mPrev.getMeasuredWidth(); + mPrev.layout( + width / 2 - prevWidth / 2, + getPaddingTop() + (int) adjustedPrevOffset, + width / 2 + prevWidth / 2, + getPaddingTop() + (int) adjustedPrevOffset + mPrev.getMeasuredHeight() + ); + + final int nextWidth = mNext.getMeasuredWidth(); + mNext.layout( + width / 2 - nextWidth / 2, + height - getPaddingBottom() - mNext.getMeasuredHeight(), + width / 2 + nextWidth / 2, + height - getPaddingBottom() + ); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + if (mTarget == null) + ensureTarget(); + if (mTarget == null) + return; + + mTarget.measure( + MeasureSpec.makeMeasureSpec(getMeasuredWidth() - getPaddingLeft() - getPaddingRight(), MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(getMeasuredHeight() - getPaddingTop() - getPaddingBottom(), MeasureSpec.EXACTLY) + ); + + mPrev.measure( + MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.AT_MOST), + MeasureSpec.makeMeasureSpec((int) adjustedPageTurnLayoutSize, MeasureSpec.EXACTLY) + ); + + mNext.measure( + MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.AT_MOST), + MeasureSpec.makeMeasureSpec((int) adjustedPageTurnLayoutSize, MeasureSpec.EXACTLY) + ); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + if (mCurrentOverScroll == 0) + return; + + if (mCurrentOverScroll > 0) { + mRippleBound.set( + getPaddingLeft(), + (int) (getPaddingTop() - adjustedRippleGive), + getMeasuredWidth() - getPaddingRight(), + (int) (getPaddingTop() + adjustedPrevOffset + mPrev.getMeasuredHeight() + adjustedRippleGive) + ); + } + + if (mCurrentOverScroll < 0) { + final int height = getMeasuredHeight(); + mRippleBound.set( + getPaddingLeft(), + (int) (height - getPaddingBottom() - mNext.getMeasuredHeight() - adjustedRippleGive), + getMeasuredWidth() - getPaddingRight(), + height - getPaddingBottom() + ); + } + + mRipplePaint.reset(); + mRipplePaint.setStyle(Paint.Style.FILL); + + int currentNightMode = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; + + switch (currentNightMode) { + case Configuration.UI_MODE_NIGHT_YES: + mRipplePaint.setColor(ContextCompat.getColor(getContext(), R.color.material_light_blue_700)); + break; + case Configuration.UI_MODE_NIGHT_NO: + mRipplePaint.setColor(ContextCompat.getColor(getContext(), R.color.material_light_blue_300)); + break; + } + + canvas.drawCircle( + (mRippleBound.left + mRippleBound.right) / 2F, + mCurrentOverScroll > 0 ? mRippleBound.bottom : mRippleBound.top, + mRippleSize, + mRipplePaint + ); + } + + private void onOverscroll(int overscroll) { + if (mTarget == null) + ensureTarget(); + if (mTarget == null) + return; + + mCurrentOverScroll = overscroll; + + if (overscroll > 0) { + mPrev.setVisibility(View.VISIBLE); + mNext.setVisibility(View.INVISIBLE); + } else if (overscroll < 0) { + mPrev.setVisibility(View.INVISIBLE); + mNext.setVisibility(View.VISIBLE); + } else { + mPrev.setVisibility(View.INVISIBLE); + mNext.setVisibility(View.INVISIBLE); + } + + if (Math.abs(overscroll) >= adjustedPageTurnLayoutSize) { + if (!mRippleAnimator.isStarted() && mRippleSize != mRippleTargetSize) { + mVibrator.vibrate(10); + + mRippleAnimator.setIntValues(mRippleSize, mRippleTargetSize); + mRippleAnimator.start(); + } + } else { + if (!mRippleAnimator.isStarted() && mRippleSize != 0) { + mRippleAnimator.setIntValues(mRippleSize, 0); + mRippleAnimator.start(); + } + } + + float clippedOverScrollTop = (overscroll > 0 ? 1 : -1) * Math.min(Math.abs(overscroll), adjustedPageTurnLayoutSize); + mTarget.setTranslationY(clippedOverScrollTop); + } + + private void onOverscrollEnd(int overscroll) { + if (mTarget == null) + ensureTarget(); + if (mTarget == null) + return; + + mRippleAnimator.cancel(); + mRippleAnimator.setIntValues(mRippleSize, 0); + mRippleAnimator.start(); + + mPrev.setVisibility(View.INVISIBLE); + mNext.setVisibility(View.INVISIBLE); + + ViewCompat.animate(mTarget) + .setDuration(PAGE_TURN_ANIM_DURATION) + .setInterpolator(new DecelerateInterpolator()) + .translationY(0); + + if (Math.abs(overscroll) > adjustedPageTurnLayoutSize && mOnPageTurnListener != null) { + if (overscroll > 0) + mOnPageTurnListener.onPrev(mCurrentPage-1); + if (overscroll < 0) + mOnPageTurnListener.onNext(mCurrentPage+1); + } + } + + // NestedScrollingParent + + private int mTotalUnconsumed = 0; + + @Override + public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) { + return isEnabled() && (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0; + } + + @Override + public void onNestedScrollAccepted(View child, View target, int axes) { + mNestedScrollingParentHelper.onNestedScrollAccepted(child, target, axes); + startNestedScroll(axes & ViewCompat.SCROLL_AXIS_VERTICAL); + + mTotalUnconsumed = 0; + } + + @Override + public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) { + if (mTotalUnconsumed != 0 && dy > 0 == mTotalUnconsumed > 0) { + if (Math.abs(dy) > Math.abs(mTotalUnconsumed)) { + consumed[1] = dy - mTotalUnconsumed; + mTotalUnconsumed = 0; + } else { + mTotalUnconsumed -= dy; + consumed[1] = dy; + } + + onOverscroll(mTotalUnconsumed); + } + + final int[] parentConsumed = new int[2]; + if (dispatchNestedPreScroll(dx - consumed[0], dy - consumed[1], parentConsumed, null)) { + consumed[0] += parentConsumed[0]; + consumed[1] += parentConsumed[1]; + } + } + + @Override + public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) { + final int[] mParentOffsetInWindow = new int[2]; + dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, mParentOffsetInWindow); + + final int dy = dyUnconsumed + mParentOffsetInWindow[1]; + + if (mTotalUnconsumed == 0 && ((dy < 0 && !mShowPrev) || (dy > 0 && !mShowNext))) + return; + + if (dy != 0) { + mTotalUnconsumed -= dy; + onOverscroll(mTotalUnconsumed); + } + } + + @Override + public void onStopNestedScroll(View child) { + mNestedScrollingParentHelper.onStopNestedScroll(child); + + if (Math.abs(mTotalUnconsumed) > 0) { + onOverscrollEnd(mTotalUnconsumed); + mTotalUnconsumed = 0; + } + + stopNestedScroll(); + } + + // NestedScrollingChild + + @Override + public void setNestedScrollingEnabled(boolean enabled) { + mNestedScrollingChildHelper.setNestedScrollingEnabled(enabled); + } + + @Override + public boolean isNestedScrollingEnabled() { + return mNestedScrollingChildHelper.isNestedScrollingEnabled(); + } + + @Override + public boolean startNestedScroll(int axes) { + return mNestedScrollingChildHelper.startNestedScroll(axes); + } + + @Override + public void stopNestedScroll() { + mNestedScrollingChildHelper.stopNestedScroll(); + } + + @Override + public boolean hasNestedScrollingParent() { + return mNestedScrollingChildHelper.hasNestedScrollingParent(); + } + + @Override + public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, @Nullable int[] offsetInWindow) { + return mNestedScrollingChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow); + } + + @Override + public boolean dispatchNestedPreScroll(int dx, int dy, @Nullable int[] consumed, @Nullable int[] offsetInWindow) { + return mNestedScrollingChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow); + } + + @Override + public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) { + return mNestedScrollingChildHelper.dispatchNestedFling(velocityX, velocityY, consumed); + } + + @Override + public boolean dispatchNestedPreFling(float velocityX, float velocityY) { + return mNestedScrollingChildHelper.dispatchNestedPreFling(velocityX, velocityY); + } + + public interface OnPageTurnListener { + void onPrev(int page); + void onNext(int page); + } +} diff --git a/app/src/main/java/xyz/quaver/pupil/ui/view/ProgressCard.kt b/app/src/main/java/xyz/quaver/pupil/ui/view/ProgressCard.kt index d8c77ff4..c66bbda1 100644 --- a/app/src/main/java/xyz/quaver/pupil/ui/view/ProgressCard.kt +++ b/app/src/main/java/xyz/quaver/pupil/ui/view/ProgressCard.kt @@ -4,13 +4,14 @@ import android.content.Context import android.util.AttributeSet import android.view.View import android.view.ViewGroup +import androidx.cardview.widget.CardView import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.content.ContextCompat import androidx.core.graphics.drawable.DrawableCompat import kotlinx.android.synthetic.main.view_progress_card.view.* import xyz.quaver.pupil.R -class ProgressCard @JvmOverloads constructor(context: Context, attr: AttributeSet? = null, defStyle: Int = R.attr.cardViewStyle) : ConstraintLayout(context, attr, defStyle) { +class ProgressCard @JvmOverloads constructor(context: Context, attr: AttributeSet? = null, defStyle: Int = R.attr.cardViewStyle) : CardView(context, attr, defStyle) { enum class Type { LOADING, @@ -61,10 +62,11 @@ class ProgressCard @JvmOverloads constructor(context: Context, attr: AttributeSe } } - override fun addView(child: View?, params: ViewGroup.LayoutParams?) = + override fun addView(child: View?, index: Int, params: ViewGroup.LayoutParams?) { if (childCount == 0) - super.addView(child, params) + super.addView(child, index, params) else - content.addView(child, params) + content.addView(child, index, params) + } } \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_navigate_next_black_24dp.xml b/app/src/main/res/drawable/navigate_next.xml similarity index 77% rename from app/src/main/res/drawable/ic_navigate_next_black_24dp.xml rename to app/src/main/res/drawable/navigate_next.xml index 137bcca7..c25983c7 100644 --- a/app/src/main/res/drawable/ic_navigate_next_black_24dp.xml +++ b/app/src/main/res/drawable/navigate_next.xml @@ -1,3 +1,3 @@ - + diff --git a/app/src/main/res/drawable/navigate_prev.xml b/app/src/main/res/drawable/navigate_prev.xml new file mode 100644 index 00000000..a3081f14 --- /dev/null +++ b/app/src/main/res/drawable/navigate_prev.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main_content.xml b/app/src/main/res/layout/activity_main_content.xml index cbe12f09..bfa69e3e 100644 --- a/app/src/main/res/layout/activity_main_content.xml +++ b/app/src/main/res/layout/activity_main_content.xml @@ -17,36 +17,18 @@ ~ along with this program. If not, see . --> - - - - - - - - - + + + + + + + + + app:fab_label="@string/main_fab_cancel" + app:fab_size="mini"/> - + app:fab_label="@string/main_jump_title" + app:fab_size="mini"/> - + - + - - - - - - - - - + + app:close_search_on_keyboard_dismiss="false" /> - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/item_galleryblock.xml b/app/src/main/res/layout/item_galleryblock.xml index b8fc0792..7638f8e7 100644 --- a/app/src/main/res/layout/item_galleryblock.xml +++ b/app/src/main/res/layout/item_galleryblock.xml @@ -24,126 +24,137 @@ android:id="@+id/galleryblock_card" android:layout_width="match_parent" android:layout_height="wrap_content" + android:clipChildren="true" + app:cardCornerRadius="4dp" + android:layout_marginLeft="8dp" + android:layout_marginRight="8dp" + app:cardUseCompatPadding="true" tools:ignore="RtlHardcoded"> - - - - - - - - - - - - - - - - - + android:layout_height="wrap_content"> - + - + - + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_next.xml b/app/src/main/res/layout/item_next.xml deleted file mode 100644 index 4627e96d..00000000 --- a/app/src/main/res/layout/item_next.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/item_prev.xml b/app/src/main/res/layout/item_prev.xml deleted file mode 100644 index c1051d66..00000000 --- a/app/src/main/res/layout/item_prev.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/view_progress_card.xml b/app/src/main/res/layout/view_progress_card.xml index 2b7b65bb..671a789f 100644 --- a/app/src/main/res/layout/view_progress_card.xml +++ b/app/src/main/res/layout/view_progress_card.xml @@ -1,15 +1,12 @@ - + tools:parentTag="androidx.cardview.widget.CardView"> - + android:background="?android:attr/selectableItemBackground" + android:orientation="vertical"> - + - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/view_swipe_pageturn.xml b/app/src/main/res/layout/view_swipe_pageturn.xml new file mode 100644 index 00000000..6d1f82e2 --- /dev/null +++ b/app/src/main/res/layout/view_swipe_pageturn.xml @@ -0,0 +1,30 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index c351ff4f..fcce73f7 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -48,7 +48,7 @@ ページ移動 現ページ番号: %1$d\nページ数: %2$d hitomi.laに接続できません - %1$dページへ移動 + %1$dページへ移動 ダウンロード削除 ダウンロードしたギャラリーを全て削除します。\n実行しますか? ミラーサーバからイメージをロード diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index d7f74f3c..4824b0a7 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -47,7 +47,7 @@ 페이지 이동 현재 페이지: %1$d\n페이지 수: %2$d hitomi.la에 연결할 수 없습니다 - %1$d 페이지로 이동 + %1$d 페이지로 이동 다운로드 삭제 다운로드 된 만화를 모두 삭제합니다.\n계속하시겠습니까? 즐겨찾기 diff --git a/app/src/main/res/values/attr.xml b/app/src/main/res/values/attrs.xml similarity index 80% rename from app/src/main/res/values/attr.xml rename to app/src/main/res/values/attrs.xml index d13a3706..753729ee 100644 --- a/app/src/main/res/values/attr.xml +++ b/app/src/main/res/values/attrs.xml @@ -21,4 +21,11 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index faaf0be1..b313bcdf 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -4,6 +4,8 @@ #0093c4 #D81B60 + #4fc3f7 + #0288d1 #d81b60 #1976d2 #00c853 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6173bf9e..90b50039 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -71,7 +71,7 @@ Open a random gallery Cancel all downloads - Move to page %1$d + Move to page %1$d DOWNLOAD DELETE diff --git a/gradle.properties b/gradle.properties index da320022..2c2f1284 100644 --- a/gradle.properties +++ b/gradle.properties @@ -20,4 +20,4 @@ kotlin.code.style=official android.enableJetifier=true android.useAndroidX=true -kotlin_version=1.4.10 \ No newline at end of file +kotlin_version=1.4.20 \ No newline at end of file