Merge branch 'feature/desktop-ui' into devel

This commit is contained in:
Koitharu
2022-03-16 18:08:50 +02:00
32 changed files with 678 additions and 457 deletions

View File

@@ -31,7 +31,7 @@ abstract class BaseActivity<B : ViewBinding> : AppCompatActivity(), OnApplyWindo
@Suppress("LeakingThis")
protected val exceptionResolver = ExceptionResolver(this)
private var lastInsets: Insets = Insets.NONE
private var lastInsets: Insets? = null
override fun onCreate(savedInstanceState: Bundle?) {
val settings = get<AppSettings>()

View File

@@ -42,7 +42,7 @@ abstract class BaseBottomSheet<B : ViewBinding> : BottomSheetDialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return if (resources.getBoolean(R.bool.is_tablet)) {
AppCompatDialog(context, theme)
AppCompatDialog(context, R.style.Theme_Kotatsu_Dialog)
} else super.onCreateDialog(savedInstanceState)
}

View File

@@ -8,6 +8,7 @@ import androidx.core.graphics.Insets
import androidx.core.view.OnApplyWindowInsetsListener
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.doOnNextLayout
import androidx.fragment.app.Fragment
import androidx.viewbinding.ViewBinding
import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
@@ -22,7 +23,7 @@ abstract class BaseFragment<B : ViewBinding> : Fragment(), OnApplyWindowInsetsLi
@Suppress("LeakingThis")
protected val exceptionResolver = ExceptionResolver(this)
private var lastInsets: Insets = Insets.NONE
private var lastInsets: Insets? = null
override fun onCreateView(
inflater: LayoutInflater,
@@ -36,12 +37,18 @@ abstract class BaseFragment<B : ViewBinding> : Fragment(), OnApplyWindowInsetsLi
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
lastInsets = Insets.NONE
ViewCompat.setOnApplyWindowInsetsListener(view, this)
view.doOnNextLayout {
// Listener may not be called
if (lastInsets == null) {
onWindowInsetsChanged(Insets.NONE)
}
}
}
override fun onDestroyView() {
viewBinding = null
lastInsets = null
super.onDestroyView()
}

View File

@@ -4,6 +4,7 @@ import android.app.ActivityOptions
import android.os.Bundle
import android.view.*
import android.widget.AdapterView
import android.widget.Spinner
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode
import androidx.core.graphics.Insets
@@ -24,6 +25,7 @@ import org.koitharu.kotatsu.download.ui.service.DownloadService
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.reader.ui.ReaderActivity
import org.koitharu.kotatsu.reader.ui.ReaderState
import org.koitharu.kotatsu.utils.RecyclerViewScrollCallback
class ChaptersFragment : BaseFragment<FragmentChaptersBinding>(),
OnListItemClickListener<ChapterListItem>,
@@ -56,21 +58,9 @@ class ChaptersFragment : BaseFragment<FragmentChaptersBinding>(),
setHasFixedSize(true)
adapter = chaptersAdapter
}
val branchesAdapter = BranchesAdapter()
binding.spinnerBranches.adapter = branchesAdapter
binding.spinnerBranches.onItemSelectedListener = this
binding.spinnerBranches?.let(::initSpinner)
viewModel.isLoading.observe(viewLifecycleOwner, this::onLoadingStateChanged)
viewModel.chapters.observe(viewLifecycleOwner, this::onChaptersChanged)
viewModel.branches.observe(viewLifecycleOwner) {
branchesAdapter.setItems(it)
binding.spinnerBranches.isVisible = it.size > 1
}
viewModel.selectedBranchIndex.observe(viewLifecycleOwner) {
if (it != -1 && it != binding.spinnerBranches.selectedItemPosition) {
binding.spinnerBranches.setSelection(it)
}
}
viewModel.isChaptersReversed.observe(viewLifecycleOwner) {
activity?.invalidateOptionsMenu()
}
@@ -79,7 +69,7 @@ class ChaptersFragment : BaseFragment<FragmentChaptersBinding>(),
override fun onDestroyView() {
chaptersAdapter = null
selectionDecoration = null
binding.spinnerBranches.adapter = null
binding.spinnerBranches?.adapter = null
super.onDestroyView()
}
@@ -166,7 +156,8 @@ class ChaptersFragment : BaseFragment<FragmentChaptersBinding>(),
}
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
viewModel.setSelectedBranch(binding.spinnerBranches.selectedItem as String?)
val spinner = binding.spinnerBranches ?: return
viewModel.setSelectedBranch(spinner.selectedItem as String?)
}
override fun onNothingSelected(parent: AdapterView<*>?) = Unit
@@ -201,14 +192,37 @@ class ChaptersFragment : BaseFragment<FragmentChaptersBinding>(),
override fun onWindowInsetsChanged(insets: Insets) {
binding.recyclerViewChapters.updatePadding(
left = insets.left,
right = insets.right,
bottom = insets.bottom + binding.spinnerBranches.height
bottom = insets.bottom + (binding.spinnerBranches?.height ?: 0),
)
}
private fun initSpinner(spinner: Spinner) {
val branchesAdapter = BranchesAdapter()
spinner.adapter = branchesAdapter
spinner.onItemSelectedListener = this
viewModel.branches.observe(viewLifecycleOwner) {
branchesAdapter.setItems(it)
spinner.isVisible = it.size > 1
}
viewModel.selectedBranchIndex.observe(viewLifecycleOwner) {
if (it != -1 && it != spinner.selectedItemPosition) {
spinner.setSelection(it)
}
}
}
private fun onChaptersChanged(list: List<ChapterListItem>) {
chaptersAdapter?.items = list
val adapter = chaptersAdapter ?: return
if (adapter.itemCount == 0) {
val position = list.indexOfFirst { it.hasFlag(ChapterListItem.FLAG_CURRENT) } - 1
if (position > 0) {
adapter.setItems(list, RecyclerViewScrollCallback(binding.recyclerViewChapters, position))
} else {
adapter.items = list
}
} else {
adapter.items = list
}
}
private fun onLoadingStateChanged(isLoading: Boolean) {

View File

@@ -6,25 +6,26 @@ import android.net.Uri
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.widget.AdapterView
import android.widget.Spinner
import android.widget.Toast
import androidx.appcompat.view.ActionMode
import androidx.appcompat.widget.Toolbar
import androidx.core.content.pm.ShortcutManagerCompat
import androidx.core.graphics.Insets
import androidx.core.net.toFile
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
import androidx.core.view.updatePadding
import androidx.lifecycle.lifecycleScope
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator
import kotlinx.coroutines.launch
import org.koin.android.ext.android.get
import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koin.core.parameter.parametersOf
import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.domain.MangaIntent
import org.koitharu.kotatsu.base.ui.BaseActivity
@@ -34,6 +35,7 @@ import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
import org.koitharu.kotatsu.core.os.ShortcutsRepository
import org.koitharu.kotatsu.databinding.ActivityDetailsBinding
import org.koitharu.kotatsu.details.ui.adapter.BranchesAdapter
import org.koitharu.kotatsu.download.ui.service.DownloadService
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaSource
@@ -43,8 +45,8 @@ import org.koitharu.kotatsu.search.ui.global.GlobalSearchActivity
import org.koitharu.kotatsu.utils.ShareHelper
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
class DetailsActivity : BaseActivity<ActivityDetailsBinding>(),
TabLayoutMediator.TabConfigurationStrategy {
class DetailsActivity : BaseActivity<ActivityDetailsBinding>(), TabLayoutMediator.TabConfigurationStrategy,
AdapterView.OnItemSelectedListener {
private val viewModel by viewModel<DetailsViewModel> {
parametersOf(MangaIntent(intent))
@@ -53,9 +55,16 @@ class DetailsActivity : BaseActivity<ActivityDetailsBinding>(),
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(ActivityDetailsBinding.inflate(layoutInflater))
supportActionBar?.setDisplayHomeAsUpEnabled(true)
binding.pager.adapter = MangaDetailsAdapter(this)
TabLayoutMediator(binding.tabs, binding.pager, this).attach()
supportActionBar?.run {
setDisplayHomeAsUpEnabled(true)
setDisplayShowTitleEnabled(false)
}
val pager = binding.pager
if (pager != null) {
pager.adapter = MangaDetailsAdapter(this)
TabLayoutMediator(checkNotNull(binding.tabs), pager, this).attach()
}
binding.spinnerBranches?.let(::initSpinner)
viewModel.manga.observe(this, ::onMangaUpdated)
viewModel.newChaptersCount.observe(this, ::onNewChaptersChanged)
@@ -96,25 +105,17 @@ class DetailsActivity : BaseActivity<ActivityDetailsBinding>(),
binding.snackbar.updatePadding(
bottom = insets.bottom
)
with(binding.toolbar) {
updatePadding(
left = insets.left,
right = insets.right
)
updateLayoutParams<ViewGroup.MarginLayoutParams> {
topMargin = insets.top
}
}
if (binding.tabs.parent !is Toolbar) {
binding.tabs.updatePadding(
left = insets.left,
right = insets.right
)
binding.toolbar.updateLayoutParams<ViewGroup.MarginLayoutParams> {
topMargin = insets.top
}
binding.root.updatePadding(
left = insets.left,
right = insets.right
)
}
private fun onNewChaptersChanged(newChapters: Int) {
val tab = binding.tabs.getTabAt(1) ?: return
val tab = binding.tabs?.getTabAt(1) ?: return
if (newChapters == 0) {
tab.removeBadge()
} else {
@@ -208,11 +209,7 @@ class DetailsActivity : BaseActivity<ActivityDetailsBinding>(),
viewModel.manga.value?.let {
lifecycleScope.launch {
if (!get<ShortcutsRepository>().requestPinShortcut(it)) {
Snackbar.make(
binding.pager,
R.string.operation_not_supported,
Snackbar.LENGTH_SHORT
).show()
binding.snackbar.show(getString(R.string.operation_not_supported))
}
}
}
@@ -231,19 +228,25 @@ class DetailsActivity : BaseActivity<ActivityDetailsBinding>(),
override fun onSupportActionModeStarted(mode: ActionMode) {
super.onSupportActionModeStarted(mode)
binding.pager.isUserInputEnabled = false
binding.pager?.isUserInputEnabled = false
}
override fun onSupportActionModeFinished(mode: ActionMode) {
super.onSupportActionModeFinished(mode)
binding.pager.isUserInputEnabled = true
binding.pager?.isUserInputEnabled = true
}
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
val spinner = binding.spinnerBranches ?: return
viewModel.setSelectedBranch(spinner.selectedItem as String?)
}
override fun onNothingSelected(parent: AdapterView<*>?) = Unit
fun showChapterMissingDialog(chapterId: Long) {
val remoteManga = viewModel.getRemoteManga()
if (remoteManga == null) {
Snackbar.make(binding.pager, R.string.chapter_is_missing, Snackbar.LENGTH_LONG)
.show()
binding.snackbar.show(getString( R.string.chapter_is_missing))
return
}
MaterialAlertDialogBuilder(this).apply {
@@ -266,9 +269,22 @@ class DetailsActivity : BaseActivity<ActivityDetailsBinding>(),
}.show()
}
companion object {
private fun initSpinner(spinner: Spinner) {
val branchesAdapter = BranchesAdapter()
spinner.adapter = branchesAdapter
spinner.onItemSelectedListener = this
viewModel.branches.observe(this) {
branchesAdapter.setItems(it)
spinner.isVisible = it.size > 1
}
viewModel.selectedBranchIndex.observe(this) {
if (it != -1 && it != spinner.selectedItemPosition) {
spinner.setSelection(it)
}
}
}
const val ACTION_MANGA_VIEW = "${BuildConfig.APPLICATION_ID}.action.VIEW_MANGA"
companion object {
fun newIntent(context: Context, manga: Manga): Intent {
return Intent(context, DetailsActivity::class.java)

View File

@@ -243,9 +243,7 @@ class DetailsFragment : BaseFragment<FragmentDetailsBinding>(), View.OnClickList
override fun onWindowInsetsChanged(insets: Insets) {
binding.root.updatePadding(
left = insets.left,
right = insets.right,
bottom = insets.bottom
bottom = insets.bottom,
)
}

View File

@@ -176,17 +176,19 @@ abstract class MangaListFragment : BaseFragment<FragmentListBinding>(),
val headerHeight = (activity as? AppBarOwner)?.appBar?.measureHeight() ?: insets.top
binding.root.updatePadding(
left = insets.left,
right = insets.right
right = insets.right,
)
if (activity is MainActivity) {
val topOffsetDiff = binding.recyclerView.paddingTop - headerHeight
binding.recyclerView.updatePadding(
top = headerHeight,
bottom = insets.bottom
bottom = insets.bottom,
)
binding.recyclerView.scrollBy(0, topOffsetDiff)
binding.swipeRefreshLayout.setProgressViewOffset(
true,
headerHeight + resources.resolveDp(-72),
headerHeight + resources.resolveDp(10)
headerHeight + resources.resolveDp(10),
)
}
}

View File

@@ -10,13 +10,15 @@ import android.view.ViewGroup.MarginLayoutParams
import androidx.appcompat.app.ActionBarDrawerToggle
import androidx.core.content.ContextCompat
import androidx.core.graphics.Insets
import androidx.core.view.*
import androidx.core.view.GravityCompat
import androidx.core.view.ViewCompat
import androidx.core.view.updateLayoutParams
import androidx.core.view.updatePadding
import androidx.drawerlayout.widget.DrawerLayout
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentTransaction
import androidx.fragment.app.commit
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.CircularProgressDrawable
import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.dialog.MaterialAlertDialogBuilder
@@ -55,10 +57,7 @@ import org.koitharu.kotatsu.suggestions.ui.SuggestionsFragment
import org.koitharu.kotatsu.suggestions.ui.SuggestionsWorker
import org.koitharu.kotatsu.tracker.ui.FeedFragment
import org.koitharu.kotatsu.tracker.work.TrackWorker
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
import org.koitharu.kotatsu.utils.ext.hideKeyboard
import org.koitharu.kotatsu.utils.ext.measureHeight
import org.koitharu.kotatsu.utils.ext.resolveDp
import org.koitharu.kotatsu.utils.ext.*
private const val TAG_PRIMARY = "primary"
private const val TAG_SEARCH = "search"
@@ -71,7 +70,8 @@ class MainActivity : BaseActivity<ActivityMainBinding>(),
private val searchSuggestionViewModel by viewModel<SearchSuggestionViewModel>()
private lateinit var navHeaderBinding: NavigationHeaderBinding
private lateinit var drawerToggle: ActionBarDrawerToggle
private var drawerToggle: ActionBarDrawerToggle? = null
private var drawer: DrawerLayout? = null
override val appBar: AppBarLayout
get() = binding.appbar
@@ -80,36 +80,35 @@ class MainActivity : BaseActivity<ActivityMainBinding>(),
super.onCreate(savedInstanceState)
setContentView(ActivityMainBinding.inflate(layoutInflater))
navHeaderBinding = NavigationHeaderBinding.inflate(layoutInflater)
drawerToggle = ActionBarDrawerToggle(
this,
binding.drawer,
binding.toolbar,
R.string.open_menu,
R.string.close_menu
)
drawerToggle.setHomeAsUpIndicator(ContextCompat.getDrawable(this, R.drawable.ic_arrow_back))
drawerToggle.setToolbarNavigationClickListener {
binding.searchView.hideKeyboard()
onBackPressed()
drawer = binding.root as? DrawerLayout
drawerToggle = drawer?.let {
ActionBarDrawerToggle(
this,
it,
binding.toolbar,
R.string.open_menu,
R.string.close_menu
).apply {
setHomeAsUpIndicator(ContextCompat.getDrawable(this@MainActivity, R.drawable.ic_arrow_back))
setToolbarNavigationClickListener {
binding.searchView.hideKeyboard()
onBackPressed()
}
it.addDrawerListener(this)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
}
}
binding.drawer.addDrawerListener(drawerToggle)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
with(binding.searchView) {
onFocusChangeListener = this@MainActivity
searchSuggestionListener = this@MainActivity
if (drawer == null) {
drawableStart = ContextCompat.getDrawable(context, R.drawable.ic_search)
}
}
with(binding.navigationView) {
val menuView =
findViewById<RecyclerView>(com.google.android.material.R.id.design_navigation_view)
ViewCompat.setOnApplyWindowInsetsListener(navHeaderBinding.root) { v, insets ->
val systemWindowInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.updatePadding(top = systemWindowInsets.top)
// NavigationView doesn't dispatch insets to the menu view, so pad the bottom here.
menuView.updatePadding(bottom = systemWindowInsets.bottom)
insets
}
ViewCompat.setOnApplyWindowInsetsListener(this, NavigationViewInsetsListener())
addHeaderView(navHeaderBinding.root)
setNavigationItemSelectedListener(this@MainActivity)
}
@@ -134,26 +133,27 @@ class MainActivity : BaseActivity<ActivityMainBinding>(),
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
super.onRestoreInstanceState(savedInstanceState)
drawerToggle.isDrawerIndicatorEnabled =
binding.drawer.getDrawerLockMode(GravityCompat.START) == DrawerLayout.LOCK_MODE_UNLOCKED
drawerToggle?.isDrawerIndicatorEnabled =
drawer?.getDrawerLockMode(GravityCompat.START) == DrawerLayout.LOCK_MODE_UNLOCKED
}
override fun onPostCreate(savedInstanceState: Bundle?) {
super.onPostCreate(savedInstanceState)
drawerToggle.syncState()
drawerToggle?.syncState()
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
drawerToggle.onConfigurationChanged(newConfig)
drawerToggle?.onConfigurationChanged(newConfig)
}
override fun onBackPressed() {
val fragment = supportFragmentManager.findFragmentByTag(TAG_SEARCH)
binding.searchView.clearFocus()
when {
binding.drawer.isDrawerOpen(binding.navigationView) -> binding.drawer.closeDrawer(
binding.navigationView)
drawer?.isDrawerOpen(binding.navigationView) == true -> {
drawer?.closeDrawer(binding.navigationView)
}
fragment != null -> supportFragmentManager.commit {
remove(fragment)
setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
@@ -164,7 +164,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>(),
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return drawerToggle.onOptionsItemSelected(item) || when (item.itemId) {
return drawerToggle?.onOptionsItemSelected(item) == true || when (item.itemId) {
else -> super.onOptionsItemSelected(item)
}
}
@@ -210,19 +210,22 @@ class MainActivity : BaseActivity<ActivityMainBinding>(),
else -> return false
}
}
binding.drawer.closeDrawers()
drawer?.closeDrawers()
appBar.setExpanded(true)
return true
}
override fun onWindowInsetsChanged(insets: Insets) {
binding.toolbarCard.updateLayoutParams<MarginLayoutParams> {
topMargin = insets.top + resources.resolveDp(8)
}
binding.fab.updateLayoutParams<MarginLayoutParams> {
bottomMargin = insets.bottom + topMargin
leftMargin = insets.left + topMargin
rightMargin = insets.right + topMargin
}
binding.toolbarCard.updateLayoutParams<MarginLayoutParams> {
topMargin = insets.top + bottomMargin
}
binding.root.updatePadding(
left = insets.left,
right = insets.right,
)
binding.container.updateLayoutParams<MarginLayoutParams> {
topMargin = -(binding.appbar.measureHeight())
}
@@ -360,14 +363,14 @@ class MainActivity : BaseActivity<ActivityMainBinding>(),
}
private fun onSearchOpened() {
binding.drawer.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
drawerToggle.isDrawerIndicatorEnabled = false
drawer?.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
drawerToggle?.isDrawerIndicatorEnabled = false
adjustFabVisibility(isSearchOpened = true)
}
private fun onSearchClosed() {
binding.drawer.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED)
drawerToggle.isDrawerIndicatorEnabled = true
drawer?.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED)
drawerToggle?.isDrawerIndicatorEnabled = true
adjustFabVisibility(isSearchOpened = false)
}

View File

@@ -0,0 +1,24 @@
package org.koitharu.kotatsu.main.ui
import android.view.View
import androidx.core.view.OnApplyWindowInsetsListener
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding
import java.lang.ref.WeakReference
import com.google.android.material.R as materialR
class NavigationViewInsetsListener : OnApplyWindowInsetsListener {
private var menuViewRef: WeakReference<View>? = null
override fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat {
val menuView = menuViewRef?.get() ?: v.findViewById<View>(materialR.id.design_navigation_view).also {
menuViewRef = WeakReference(it)
}
val systemWindowInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.updatePadding(top = systemWindowInsets.top)
// NavigationView doesn't dispatch insets to the menu view, so pad the bottom here.
menuView.updatePadding(bottom = systemWindowInsets.bottom)
return WindowInsetsCompat.CONSUMED
}
}

View File

@@ -5,7 +5,6 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.FragmentManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.divider.MaterialDividerItemDecoration
import org.koin.android.ext.android.get
@@ -20,6 +19,7 @@ import org.koitharu.kotatsu.details.ui.model.ChapterListItem
import org.koitharu.kotatsu.details.ui.model.toListItem
import org.koitharu.kotatsu.parsers.model.MangaChapter
import org.koitharu.kotatsu.utils.BottomSheetToolbarController
import org.koitharu.kotatsu.utils.RecyclerViewScrollCallback
import org.koitharu.kotatsu.utils.ext.withArgs
class ChaptersBottomSheet : BaseBottomSheet<SheetChaptersBinding>(), OnListItemClickListener<ChapterListItem> {
@@ -59,7 +59,7 @@ class ChaptersBottomSheet : BaseBottomSheet<SheetChaptersBinding>(), OnListItemC
binding.recyclerView.adapter = ChaptersAdapter(this).also { adapter ->
if (currentPosition >= 0) {
val targetPosition = (currentPosition - 1).coerceAtLeast(0)
adapter.setItems(items, Scroller(binding.recyclerView, targetPosition))
adapter.setItems(items, RecyclerViewScrollCallback(binding.recyclerView, targetPosition))
} else {
adapter.items = items
}
@@ -78,13 +78,6 @@ class ChaptersBottomSheet : BaseBottomSheet<SheetChaptersBinding>(), OnListItemC
fun onChapterChanged(chapter: MangaChapter)
}
private class Scroller(private val recyclerView: RecyclerView, private val position: Int) : Runnable {
override fun run() {
val offset = recyclerView.resources.getDimensionPixelSize(R.dimen.chapter_list_item_height) / 2
(recyclerView.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(position, offset)
}
}
companion object {
private const val ARG_CHAPTERS = "chapters"

View File

@@ -243,7 +243,7 @@ class ReaderActivity : BaseFullscreenActivity<ActivityReaderBinding>(),
rawX >= binding.root.width - gestureInsets.right ||
rawY >= binding.root.height - gestureInsets.bottom ||
binding.appbarTop.hasGlobalPoint(rawX, rawY) ||
binding.appbarBottom.hasGlobalPoint(rawX, rawY)
binding.appbarBottom?.hasGlobalPoint(rawX, rawY) == true
) {
false
} else {
@@ -320,11 +320,13 @@ class ReaderActivity : BaseFullscreenActivity<ActivityReaderBinding>(),
if (binding.appbarTop.isVisible != isUiVisible) {
val transition = TransitionSet()
.setOrdering(TransitionSet.ORDERING_TOGETHER)
.addTransition(Slide(Gravity.BOTTOM).addTarget(binding.appbarBottom))
.addTransition(Slide(Gravity.TOP).addTarget(binding.appbarTop))
binding.appbarBottom?.let { botomBar ->
transition.addTransition(Slide(Gravity.BOTTOM).addTarget(botomBar))
}
TransitionManager.beginDelayedTransition(binding.root, transition)
binding.appbarTop.isVisible = isUiVisible
binding.appbarBottom.isVisible = isUiVisible
binding.appbarBottom?.isVisible = isUiVisible
if (isUiVisible) {
showSystemUI()
} else {
@@ -341,7 +343,7 @@ class ReaderActivity : BaseFullscreenActivity<ActivityReaderBinding>(),
right = systemBars.right,
left = systemBars.left
)
binding.appbarBottom.updatePadding(
binding.appbarBottom?.updatePadding(
bottom = systemBars.bottom,
right = systemBars.right,
left = systemBars.left

View File

@@ -6,7 +6,7 @@ import android.view.View
import android.view.ViewGroup
import androidx.appcompat.widget.Toolbar
import androidx.fragment.app.FragmentManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.GridLayoutManager
import com.google.android.material.bottomsheet.BottomSheetBehavior
import org.koin.android.ext.android.get
import org.koitharu.kotatsu.R
@@ -29,6 +29,7 @@ class PagesThumbnailsSheet : BaseBottomSheet<SheetPagesBinding>(),
private lateinit var thumbnails: List<PageThumbnail>
private val spanResolver = MangaListSpanResolver()
private var currentPageIndex = -1
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -37,12 +38,12 @@ class PagesThumbnailsSheet : BaseBottomSheet<SheetPagesBinding>(),
dismissAllowingStateLoss()
return
}
val current = arguments?.getInt(ARG_CURRENT, -1) ?: -1
currentPageIndex = requireArguments().getInt(ARG_CURRENT, currentPageIndex)
val repository = MangaRepository(pages.first().source)
thumbnails = pages.mapIndexed { i, x ->
PageThumbnail(
number = i + 1,
isCurrent = i == current,
isCurrent = i == currentPageIndex,
repository = repository,
page = x
)
@@ -69,11 +70,9 @@ class PagesThumbnailsSheet : BaseBottomSheet<SheetPagesBinding>(),
resources.getQuantityString(R.plurals.pages, thumbnails.size, thumbnails.size)
}
val initialTopPosition = binding.recyclerView.top
with(binding.recyclerView) {
addItemDecoration(
SpacingItemDecoration(view.resources.getDimensionPixelOffset(R.dimen.grid_spacing))
SpacingItemDecoration(resources.getDimensionPixelOffset(R.dimen.grid_spacing))
)
adapter = PageThumbnailAdapter(
thumbnails,
@@ -82,16 +81,12 @@ class PagesThumbnailsSheet : BaseBottomSheet<SheetPagesBinding>(),
get(),
this@PagesThumbnailsSheet
)
addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) = Unit
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
binding.appbar.isLifted = getChildAt(0).top < initialTopPosition
}
})
addOnLayoutChangeListener(spanResolver)
spanResolver.setGridSize(get<AppSettings>().gridSize / 100f, this)
if (currentPageIndex > 0) {
val offset = resources.getDimensionPixelOffset(R.dimen.preferred_grid_width)
(layoutManager as GridLayoutManager).scrollToPositionWithOffset(currentPageIndex, offset)
}
}
}

View File

@@ -14,6 +14,8 @@ import org.koitharu.kotatsu.parsers.model.MangaPage
import org.koitharu.kotatsu.reader.ui.thumbnails.PageThumbnail
import org.koitharu.kotatsu.utils.ext.IgnoreErrors
import org.koitharu.kotatsu.utils.ext.referer
import org.koitharu.kotatsu.utils.ext.setTextColorAttr
import com.google.android.material.R as materialR
fun pageThumbnailAD(
coil: ImageLoader,
@@ -31,7 +33,7 @@ fun pageThumbnailAD(
height = (gridWidth * 13f / 18f).toInt()
)
binding.handle.setOnClickListener {
binding.root.setOnClickListener {
clickListener.onItemClick(item.page, itemView)
}
@@ -39,7 +41,8 @@ fun pageThumbnailAD(
job?.cancel()
binding.imageViewThumb.setImageDrawable(null)
with(binding.textViewNumber) {
setBackgroundResource(if (item.isCurrent) R.drawable.bg_badge_accent else R.drawable.bg_badge_default)
setBackgroundResource(if (item.isCurrent) R.drawable.bg_badge_accent else R.drawable.bg_badge_empty)
setTextColorAttr(if (item.isCurrent) materialR.attr.colorOnTertiary else android.R.attr.textColorPrimary)
text = (item.number).toString()
}
job = scope.launch(Dispatchers.Default + IgnoreErrors) {

View File

@@ -15,7 +15,7 @@ import org.koitharu.kotatsu.databinding.ItemSearchSuggestionMangaGridBinding
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionListener
import org.koitharu.kotatsu.search.ui.suggestion.model.SearchSuggestionItem
import org.koitharu.kotatsu.utils.ScrollResetCallback
import org.koitharu.kotatsu.utils.RecyclerViewScrollCallback
import org.koitharu.kotatsu.utils.ext.enqueueWith
import org.koitharu.kotatsu.utils.ext.newImageRequest
@@ -37,7 +37,7 @@ fun searchSuggestionMangaListAD(
right = recyclerView.paddingRight - spacing,
)
recyclerView.addItemDecoration(SpacingItemDecoration(spacing))
val scrollResetCallback = ScrollResetCallback(recyclerView)
val scrollResetCallback = RecyclerViewScrollCallback(recyclerView, 0)
bind {
adapter.setItems(item.items, scrollResetCallback)

View File

@@ -0,0 +1,21 @@
package org.koitharu.kotatsu.utils
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import java.lang.ref.WeakReference
class RecyclerViewScrollCallback(recyclerView: RecyclerView, private val position: Int) : Runnable {
private val recyclerViewRef = WeakReference(recyclerView)
override fun run() {
val rv = recyclerViewRef.get() ?: return
val lm = rv.layoutManager ?: return
rv.stopScroll()
if (lm is LinearLayoutManager) {
lm.scrollToPositionWithOffset(position, 0)
} else {
lm.scrollToPosition(position)
}
}
}

View File

@@ -1,13 +0,0 @@
package org.koitharu.kotatsu.utils
import androidx.recyclerview.widget.RecyclerView
import java.lang.ref.WeakReference
class ScrollResetCallback(recyclerView: RecyclerView) : Runnable {
private val recyclerViewRef = WeakReference(recyclerView)
override fun run() {
recyclerViewRef.get()?.scrollToPosition(0)
}
}

View File

@@ -0,0 +1,20 @@
package org.koitharu.kotatsu.utils.ext
import android.view.View
import androidx.core.graphics.Insets
fun Insets.getStart(view: View): Int {
return if (view.layoutDirection == View.LAYOUT_DIRECTION_RTL) {
right
} else {
left
}
}
fun Insets.getEnd(view: View): Int {
return if (view.layoutDirection == View.LAYOUT_DIRECTION_RTL) {
left
} else {
right
}
}

View File

@@ -3,7 +3,9 @@ package org.koitharu.kotatsu.utils.ext
import android.graphics.drawable.Drawable
import android.view.View
import android.widget.TextView
import androidx.annotation.AttrRes
import androidx.annotation.StringRes
import androidx.core.content.res.use
import androidx.core.view.isGone
var TextView.textAndVisible: CharSequence?
@@ -35,4 +37,11 @@ fun TextView.setTextAndVisible(@StringRes textResId: Int) {
setText(textResId)
isGone = text.isNullOrEmpty()
}
}
fun TextView.setTextColorAttr(@AttrRes attrResId: Int) {
val colors = context.obtainStyledAttributes(intArrayOf(attrResId)).use {
it.getColorStateList(0)
}
setTextColor(colors)
}

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="5dp" />
<solid android:color="?colorBackgroundFloating" />
<stroke
android:width="1dp"
android:color="?colorOutline" />
<padding
android:bottom="2dp"
android:left="2dp"
android:right="2dp"
android:top="2dp" />
</shape>

View File

@@ -0,0 +1,81 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
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:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
android:orientation="vertical">
<org.koitharu.kotatsu.base.ui.widgets.CheckableButtonGroup
android:id="@+id/checkableGroup"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:orientation="horizontal"
android:weightSum="3">
<com.google.android.material.button.MaterialButton
android:id="@+id/button_list"
style="@style/Widget.Kotatsu.ToggleButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:singleLine="true"
android:ellipsize="end"
android:text="@string/list"
app:icon="@drawable/ic_list" />
<com.google.android.material.button.MaterialButton
android:id="@+id/button_list_detailed"
style="@style/Widget.Kotatsu.ToggleButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="12dp"
android:layout_weight="1"
android:singleLine="true"
android:ellipsize="end"
android:text="@string/detailed_list"
app:icon="@drawable/ic_list_detailed" />
<com.google.android.material.button.MaterialButton
android:id="@+id/button_grid"
style="@style/Widget.Kotatsu.ToggleButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:singleLine="true"
android:ellipsize="end"
android:text="@string/grid"
app:icon="@drawable/ic_grid" />
</org.koitharu.kotatsu.base.ui.widgets.CheckableButtonGroup>
<TextView
android:id="@+id/textView_grid_title"
style="?materialAlertDialogTitleTextStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="?attr/dialogPreferredPadding"
android:paddingRight="?attr/dialogPreferredPadding"
android:singleLine="true"
android:text="@string/grid_size"
android:visibility="gone"
tools:visibility="visible" />
<com.google.android.material.slider.Slider
android:id="@+id/slider_grid"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:stepSize="5"
android:valueFrom="50"
android:valueTo="150"
android:visibility="gone"
app:labelBehavior="floating"
app:tickVisible="false"
tools:value="100"
tools:visibility="visible" />
</LinearLayout>

View File

@@ -1,198 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView
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:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:scrollbars="vertical"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<org.koitharu.kotatsu.base.ui.widgets.CoverImageView
android:id="@+id/imageView_cover"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:foreground="?selectableItemBackground"
android:scaleType="centerCrop"
android:transitionName="cover"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_percent="0.3"
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Kotatsu.Cover"
tools:background="@tools:sample/backgrounds/scenic"
tools:ignore="ContentDescription,UnusedAttribute" />
<TextView
android:id="@+id/textView_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:ellipsize="end"
android:maxLines="4"
android:textAppearance="?attr/textAppearanceHeadlineSmall"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/imageView_cover"
app:layout_constraintTop_toTopOf="parent"
tools:text="@tools:sample/lorem[15]" />
<TextView
android:id="@+id/textView_subtitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="6dp"
android:layout_marginEnd="16dp"
android:ellipsize="end"
android:maxLines="4"
android:textAppearance="?attr/textAppearanceBodyMedium"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/imageView_cover"
app:layout_constraintTop_toBottomOf="@id/textView_title"
tools:text="@tools:sample/lorem[12]" />
<TextView
android:id="@+id/textView_author"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginTop="2dp"
android:layout_marginEnd="12dp"
android:background="@drawable/list_selector"
android:padding="4dp"
android:singleLine="true"
android:textColor="?attr/colorTertiary"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toEndOf="@id/imageView_cover"
app:layout_constraintTop_toBottomOf="@id/textView_subtitle"
app:layout_constraintWidth_default="wrap"
tools:text="@tools:sample/full_names" />
<TextView
android:id="@+id/textView_state"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="16dp"
android:drawablePadding="4dp"
android:singleLine="true"
android:textAppearance="?attr/textAppearanceBodySmall"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/imageView_cover"
app:layout_constraintTop_toBottomOf="@id/textView_author"
tools:drawableStart="@drawable/ic_state_finished"
tools:text="Finished" />
<include
android:id="@+id/info_layout"
layout="@layout/layout_details_info"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/imageView_cover"
app:layout_constraintTop_toBottomOf="@id/textView_state" />
<com.google.android.material.button.MaterialButton
android:id="@+id/button_favorite"
style="@style/Widget.Material3.Button.OutlinedButton"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="16dp"
android:paddingStart="0dp"
android:paddingEnd="0dp"
app:icon="@drawable/ic_heart_outline"
app:iconGravity="textTop"
app:iconPadding="0dp"
app:layout_constraintBottom_toBottomOf="@id/button_read"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintStart_toEndOf="@id/imageView_cover"
app:layout_constraintTop_toTopOf="@id/button_read" />
<com.google.android.material.button.MaterialButton
android:id="@+id/button_read"
style="@style/Widget.Material3.Button.TonalButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:enabled="false"
android:text="@string/read"
android:textAllCaps="false"
app:iconGravity="textStart"
app:iconPadding="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/button_favorite"
app:layout_constraintTop_toBottomOf="@id/info_layout"
tools:text="@string/_continue" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/barrier_header"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:barrierDirection="bottom"
app:barrierMargin="8dp"
app:constraint_referenced_ids="imageView_cover,button_read" />
<org.koitharu.kotatsu.base.ui.widgets.ChipsView
android:id="@+id/chips_tags"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:paddingStart="16dp"
android:paddingEnd="16dp"
app:chipSpacingHorizontal="6dp"
app:chipSpacingVertical="6dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/barrier_header" />
<TextView
android:id="@+id/textView_description"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:lineSpacingMultiplier="1.2"
android:textAppearance="?attr/textAppearanceBodyMedium"
android:textIsSelectable="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/chips_tags"
tools:ignore="UnusedAttribute"
tools:text="@tools:sample/lorem/random[250]" />
<com.google.android.material.progressindicator.LinearProgressIndicator
android:id="@+id/progressBar"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:indeterminate="true"
android:visibility="gone"
app:hideAnimationBehavior="outward"
app:layout_constraintBottom_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:showAnimationBehavior="inward"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>

View File

@@ -1,49 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
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:id="@+id/coordinator"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".details.ui.DetailsActivity">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.appbar.MaterialToolbar
android:id="@id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_scrollFlags="scroll|enterAlways">
<com.google.android.material.tabs.TabLayout
android:id="@+id/tabs"
style="@style/Widget.Kotatsu.Tabs"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:background="@android:color/transparent"
app:tabGravity="center"
app:tabMode="fixed" />
</com.google.android.material.appbar.MaterialToolbar>
</com.google.android.material.appbar.AppBarLayout>
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" />
<org.koitharu.kotatsu.base.ui.widgets.FadingSnackbar
android:id="@+id/snackbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:visibility="gone" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@@ -0,0 +1,83 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
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:layout_width="match_parent"
android:layout_height="match_parent"
android:keepScreenOn="true">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<org.koitharu.kotatsu.reader.ui.ReaderToastView
android:id="@+id/toastView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center_horizontal"
android:layout_marginBottom="20dp"
android:background="@drawable/bg_reader_indicator"
android:drawablePadding="6dp"
android:singleLine="true"
android:textAppearance="?attr/textAppearanceBodySmall"
android:theme="@style/ThemeOverlay.Material3.Dark"
tools:text="@string/loading_" />
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar_top"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/dim"
android:elevation="0dp"
android:theme="@style/ThemeOverlay.Material3.Dark"
app:elevation="0dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<com.google.android.material.appbar.MaterialToolbar
android:id="@id/toolbar"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
app:popupTheme="@style/ThemeOverlay.Kotatsu" />
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar_bottom"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:popupTheme="@style/ThemeOverlay.Kotatsu" />
</LinearLayout>
</com.google.android.material.appbar.AppBarLayout>
<LinearLayout
android:id="@+id/layout_loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center_horizontal"
android:orientation="vertical">
<com.google.android.material.progressindicator.CircularProgressIndicator
android:id="@+id/progressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:indeterminate="true" />
<TextView
android:id="@+id/textView_loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/loading_"
android:textAppearance="?attr/textAppearanceBody2" />
</LinearLayout>
</FrameLayout>

View File

@@ -17,8 +17,8 @@
android:id="@+id/imageView_cover"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:foreground="?selectableItemBackground"
android:scaleType="centerCrop"
android:transitionName="cover"
@@ -35,9 +35,9 @@
android:id="@+id/textView_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:ellipsize="end"
android:maxLines="3"
android:textAppearance="?attr/textAppearanceHeadlineSmall"
@@ -50,9 +50,9 @@
android:id="@+id/textView_subtitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="6dp"
android:layout_marginEnd="16dp"
android:layout_marginStart="8dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="8dp"
android:ellipsize="end"
android:maxLines="3"
android:textAppearance="?attr/textAppearanceBodyMedium"
@@ -65,11 +65,11 @@
android:id="@+id/textView_author"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginStart="6dp"
android:layout_marginTop="2dp"
android:layout_marginEnd="12dp"
android:layout_marginEnd="6dp"
android:background="@drawable/list_selector"
android:padding="4dp"
android:padding="2dp"
android:singleLine="true"
android:textColor="?attr/colorTertiary"
android:textStyle="bold"
@@ -84,9 +84,9 @@
android:id="@+id/textView_state"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginStart="8dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="16dp"
android:layout_marginEnd="8dp"
android:drawablePadding="4dp"
android:singleLine="true"
android:textAppearance="?attr/textAppearanceBodySmall"
@@ -96,12 +96,19 @@
tools:drawableStart="@drawable/ic_state_finished"
tools:text="Finished" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/barrier_header"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:barrierDirection="bottom"
app:barrierMargin="8dp"
app:constraint_referenced_ids="imageView_cover,button_read" />
<include
android:id="@+id/info_layout"
layout="@layout/layout_details_info"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/imageView_cover"
app:layout_constraintTop_toBottomOf="@id/textView_state" />
@@ -128,7 +135,6 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:enabled="false"
android:text="@string/read"
@@ -140,14 +146,6 @@
app:layout_constraintTop_toBottomOf="@id/info_layout"
tools:text="@string/_continue" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/barrier_header"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:barrierDirection="bottom"
app:barrierMargin="8dp"
app:constraint_referenced_ids="imageView_cover,button_read" />
<org.koitharu.kotatsu.base.ui.widgets.ChipsView
android:id="@+id/chips_tags"
android:layout_width="0dp"

View File

@@ -0,0 +1,90 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
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:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<com.google.android.material.navigation.NavigationView
android:id="@+id/navigationView"
android:layout_width="260dp"
android:layout_height="match_parent"
android:fitsSystemWindows="false"
app:drawerLayoutCornerSize="0dp"
app:elevation="0dp"
app:menu="@menu/nav_drawer" />
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.fragment.app.FragmentContainerView
android:id="@id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
tools:layout="@layout/fragment_list" />
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:elevation="0dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
app:liftOnScroll="false">
<FrameLayout
android:id="@+id/toolbar_card"
android:layout_width="match_parent"
android:layout_height="54dp"
android:layout_marginVertical="8dp"
android:background="@drawable/toolbar_background">
<com.google.android.material.appbar.MaterialToolbar
android:id="@id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@null"
android:focusable="true"
android:focusableInTouchMode="true">
<org.koitharu.kotatsu.search.ui.widget.SearchEditText
android:id="@+id/searchView"
style="@style/Widget.Kotatsu.SearchView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@null"
android:drawablePadding="16dp"
android:gravity="center_vertical"
android:hint="@string/search_manga"
android:imeOptions="actionSearch"
android:importantForAutofill="no"
android:singleLine="true" />
</com.google.android.material.appbar.MaterialToolbar>
</FrameLayout>
</com.google.android.material.appbar.AppBarLayout>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:contentDescription="@string/_continue"
android:src="@drawable/ic_read_fill"
android:visibility="gone"
app:backgroundTint="?attr/colorContainer"
app:fabSize="normal"
app:layout_anchor="@id/container"
app:layout_anchorGravity="bottom|end"
app:layout_behavior="com.google.android.material.behavior.HideBottomViewOnScrollBehavior"
app:layout_dodgeInsetEdges="bottom"
tools:visibility="visible" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</LinearLayout>

View File

@@ -0,0 +1,79 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
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:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".details.ui.DetailsActivity">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:elevation="0dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:liftOnScroll="false">
<com.google.android.material.appbar.MaterialToolbar
android:id="@id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:theme="?attr/actionBarTheme">
<Spinner
android:id="@+id/spinner_branches"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:visibility="gone"
tools:listitem="@layout/item_branch"
tools:visibility="visible" />
</com.google.android.material.appbar.MaterialToolbar>
</com.google.android.material.appbar.AppBarLayout>
<androidx.fragment.app.FragmentContainerView
android:id="@+id/container_details"
android:name="org.koitharu.kotatsu.details.ui.DetailsFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/guideline"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/appbar"
tools:layout="@layout/fragment_details" />
<androidx.fragment.app.FragmentContainerView
android:id="@+id/container_chapters"
android:name="org.koitharu.kotatsu.details.ui.ChaptersFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintTop_toBottomOf="@id/appbar"
tools:layout="@layout/fragment_chapters" />
<org.koitharu.kotatsu.base.ui.widgets.FadingSnackbar
android:id="@+id/snackbar"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.6" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
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:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView_chapters"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:orientation="vertical"
app:fastScrollEnabled="true"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/item_chapter" />
<com.google.android.material.progressindicator.CircularProgressIndicator
android:id="@+id/progressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:indeterminate="true"
android:visibility="gone"
tools:visibility="visible" />
</FrameLayout>

View File

@@ -3,7 +3,6 @@
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:id="@+id/coordinator"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".details.ui.DetailsActivity">
@@ -12,24 +11,28 @@
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:liftOnScroll="false"
app:elevation="0dp">
app:elevation="0dp"
app:liftOnScroll="false">
<com.google.android.material.appbar.MaterialToolbar
android:id="@id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@drawable/m3_tabs_background"
android:theme="?attr/actionBarTheme"
app:layout_scrollFlags="scroll|enterAlways"/>
app:layout_scrollFlags="scroll|enterAlways"
tools:ignore="PrivateResource">
<com.google.android.material.tabs.TabLayout
android:id="@+id/tabs"
style="@style/Widget.Kotatsu.Tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabGravity="fill"
app:tabMaxWidth="0dp"
app:tabMode="fixed" />
<com.google.android.material.tabs.TabLayout
android:id="@+id/tabs"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center_horizontal"
android:background="@null"
app:tabGravity="center"
app:tabMode="scrollable" />
</com.google.android.material.appbar.MaterialToolbar>
</com.google.android.material.appbar.AppBarLayout>

View File

@@ -3,7 +3,6 @@
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:id="@+id/drawer"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".main.ui.MainActivity">
@@ -16,12 +15,15 @@
android:id="@id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:layout="@layout/fragment_list"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" />
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:background="@null"
android:stateListAnimator="@null">
@@ -29,10 +31,7 @@
android:id="@+id/toolbar_card"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="8dp"
android:layout_marginVertical="8dp"
android:background="@drawable/toolbar_background">
<com.google.android.material.appbar.MaterialToolbar
@@ -42,11 +41,7 @@
android:background="@null"
android:focusable="true"
android:focusableInTouchMode="true"
android:theme="@style/ThemeOverlay.Kotatsu.MainToolbar"
app:contentInsetStartWithNavigation="0dp"
app:titleTextAppearance="@style/TextAppearance.Kotatsu.PersistentToolbarTitle"
app:titleTextColor="?android:colorControlNormal"
tools:title="@string/app_name">
app:contentInsetStartWithNavigation="0dp">
<org.koitharu.kotatsu.search.ui.widget.SearchEditText
android:id="@+id/searchView"

View File

@@ -1,10 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
<com.google.android.material.card.MaterialCardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/scrimBackground">
xmlns:app="http://schemas.android.com/apk/res-auto"
style="@style/Widget.Material3.CardView.Outlined"
app:cardBackgroundColor="?scrimBackground">
<org.koitharu.kotatsu.base.ui.widgets.CoverImageView
android:id="@+id/imageView_thumb"
@@ -13,18 +15,12 @@
android:scaleType="centerCrop"
tools:src="@drawable/ic_placeholder" />
<View
android:id="@+id/handle"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?selectableItemBackground" />
<TextView
android:id="@+id/textView_number"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="8dp"
android:layout_margin="4dp"
android:ellipsize="none"
android:gravity="center"
android:minWidth="26dp"
@@ -34,4 +30,4 @@
tools:background="@drawable/bg_badge_default"
tools:text="2" />
</FrameLayout>
</com.google.android.material.card.MaterialCardView>

View File

@@ -21,6 +21,4 @@
<dimen name="search_suggestions_manga_height">124dp</dimen>
<dimen name="search_suggestions_manga_spacing">4dp</dimen>
<!--Text dimens-->
</resources>

View File

@@ -85,4 +85,12 @@
<style name="ThemeOverlay.Kotatsu.AMOLED" parent="" />
<style name="Theme.Kotatsu.Dialog" parent="">
<item name="android:windowNoTitle">true</item>
<item name="android:windowIsFloating">true</item>
<item name="windowMinWidthMajor">@dimen/abc_dialog_min_width_major</item>
<item name="windowMinWidthMinor">@dimen/abc_dialog_min_width_minor</item>
<item name="toolbarNavigationButtonStyle">@style/Theme.Kotatsu.ActionMode.CloseButton</item>
</style>
</resources>