Some UI fixes

This commit is contained in:
Koitharu
2020-02-12 21:01:11 +02:00
parent faf8e57619
commit 315aea8b5c
18 changed files with 203 additions and 45 deletions

View File

@@ -32,7 +32,9 @@ class LocalMangaRepository(loaderContext: MangaLoaderContext) : BaseMangaReposit
return files.mapNotNull { x -> safe { getDetails(x) } }
}
override suspend fun getDetails(manga: Manga) = manga
override suspend fun getDetails(manga: Manga) = if (manga.chapters == null) {
getDetails(Uri.parse(manga.url).toFile())
} else manga
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
val file = Uri.parse(chapter.url).toFile()
@@ -44,7 +46,7 @@ class LocalMangaRepository(loaderContext: MangaLoaderContext) : BaseMangaReposit
.filter { x -> !x.isDirectory && x.name.substringBefore('.').matches(pattern) }
} else {
zip.entries().asSequence().filter { x -> !x.isDirectory }
}.toList().sortedWith(compareBy(AlphanumComparator()) { x -> x.name})
}.toList().sortedWith(compareBy(AlphanumComparator()) { x -> x.name })
return entries.map { x ->
val uri = zipUri(file, x.name)
MangaPage(

View File

@@ -1,5 +1,7 @@
package org.koitharu.kotatsu.domain.favourites
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.koin.core.KoinComponent
import org.koin.core.inject
import org.koitharu.kotatsu.core.db.MangaDatabase
@@ -9,6 +11,7 @@ import org.koitharu.kotatsu.core.db.entity.MangaEntity
import org.koitharu.kotatsu.core.db.entity.TagEntity
import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.core.model.Manga
import java.util.*
class FavouritesRepository : KoinComponent {
@@ -49,9 +52,30 @@ class FavouritesRepository : KoinComponent {
db.mangaDao().upsert(MangaEntity.from(manga), tags)
val entity = FavouriteEntity(manga.id, categoryId, System.currentTimeMillis())
db.favouritesDao().add(entity)
notifyFavouritesChanged(manga.id)
}
suspend fun removeFromCategory(manga: Manga, categoryId: Long) {
db.favouritesDao().delete(categoryId, manga.id)
notifyFavouritesChanged(manga.id)
}
companion object {
private val listeners = HashSet<OnFavouritesChangeListener>()
fun subscribe(listener: OnFavouritesChangeListener) {
listeners += listener
}
fun unsubscribe(listener: OnFavouritesChangeListener) {
listeners += listener
}
private suspend fun notifyFavouritesChanged(mangaId: Long) {
withContext(Dispatchers.Main) {
listeners.forEach { x -> x.onFavouritesChanged(mangaId) }
}
}
}
}

View File

@@ -0,0 +1,6 @@
package org.koitharu.kotatsu.domain.favourites
interface OnFavouritesChangeListener {
fun onFavouritesChanged(mangaId: Long)
}

View File

@@ -1,24 +1,21 @@
package org.koitharu.kotatsu.ui.common
import android.graphics.Color
import android.os.Bundle
import android.view.View
import android.view.WindowManager
abstract class BaseFullscreenActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
window.decorView.setOnSystemUiVisibilityChangeListener { visibility ->
if (visibility and View.SYSTEM_UI_FLAG_FULLSCREEN == 0) {
onSystemUiShown()
} else {
onSystemUiHidden()
}
with(window) {
addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
statusBarColor = Color.TRANSPARENT
navigationBarColor = Color.TRANSPARENT
}
}
override fun onWindowFocusChanged(hasFocus: Boolean) {
super.onWindowFocusChanged(hasFocus)
if (hasFocus) hideSystemUI()
showSystemUI()
}
protected fun hideSystemUI() {
@@ -28,6 +25,7 @@ abstract class BaseFullscreenActivity : BaseActivity() {
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
or View.SYSTEM_UI_FLAG_FULLSCREEN)
}
protected fun showSystemUI() {
@@ -35,8 +33,4 @@ abstract class BaseFullscreenActivity : BaseActivity() {
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)
}
protected open fun onSystemUiShown() = Unit
protected open fun onSystemUiHidden() = Unit
}

View File

@@ -4,11 +4,12 @@ import android.annotation.SuppressLint
import android.content.Context
import android.content.DialogInterface
import android.view.LayoutInflater
import android.view.inputmethod.InputMethodManager
import android.widget.TextView
import androidx.annotation.StringRes
import androidx.appcompat.app.AlertDialog
import kotlinx.android.synthetic.main.dialog_input.view.*
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.utils.ext.hideKeyboard
import org.koitharu.kotatsu.utils.ext.showKeyboard
class TextInputDialog private constructor(private val delegate: AlertDialog) :
@@ -16,7 +17,11 @@ class TextInputDialog private constructor(private val delegate: AlertDialog) :
init {
delegate.setOnShowListener {
delegate.currentFocus?.showKeyboard()
val view = delegate.findViewById<TextView>(R.id.inputEdit)?:return@setOnShowListener
view.post {
view.requestFocus()
view.showKeyboard()
}
}
}
@@ -29,6 +34,10 @@ class TextInputDialog private constructor(private val delegate: AlertDialog) :
private val delegate = AlertDialog.Builder(context)
.setView(view)
.setOnDismissListener {
val imm = view.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.toggleSoftInput(InputMethodManager.HIDE_IMPLICIT_ONLY, 0)
}
fun setTitle(@StringRes titleResId: Int): Builder {
delegate.setTitle(titleResId)
@@ -52,7 +61,6 @@ class TextInputDialog private constructor(private val delegate: AlertDialog) :
fun setPositiveButton(@StringRes textId: Int, listener: (DialogInterface, String) -> Unit): Builder {
delegate.setPositiveButton(textId) { dialog, _ ->
view.hideKeyboard()
listener(dialog, view.inputEdit.text?.toString().orEmpty())
}
return this

View File

@@ -8,6 +8,7 @@ import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.fragment_chapters.*
import moxy.ktx.moxyPresenter
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.core.model.MangaChapter
import org.koitharu.kotatsu.core.model.MangaHistory
@@ -56,6 +57,8 @@ class ChaptersFragment : BaseFragment(R.layout.fragment_chapters), MangaDetailsV
adapter.currentChapterId = history?.chapterId
}
override fun onFavouriteChanged(categories: List<FavouriteCategory>) = Unit
override fun onItemClick(item: MangaChapter, position: Int, view: View) {
startActivity(
ReaderActivity.newIntent(

View File

@@ -11,6 +11,7 @@ import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.activity_details.*
import moxy.ktx.moxyPresenter
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.core.model.MangaHistory
import org.koitharu.kotatsu.core.model.MangaSource
@@ -44,6 +45,8 @@ class MangaDetailsActivity : BaseActivity(), MangaDetailsView {
override fun onHistoryChanged(history: MangaHistory?) = Unit
override fun onFavouriteChanged(categories: List<FavouriteCategory>) = Unit
override fun onLoadingStateChanged(isLoading: Boolean) = Unit
override fun onError(e: Exception) {

View File

@@ -6,6 +6,7 @@ import coil.api.load
import kotlinx.android.synthetic.main.fragment_details.*
import moxy.ktx.moxyPresenter
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.core.model.MangaHistory
import org.koitharu.kotatsu.ui.common.BaseFragment
@@ -52,6 +53,16 @@ class MangaDetailsFragment : BaseFragment(R.layout.fragment_details), MangaDetai
updateReadButton()
}
override fun onFavouriteChanged(categories: List<FavouriteCategory>) {
imageView_favourite.setImageResource(
if (categories.isEmpty()) {
R.drawable.ic_tag_heart_outline
} else {
R.drawable.ic_tag_heart
}
)
}
override fun onLoadingStateChanged(isLoading: Boolean) {
progressBar.isVisible = isLoading
}

View File

@@ -7,21 +7,27 @@ import moxy.InjectViewState
import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.domain.MangaProviderFactory
import org.koitharu.kotatsu.domain.favourites.FavouritesRepository
import org.koitharu.kotatsu.domain.favourites.OnFavouritesChangeListener
import org.koitharu.kotatsu.domain.history.HistoryRepository
import org.koitharu.kotatsu.domain.history.OnHistoryChangeListener
import org.koitharu.kotatsu.ui.common.BasePresenter
@InjectViewState
class MangaDetailsPresenter : BasePresenter<MangaDetailsView>(), OnHistoryChangeListener {
class MangaDetailsPresenter : BasePresenter<MangaDetailsView>(), OnHistoryChangeListener,
OnFavouritesChangeListener {
private lateinit var historyRepository: HistoryRepository
private lateinit var favouritesRepository: FavouritesRepository
private var manga: Manga? = null
override fun onFirstViewAttach() {
historyRepository = HistoryRepository()
favouritesRepository = FavouritesRepository()
super.onFirstViewAttach()
HistoryRepository.subscribe(this)
FavouritesRepository.subscribe(this)
}
fun loadDetails(manga: Manga, force: Boolean = false) {
@@ -30,6 +36,7 @@ class MangaDetailsPresenter : BasePresenter<MangaDetailsView>(), OnHistoryChange
}
loadHistory(manga)
viewState.onMangaUpdated(manga)
loadFavourite(manga)
launch {
try {
viewState.onLoadingStateChanged(true)
@@ -64,12 +71,34 @@ class MangaDetailsPresenter : BasePresenter<MangaDetailsView>(), OnHistoryChange
}
}
private fun loadFavourite(manga: Manga) {
launch {
try {
val categories = withContext(Dispatchers.IO) {
favouritesRepository.getCategories(manga.id)
}
viewState.onFavouriteChanged(categories)
} catch (e: Exception) {
if (BuildConfig.DEBUG) {
e.printStackTrace()
}
}
}
}
override fun onHistoryChanged() {
loadHistory(manga ?: return)
}
override fun onFavouritesChanged(mangaId: Long) {
if (mangaId == manga?.id) {
loadFavourite(manga!!)
}
}
override fun onDestroy() {
HistoryRepository.unsubscribe(this)
FavouritesRepository.unsubscribe(this)
super.onDestroy()
}
}

View File

@@ -4,6 +4,7 @@ import moxy.MvpView
import moxy.viewstate.strategy.AddToEndSingleStrategy
import moxy.viewstate.strategy.OneExecutionStateStrategy
import moxy.viewstate.strategy.StateStrategyType
import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.core.model.MangaHistory
@@ -20,4 +21,7 @@ interface MangaDetailsView : MvpView {
@StateStrategyType(AddToEndSingleStrategy::class)
fun onHistoryChanged(history: MangaHistory?)
@StateStrategyType(AddToEndSingleStrategy::class)
fun onFavouriteChanged(categories: List<FavouriteCategory>)
}

View File

@@ -6,8 +6,8 @@ import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import android.view.MotionEvent
import android.widget.Button
import android.widget.Toast
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.core.view.updatePadding
import kotlinx.android.synthetic.main.activity_reader.*
@@ -18,6 +18,8 @@ import org.koitharu.kotatsu.core.model.MangaHistory
import org.koitharu.kotatsu.core.model.MangaPage
import org.koitharu.kotatsu.ui.common.BaseFullscreenActivity
import org.koitharu.kotatsu.utils.GridTouchHelper
import org.koitharu.kotatsu.utils.ext.hasGlobalPoint
import org.koitharu.kotatsu.utils.ext.hitTest
import org.koitharu.kotatsu.utils.ext.showDialog
class ReaderActivity : BaseFullscreenActivity(), ReaderView, GridTouchHelper.OnGridTouchListener {
@@ -107,25 +109,17 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, GridTouchHelper.OnG
}
}
override fun onSystemUiShown() {
appbar_top.isGone = true
appbar_bottom.isGone = true
}
override fun onSystemUiHidden() {
appbar_top.isGone = false
appbar_bottom.isGone = false
}
override fun onGridTouch(area: Int) {
when (area) {
GridTouchHelper.AREA_CENTER -> {
if (appbar_top.isVisible) {
appbar_top.isVisible = false
appbar_bottom.isVisible = false
hideSystemUI()
} else {
appbar_top.isVisible = true
appbar_bottom.isVisible = true
showSystemUI()
}
}
GridTouchHelper.AREA_TOP,
@@ -139,6 +133,15 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, GridTouchHelper.OnG
}
}
override fun onProcessTouch(rawX: Int, rawY: Int): Boolean {
return if (appbar_top.hasGlobalPoint(rawX, rawY) || appbar_bottom.hasGlobalPoint(rawX, rawY)) {
false
} else {
val target = rootLayout.hitTest(rawX, rawY)
target !is Button
}
}
override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
touchHelper.dispatchTouchEvent(ev)
return super.dispatchTouchEvent(ev)

View File

@@ -22,6 +22,9 @@ class GridTouchHelper(context: Context, private val listener: OnGridTouchListene
}
override fun onSingleTapConfirmed(event: MotionEvent): Boolean {
if (!listener.onProcessTouch(event.rawX.toInt(), event.rawY.toInt())) {
return false
}
val xIndex = (event.rawX * 2f / width).roundToInt()
val yIndex = (event.rawY * 2f / height).roundToInt()
listener.onGridTouch(
@@ -54,5 +57,7 @@ class GridTouchHelper(context: Context, private val listener: OnGridTouchListene
interface OnGridTouchListener {
fun onGridTouch(area: Int)
fun onProcessTouch(rawX: Int, rawY: Int): Boolean
}
}

View File

@@ -1,17 +1,16 @@
package org.koitharu.kotatsu.utils.ext
import android.app.Activity
import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.view.*
import android.view.inputmethod.InputMethodManager
import android.widget.EditText
import android.widget.TextView
import androidx.annotation.LayoutRes
import androidx.annotation.MenuRes
import androidx.appcompat.widget.PopupMenu
import androidx.core.view.children
import androidx.core.view.isGone
import androidx.core.view.postDelayed
import androidx.recyclerview.widget.GridLayoutManager
@@ -101,9 +100,35 @@ fun View.disableFor(timeInMillis: Long) {
}
}
fun View.showPopupMenu(@MenuRes menuRes: Int, onItemClick: (MenuItem) -> Boolean) {
fun View.showPopupMenu(@MenuRes menuRes: Int, onPrepare:((Menu) -> Unit)? = null, onItemClick: (MenuItem) -> Boolean) {
val menu = PopupMenu(context, this)
menu.inflate(menuRes)
menu.setOnMenuItemClickListener(onItemClick)
onPrepare?.invoke(menu.menu)
menu.show()
}
fun ViewGroup.hitTest(x: Int, y: Int): View? {
val rect = Rect()
for (child in children) {
if (child.getGlobalVisibleRect(rect)) {
if (rect.contains(x, y)) {
return if (child is ViewGroup) {
child.hitTest(x, y)
} else {
child
}
}
}
}
return null
}
fun View.hasGlobalPoint(x: Int, y: Int): Boolean {
if (visibility != View.VISIBLE) {
return false
}
val rect = Rect()
getGlobalVisibleRect(rect)
return rect.contains(x, y)
}

View File

@@ -0,0 +1,11 @@
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="@android:color/white"
android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" />
</vector>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#000"
android:pathData="M21.41,11.58L12.41,2.58C12.05,2.22 11.55,2 11,2H4A2,2 0 0,0 2,4V11C2,11.55 2.22,12.05 2.59,12.42L11.59,21.42C11.95,21.78 12.45,22 13,22C13.55,22 14.05,21.78 14.41,21.41L21.41,14.41C21.78,14.05 22,13.55 22,13C22,12.45 21.77,11.94 21.41,11.58M5.5,7A1.5,1.5 0 0,1 4,5.5A1.5,1.5 0 0,1 5.5,4A1.5,1.5 0 0,1 7,5.5A1.5,1.5 0 0,1 5.5,7M17.27,15.27L13,19.54L8.73,15.27C8.28,14.81 8,14.19 8,13.5A2.5,2.5 0 0,1 10.5,11C11.19,11 11.82,11.28 12.27,11.74L13,12.46L13.73,11.73C14.18,11.28 14.81,11 15.5,11A2.5,2.5 0 0,1 18,13.5C18,14.19 17.72,14.82 17.27,15.27Z" />
</vector>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#000"
android:pathData="M4,2A2,2 0 0,0 2,4V11C2,11.55 2.22,12.05 2.59,12.42L11.59,21.42C11.95,21.78 12.45,22 13,22C13.55,22 14.05,21.78 14.41,21.41L21.41,14.41C21.78,14.05 22,13.55 22,13C22,12.45 21.77,11.94 21.41,11.58L12.41,2.58C12.05,2.22 11.55,2 11,2H4V2M11,4L20,13L13,20L4,11V4H11V4H11M6.5,5A1.5,1.5 0 0,0 5,6.5A1.5,1.5 0 0,0 6.5,8A1.5,1.5 0 0,0 8,6.5A1.5,1.5 0 0,0 6.5,5M10.95,10.5C9.82,10.5 8.9,11.42 8.9,12.55C8.9,13.12 9.13,13.62 9.5,14L13,17.5L16.5,14C16.87,13.63 17.1,13.11 17.1,12.55A2.05,2.05 0 0,0 15.05,10.5C14.5,10.5 13.97,10.73 13.6,11.1L13,11.7L12.4,11.11C12.03,10.73 11.5,10.5 10.95,10.5Z" />
</vector>

View File

@@ -3,18 +3,23 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:paddingTop="12dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingTop="12dp">
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?android:listDivider" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView_categories"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
android:scrollbars="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/item_caegory_checkable" />
<View
@@ -27,11 +32,12 @@
android:layout_width="match_parent"
android:layout_height="?android:listPreferredItemHeightSmall"
android:background="?android:selectableItemBackground"
android:drawableEnd="@drawable/ic_add"
android:gravity="start|center_vertical"
android:paddingStart="?android:listPreferredItemPaddingStart"
android:paddingEnd="?android:listPreferredItemPaddingEnd"
android:text="@string/add_new_category"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
android:textColor="?android:textColorPrimary"
android:text="@string/add_new_category" />
android:textColor="?android:textColorPrimary" />
</LinearLayout>

View File

@@ -84,8 +84,8 @@
android:background="?selectableItemBackgroundBorderless"
android:contentDescription="@string/add_to_favourites"
android:scaleType="center"
android:src="@drawable/ic_favourites"
android:tint="?colorAccent"
android:src="@drawable/ic_tag_heart_outline"
app:tint="?colorAccent"
app:layout_constraintBottom_toBottomOf="@id/button_read"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintEnd_toStartOf="@id/button_read"