Added Tips for filters and chapter swipe

Using tooltip library to showtips
Swipe animations for first chapter item
Flow preferences to handle this
This commit is contained in:
Jay 2020-04-25 00:47:01 -04:00
parent 3bee24e79a
commit e9efc7d020
14 changed files with 118 additions and 26 deletions

View File

@ -204,6 +204,7 @@ dependencies {
implementation("com.github.kizitonwose:AndroidTagGroup:1.6.0") implementation("com.github.kizitonwose:AndroidTagGroup:1.6.0")
implementation("com.github.chrisbanes:PhotoView:2.3.0") implementation("com.github.chrisbanes:PhotoView:2.3.0")
implementation("com.github.carlosesco:DirectionalViewPager:a844dbca0a") implementation("com.github.carlosesco:DirectionalViewPager:a844dbca0a")
implementation("com.github.florent37:viewtooltip:1.2.2")
// Conductor // Conductor
implementation("com.bluelinelabs:conductor:2.1.5") implementation("com.bluelinelabs:conductor:2.1.5")

View File

@ -7,6 +7,7 @@ import android.os.Environment
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.f2prateek.rx.preferences.Preference import com.f2prateek.rx.preferences.Preference
import com.f2prateek.rx.preferences.RxSharedPreferences import com.f2prateek.rx.preferences.RxSharedPreferences
import com.tfcporciuncula.flow.FlowSharedPreferences
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.track.TrackService import eu.kanade.tachiyomi.data.track.TrackService
import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.Source
@ -40,6 +41,7 @@ class PreferencesHelper(val context: Context) {
private val prefs = PreferenceManager.getDefaultSharedPreferences(context) private val prefs = PreferenceManager.getDefaultSharedPreferences(context)
private val rxPrefs = RxSharedPreferences.create(prefs) private val rxPrefs = RxSharedPreferences.create(prefs)
private val flowPrefs = FlowSharedPreferences(prefs)
private val defaultDownloadsDir = Uri.fromFile( private val defaultDownloadsDir = Uri.fromFile(
File(Environment.getExternalStorageDirectory().absolutePath + File.separator + File(Environment.getExternalStorageDirectory().absolutePath + File.separator +
@ -258,4 +260,9 @@ class PreferencesHelper(val context: Context) {
fun hideFiltersAtStart() = rxPrefs.getBoolean("hide_filters_at_start", false) fun hideFiltersAtStart() = rxPrefs.getBoolean("hide_filters_at_start", false)
fun alwaysShowChapterTransition() = rxPrefs.getBoolean(Keys.alwaysShowChapterTransition, true) fun alwaysShowChapterTransition() = rxPrefs.getBoolean(Keys.alwaysShowChapterTransition, true)
// Tutorial preference
fun shownFilterTutorial() = flowPrefs.getBoolean("shown_filter_tutorial", false)
fun shownChapterSwipeTutorial() = flowPrefs.getBoolean("shown_swipe_tutorial", false)
} }

View File

@ -14,7 +14,6 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.ViewPropertyAnimator import android.view.ViewPropertyAnimator
import android.view.inputmethod.InputMethodManager import android.view.inputmethod.InputMethodManager
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode import androidx.appcompat.view.ActionMode
import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView
@ -27,6 +26,7 @@ import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.list.listItemsSingleChoice import com.afollestad.materialdialogs.list.listItemsSingleChoice
import com.bluelinelabs.conductor.ControllerChangeHandler import com.bluelinelabs.conductor.ControllerChangeHandler
import com.bluelinelabs.conductor.ControllerChangeType import com.bluelinelabs.conductor.ControllerChangeType
import com.github.florent37.viewtooltip.ViewTooltip
import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.snackbar.BaseTransientBottomBar import com.google.android.material.snackbar.BaseTransientBottomBar
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
@ -57,7 +57,6 @@ import eu.kanade.tachiyomi.util.system.dpToPx
import eu.kanade.tachiyomi.util.system.dpToPxEnd import eu.kanade.tachiyomi.util.system.dpToPxEnd
import eu.kanade.tachiyomi.util.system.getResourceColor import eu.kanade.tachiyomi.util.system.getResourceColor
import eu.kanade.tachiyomi.util.system.launchUI import eu.kanade.tachiyomi.util.system.launchUI
import eu.kanade.tachiyomi.util.system.toast
import eu.kanade.tachiyomi.util.view.applyWindowInsetsForRootController import eu.kanade.tachiyomi.util.view.applyWindowInsetsForRootController
import eu.kanade.tachiyomi.util.view.getItemView import eu.kanade.tachiyomi.util.view.getItemView
import eu.kanade.tachiyomi.util.view.gone import eu.kanade.tachiyomi.util.view.gone
@ -140,6 +139,7 @@ class LibraryController(
private var textAnim: ViewPropertyAnimator? = null private var textAnim: ViewPropertyAnimator? = null
private var scrollAnim: ViewPropertyAnimator? = null private var scrollAnim: ViewPropertyAnimator? = null
private var alwaysShowScroller: Boolean = preferences.alwaysShowSeeker().getOrDefault() private var alwaysShowScroller: Boolean = preferences.alwaysShowSeeker().getOrDefault()
private var filterTooltip: ViewTooltip? = null
override fun getTitle(): String? { override fun getTitle(): String? {
return view?.context?.getString(R.string.library) return view?.context?.getString(R.string.library)
@ -198,6 +198,28 @@ class LibraryController(
} }
} }
private fun showFilterTip() {
if (preferences.shownFilterTutorial().get()) return
val activity = activity ?: return
val icon = activity.bottom_nav.getItemView(R.id.nav_library) ?: return
filterTooltip =
ViewTooltip.on(activity, icon).autoHide(false, 0L).align(ViewTooltip.ALIGN.START)
.position(ViewTooltip.Position.TOP).text(R.string.tap_library_to_show_filters)
.color(activity.getResourceColor(R.attr.colorAccent))
.textSize(TypedValue.COMPLEX_UNIT_SP, 15f).textColor(Color.WHITE).withShadow(false)
.corner(30).arrowWidth(15).arrowHeight(15).distanceWithView(0)
filterTooltip?.show()
}
private fun closeTip() {
if (filterTooltip != null) {
filterTooltip?.close()
filterTooltip = null
preferences.shownFilterTutorial().set(true)
}
}
private fun hideScroller(duration: Long = 1000) { private fun hideScroller(duration: Long = 1000) {
if (alwaysShowScroller) return if (alwaysShowScroller) return
scrollAnim = scrollAnim =
@ -447,7 +469,7 @@ class LibraryController(
presenter.getLibrary() presenter.getLibrary()
DownloadService.callListeners() DownloadService.callListeners()
LibraryUpdateService.setListener(this) LibraryUpdateService.setListener(this)
} } else closeTip()
if (type == ControllerChangeType.POP_ENTER) filter_bottom_sheet.hideIfPossible() if (type == ControllerChangeType.POP_ENTER) filter_bottom_sheet.hideIfPossible()
} }
@ -836,6 +858,7 @@ class LibraryController(
} }
override fun showSheet() { override fun showSheet() {
closeTip()
when { when {
filter_bottom_sheet.sheetBehavior?.state == BottomSheetBehavior.STATE_HIDDEN -> filter_bottom_sheet.sheetBehavior?.state = filter_bottom_sheet.sheetBehavior?.state == BottomSheetBehavior.STATE_HIDDEN -> filter_bottom_sheet.sheetBehavior?.state =
BottomSheetBehavior.STATE_COLLAPSED BottomSheetBehavior.STATE_COLLAPSED

View File

@ -1,5 +1,6 @@
package eu.kanade.tachiyomi.ui.library package eu.kanade.tachiyomi.ui.library
import android.annotation.SuppressLint
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.text.SpannableString import android.text.SpannableString
import android.text.style.ForegroundColorSpan import android.text.style.ForegroundColorSpan
@ -164,6 +165,8 @@ class LibraryHeaderItem(
updateButton.invisible() updateButton.invisible()
} }
} }
@SuppressLint("RestrictedApi")
private fun showCatSortOptions() { private fun showCatSortOptions() {
val category = val category =
(adapter.getItem(adapterPosition) as? LibraryHeaderItem)?.category ?: return (adapter.getItem(adapterPosition) as? LibraryHeaderItem)?.category ?: return

View File

@ -133,6 +133,7 @@ class FilterBottomSheet @JvmOverloads constructor(context: Context, attrs: Attri
pill.alpha = 0f pill.alpha = 0f
} }
if (state == BottomSheetBehavior.STATE_HIDDEN) { if (state == BottomSheetBehavior.STATE_HIDDEN) {
onGroupClicked(ACTION_HIDE_FILTER_TIP)
reSortViews() reSortViews()
shadow?.alpha = 0f shadow?.alpha = 0f
pager?.updatePaddingRelative(bottom = 0) pager?.updatePaddingRelative(bottom = 0)

View File

@ -1,6 +1,5 @@
package eu.kanade.tachiyomi.ui.manga package eu.kanade.tachiyomi.ui.manga
import android.content.Context
import android.view.View import android.view.View
import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.ItemTouchHelper
import eu.davidea.flexibleadapter.items.IFlexible import eu.davidea.flexibleadapter.items.IFlexible
@ -13,12 +12,14 @@ import java.text.DecimalFormat
import java.text.DecimalFormatSymbols import java.text.DecimalFormatSymbols
class MangaDetailsAdapter( class MangaDetailsAdapter(
val controller: MangaDetailsController, val controller: MangaDetailsController
context: Context
) : BaseChapterAdapter<IFlexible<*>>(controller) { ) : BaseChapterAdapter<IFlexible<*>>(controller) {
val preferences: PreferencesHelper by injectLazy() val preferences: PreferencesHelper by injectLazy()
val hasShownSwipeTut
get() = preferences.shownChapterSwipeTutorial()
var items: List<ChapterItem> = emptyList() var items: List<ChapterItem> = emptyList()
val delegate: MangaDetailsInterface = controller val delegate: MangaDetailsInterface = controller

View File

@ -231,7 +231,7 @@ class MangaDetailsController : BaseController,
width = ViewGroup.LayoutParams.MATCH_PARENT width = ViewGroup.LayoutParams.MATCH_PARENT
} }
tabletRecycler?.clipToPadding = false tabletRecycler?.clipToPadding = false
tabletAdapter = MangaDetailsAdapter(this, view.context) tabletAdapter = MangaDetailsAdapter(this)
tabletRecycler?.adapter = tabletAdapter tabletRecycler?.adapter = tabletAdapter
tabletRecycler?.layoutManager = LinearLayoutManager(view.context) tabletRecycler?.layoutManager = LinearLayoutManager(view.context)
val divider = View(view.context) val divider = View(view.context)
@ -246,7 +246,7 @@ class MangaDetailsController : BaseController,
/** Set adapter, insets, and scroll listener for recycler view */ /** Set adapter, insets, and scroll listener for recycler view */
private fun setRecycler(view: View) { private fun setRecycler(view: View) {
adapter = MangaDetailsAdapter(this, view.context) adapter = MangaDetailsAdapter(this)
recycler.adapter = adapter recycler.adapter = adapter
adapter?.isSwipeEnabled = true adapter?.isSwipeEnabled = true

View File

@ -1,5 +1,7 @@
package eu.kanade.tachiyomi.ui.manga.chapter package eu.kanade.tachiyomi.ui.manga.chapter
import android.animation.AnimatorSet
import android.animation.ObjectAnimator
import android.view.View import android.view.View
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
@ -7,8 +9,13 @@ import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.source.LocalSource import eu.kanade.tachiyomi.source.LocalSource
import eu.kanade.tachiyomi.ui.manga.MangaDetailsAdapter import eu.kanade.tachiyomi.ui.manga.MangaDetailsAdapter
import eu.kanade.tachiyomi.util.chapter.ChapterUtil import eu.kanade.tachiyomi.util.chapter.ChapterUtil
import eu.kanade.tachiyomi.util.system.dpToPx
import eu.kanade.tachiyomi.util.view.gone import eu.kanade.tachiyomi.util.view.gone
import eu.kanade.tachiyomi.util.view.isVisible
import eu.kanade.tachiyomi.util.view.visible
import eu.kanade.tachiyomi.util.view.visibleIf import eu.kanade.tachiyomi.util.view.visibleIf
import eu.kanade.tachiyomi.widget.EndAnimatorListener
import eu.kanade.tachiyomi.widget.StartAnimatorListener
import kotlinx.android.synthetic.main.chapters_item.* import kotlinx.android.synthetic.main.chapters_item.*
import kotlinx.android.synthetic.main.download_button.* import kotlinx.android.synthetic.main.download_button.*
@ -86,6 +93,39 @@ class ChapterHolder(
notifyStatus(status, item.isLocked, item.progress) notifyStatus(status, item.isLocked, item.progress)
resetFrontView() resetFrontView()
if (adapterPosition == 1) {
if (!adapter.hasShownSwipeTut.get())
showSlideAnimation()
}
}
private fun showSlideAnimation() {
val slide = 100f.dpToPx
val animatorSet = AnimatorSet()
val anim1 = slideAnimation(0f, slide)
anim1.startDelay = 1000
anim1.addListener(StartAnimatorListener { left_view.visible() })
val anim2 = slideAnimation(slide, -slide)
anim2.duration = 600
anim2.startDelay = 500
anim2.addUpdateListener {
if (left_view.isVisible() && front_view.translationX <= 0) {
left_view.gone()
right_view.visible()
}
}
val anim3 = slideAnimation(-slide, 0f)
anim3.startDelay = 750
animatorSet.playSequentially(anim1, anim2, anim3)
animatorSet.addListener(EndAnimatorListener {
adapter.hasShownSwipeTut.set(true)
})
animatorSet.start()
}
private fun slideAnimation(from: Float, to: Float): ObjectAnimator {
return ObjectAnimator.ofFloat(front_view, View.TRANSLATION_X, from, to)
.setDuration(300)
} }
override fun getFrontView(): View { override fun getFrontView(): View {

View File

@ -1,5 +1,6 @@
package eu.kanade.tachiyomi.widget package eu.kanade.tachiyomi.widget
import android.animation.Animator
import android.view.animation.Animation import android.view.animation.Animation
open class SimpleAnimationListener : Animation.AnimationListener { open class SimpleAnimationListener : Animation.AnimationListener {
@ -9,3 +10,27 @@ open class SimpleAnimationListener : Animation.AnimationListener {
override fun onAnimationStart(animation: Animation) {} override fun onAnimationStart(animation: Animation) {}
} }
open class SimpleAnimatorListener : Animator.AnimatorListener {
override fun onAnimationCancel(animation: Animator?) {}
override fun onAnimationRepeat(animator: Animator) {}
override fun onAnimationEnd(animator: Animator) {}
override fun onAnimationStart(animator: Animator) {}
}
class StartAnimatorListener(private val startAnimationListener: (animator: Animator) -> Unit) :
SimpleAnimatorListener() {
override fun onAnimationStart(animator: Animator) {
startAnimationListener(animator)
}
}
class EndAnimatorListener(private val endAnimationListener: (animator: Animator) -> Unit) :
SimpleAnimatorListener() {
override fun onAnimationEnd(animator: Animator) {
endAnimationListener(animator)
}
}

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<eu.kanade.tachiyomi.ui.library.filter.FilterBottomSheet xmlns:android="http://schemas.android.com/apk/res/android" <eu.kanade.tachiyomi.ui.library.filter.FilterBottomSheet xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/bottom_sheet" android:id="@+id/filter_bottom_sheet"
style="@style/BottomSheetDialogTheme" style="@style/BottomSheetDialogTheme"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"

View File

@ -77,7 +77,7 @@
android:alpha="0.5" android:alpha="0.5"
android:background="@drawable/shape_gradient_top_shadow" android:background="@drawable/shape_gradient_top_shadow"
android:paddingBottom="10dp" android:paddingBottom="10dp"
app:layout_anchor="@id/bottom_sheet" app:layout_anchor="@id/filter_bottom_sheet"
app:layout_anchorGravity="top" /> app:layout_anchorGravity="top" />
<!-- Adding bottom sheet after main content --> <!-- Adding bottom sheet after main content -->

View File

@ -2,7 +2,7 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/bottom_sheet" android:id="@+id/filter_bottom_sheet"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="@drawable/bottom_sheet_rounded_background" android:background="@drawable/bottom_sheet_rounded_background"

View File

@ -1,8 +1,8 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android" <menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
tools:context=".MainActivity" app:tint="@android:color/black"
app:tint="@android:color/black"> tools:context=".MainActivity">
<item <item
android:id="@+id/action_search" android:id="@+id/action_search"
@ -12,19 +12,10 @@
app:actionViewClass="androidx.appcompat.widget.SearchView" app:actionViewClass="androidx.appcompat.widget.SearchView"
app:showAsAction="collapseActionView|ifRoom" /> app:showAsAction="collapseActionView|ifRoom" />
<item
android:id="@+id/action_library_display"
android:icon="@drawable/ic_tune_white_24dp"
android:visible="false"
android:title="@string/display_options"
app:showAsAction="ifRoom"
/>
<item <item
android:id="@+id/action_settings" android:id="@+id/action_settings"
android:icon="@drawable/ic_settings_white_24dp" android:icon="@drawable/ic_settings_white_24dp"
android:title="@string/settings" android:title="@string/settings"
app:showAsAction="ifRoom" app:showAsAction="ifRoom" />
/>
</menu> </menu>

View File

@ -140,7 +140,7 @@
<string name="hide_badges">Hide badges</string> <string name="hide_badges">Hide badges</string>
<string name="show_badges">Show badges</string> <string name="show_badges">Show badges</string>
<string name="show_count">Show count</string> <string name="show_count">Show count</string>
<string name="hide_filters_tip">To show filters again, tap the Library icon</string> <string name="tap_library_to_show_filters">Tap the Library icon to show filters</string>
<string name="display_as">Display as</string> <string name="display_as">Display as</string>
<!-- Library update service notifications --> <!-- Library update service notifications -->