Initial adding Explore screen

pull/163/head
Zakhar Timoshenko 4 years ago
parent 6a8a6a08db
commit b9428e3898

@ -21,6 +21,7 @@ import org.koitharu.kotatsu.core.network.networkModule
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.ui.uiModule import org.koitharu.kotatsu.core.ui.uiModule
import org.koitharu.kotatsu.details.detailsModule import org.koitharu.kotatsu.details.detailsModule
import org.koitharu.kotatsu.explore.exploreModule
import org.koitharu.kotatsu.favourites.favouritesModule import org.koitharu.kotatsu.favourites.favouritesModule
import org.koitharu.kotatsu.history.historyModule import org.koitharu.kotatsu.history.historyModule
import org.koitharu.kotatsu.library.libraryModule import org.koitharu.kotatsu.library.libraryModule
@ -79,6 +80,7 @@ class KotatsuApp : Application() {
shikimoriModule, shikimoriModule,
bookmarksModule, bookmarksModule,
libraryModule, libraryModule,
exploreModule,
) )
} }
} }

@ -25,6 +25,7 @@ import org.koitharu.kotatsu.base.ui.util.ActionModeDelegate
import org.koitharu.kotatsu.base.ui.util.WindowInsetsDelegate import org.koitharu.kotatsu.base.ui.util.WindowInsetsDelegate
import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.settings.SettingsActivity
abstract class BaseActivity<B : ViewBinding> : abstract class BaseActivity<B : ViewBinding> :
AppCompatActivity(), AppCompatActivity(),

@ -332,7 +332,8 @@ class DetailsFragment :
override fun onWindowInsetsChanged(insets: Insets) { override fun onWindowInsetsChanged(insets: Insets) {
binding.root.updatePadding( binding.root.updatePadding(
bottom = insets.bottom, left = insets.left,
right = insets.right,
) )
} }

@ -0,0 +1,10 @@
package org.koitharu.kotatsu.explore
import org.koin.androidx.viewmodel.dsl.viewModel
import org.koin.dsl.module
import org.koitharu.kotatsu.explore.ui.ExploreViewModel
val exploreModule
get() = module {
viewModel { ExploreViewModel(get()) }
}

@ -0,0 +1,79 @@
package org.koitharu.kotatsu.explore.ui
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.graphics.Insets
import androidx.core.view.updatePadding
import androidx.recyclerview.widget.RecyclerView
import org.koin.android.ext.android.get
import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseFragment
import org.koitharu.kotatsu.base.ui.util.RecyclerViewOwner
import org.koitharu.kotatsu.databinding.FragmentExploreBinding
import org.koitharu.kotatsu.explore.ui.adapter.ExploreAdapter
import org.koitharu.kotatsu.explore.ui.adapter.SourcesHeaderEventListener
import org.koitharu.kotatsu.library.ui.adapter.LibraryAdapter
import org.koitharu.kotatsu.settings.SettingsActivity
class ExploreFragment : BaseFragment<FragmentExploreBinding>(),
RecyclerViewOwner,
SourcesHeaderEventListener {
private val viewModel by viewModel<ExploreViewModel>()
private var exploreAdapter: ExploreAdapter? = null
private var paddingHorizontal = 0
override val recyclerView: RecyclerView
get() = binding.recyclerView
override fun onInflateView(inflater: LayoutInflater, container: ViewGroup?): FragmentExploreBinding {
return FragmentExploreBinding.inflate(inflater, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
exploreAdapter = ExploreAdapter(get(), viewLifecycleOwner, this)
with(binding.recyclerView) {
adapter = exploreAdapter
setHasFixedSize(true)
val spacing = resources.getDimensionPixelOffset(R.dimen.list_spacing)
paddingHorizontal = spacing
}
viewModel.items.observe(viewLifecycleOwner) {
exploreAdapter!!.items = it
}
}
override fun onDestroyView() {
super.onDestroyView()
exploreAdapter = null
}
override fun onWindowInsetsChanged(insets: Insets) {
binding.root.updatePadding(
left = insets.left,
right = insets.right,
)
binding.recyclerView.updatePadding(
left = insets.left + paddingHorizontal,
right = insets.right + paddingHorizontal,
bottom = insets.bottom,
)
}
override fun onManageClick(view: View) {
startActivity(SettingsActivity.newManageSourcesIntent(view.context))
}
override fun onRetryClick(error: Throwable) = Unit
override fun onEmptyActionClick() = Unit
companion object {
fun newInstance() = ExploreFragment()
}
}

@ -0,0 +1,71 @@
package org.koitharu.kotatsu.explore.ui
import androidx.core.os.LocaleListCompat
import androidx.lifecycle.MutableLiveData
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseViewModel
import org.koitharu.kotatsu.core.model.getLocaleTitle
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.explore.ui.model.ExploreItem
import org.koitharu.kotatsu.utils.ext.map
import java.util.*
private const val KEY_ENABLED = "!"
class ExploreViewModel(
private val settings: AppSettings,
) : BaseViewModel() {
val items = MutableLiveData<List<ExploreItem>>(emptyList())
init {
buildList()
}
private fun buildList() {
val sources = settings.getMangaSources(includeHidden = true)
val hiddenSources = settings.hiddenSources
val map = sources.groupByTo(TreeMap(LocaleKeyComparator())) {
if (it.name !in hiddenSources) {
KEY_ENABLED
} else {
it.locale
}
}
val result = ArrayList<ExploreItem>(sources.size + map.size + 1)
result += ExploreItem.Buttons
val enabledSources = map.remove(KEY_ENABLED)
if (!enabledSources.isNullOrEmpty()) {
result += ExploreItem.Header(R.string.enabled_sources)
enabledSources.mapTo(result) {
ExploreItem.Source(
source = it,
summary = it.getLocaleTitle(),
)
}
}
items.value = result
}
private class LocaleKeyComparator : Comparator<String?> {
private val deviceLocales = LocaleListCompat.getAdjustedDefault()
.map { it.language }
override fun compare(a: String?, b: String?): Int {
when {
a == b -> return 0
a == null -> return 1
b == null -> return -1
}
val ai = deviceLocales.indexOf(a!!)
val bi = deviceLocales.indexOf(b!!)
return when {
ai < 0 && bi < 0 -> a.compareTo(b)
ai < 0 -> 1
bi < 0 -> -1
else -> ai.compareTo(bi)
}
}
}
}

@ -0,0 +1,27 @@
package org.koitharu.kotatsu.explore.ui.adapter
import androidx.lifecycle.LifecycleOwner
import coil.ImageLoader
import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter
import org.koitharu.kotatsu.explore.ui.model.ExploreItem
class ExploreAdapter(
coil: ImageLoader,
lifecycleOwner: LifecycleOwner,
listener: SourcesHeaderEventListener,
) : AsyncListDifferDelegationAdapter<ExploreItem>(
ExploreDiffCallback(),
exploreButtonsDelegate(),
sourceHeaderDelegate(listener),
sourceItemDelegate(coil, lifecycleOwner),
) {
init {
delegatesManager
.addDelegate(exploreButtonsDelegate())
.addDelegate(sourceHeaderDelegate(listener = listener))
.addDelegate(sourceItemDelegate(coil, lifecycleOwner))
}
}

@ -0,0 +1,65 @@
package org.koitharu.kotatsu.explore.ui.adapter
import android.view.View
import androidx.lifecycle.LifecycleOwner
import coil.ImageLoader
import coil.request.Disposable
import coil.request.ImageRequest
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.databinding.ItemExploreButtonsBinding
import org.koitharu.kotatsu.databinding.ItemExploreHeaderBinding
import org.koitharu.kotatsu.databinding.ItemExploreSourceBinding
import org.koitharu.kotatsu.explore.ui.model.ExploreItem
import org.koitharu.kotatsu.utils.ext.enqueueWith
fun exploreButtonsDelegate() = adapterDelegateViewBinding<ExploreItem.Buttons, ExploreItem, ItemExploreButtonsBinding>(
{ layoutInflater, parent -> ItemExploreButtonsBinding.inflate(layoutInflater, parent, false) }
) {
binding.localStorage.requestFocus() // stub
} // TODO
fun sourceHeaderDelegate(
listener: SourcesHeaderEventListener,
) = adapterDelegateViewBinding<ExploreItem.Header, ExploreItem, ItemExploreHeaderBinding>(
{ layoutInflater, parent -> ItemExploreHeaderBinding.inflate(layoutInflater, parent, false) }
) {
val listenerAdapter = View.OnClickListener {
listener.onManageClick(itemView)
}
binding.buttonMore.setOnClickListener(listenerAdapter)
bind {
binding.textViewTitle.setText(R.string.remote_sources)
}
}
fun sourceItemDelegate(
coil: ImageLoader,
lifecycleOwner: LifecycleOwner,
) = adapterDelegateViewBinding<ExploreItem.Source, ExploreItem, ItemExploreSourceBinding>(
{ layoutInflater, parent -> ItemExploreSourceBinding.inflate(layoutInflater, parent, false) },
on = { item, _, _ -> item is ExploreItem.Source }
) {
var imageRequest: Disposable? = null
bind {
binding.textViewTitle.text = item.source.title
imageRequest = ImageRequest.Builder(context)
.data(item.faviconUrl)
.error(R.drawable.ic_favicon_fallback)
.target(binding.imageViewCover)
.lifecycle(lifecycleOwner)
.enqueueWith(coil)
}
onViewRecycled {
imageRequest?.dispose()
imageRequest = null
}
}

@ -0,0 +1,28 @@
package org.koitharu.kotatsu.explore.ui.adapter
import androidx.recyclerview.widget.DiffUtil
import org.koitharu.kotatsu.explore.ui.model.ExploreItem
class ExploreDiffCallback : DiffUtil.ItemCallback<ExploreItem>() {
override fun areItemsTheSame(oldItem: ExploreItem, newItem: ExploreItem): Boolean {
return when {
oldItem.javaClass != newItem.javaClass -> false
oldItem is ExploreItem.Buttons && newItem is ExploreItem.Buttons -> {
oldItem == newItem
}
oldItem is ExploreItem.Source && newItem is ExploreItem.Source -> {
oldItem.source == newItem.source
}
oldItem is ExploreItem.Header && newItem is ExploreItem.Header -> {
oldItem.titleResId == newItem.titleResId
}
else -> false
}
}
override fun areContentsTheSame(oldItem: ExploreItem, newItem: ExploreItem): Boolean {
return oldItem == newItem
}
}

@ -0,0 +1,10 @@
package org.koitharu.kotatsu.explore.ui.adapter
import android.view.View
import org.koitharu.kotatsu.list.ui.adapter.ListStateHolderListener
interface SourcesHeaderEventListener : ListStateHolderListener {
fun onManageClick(view: View)
}

@ -0,0 +1,52 @@
package org.koitharu.kotatsu.explore.ui.model
import android.net.Uri
import androidx.annotation.StringRes
import org.koitharu.kotatsu.parsers.model.MangaSource
sealed interface ExploreItem {
object Buttons : ExploreItem
class Header(
@StringRes val titleResId: Int,
) : ExploreItem {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Header
return titleResId == other.titleResId
}
override fun hashCode(): Int = titleResId
}
class Source(
val source: MangaSource,
val summary: String?,
) : ExploreItem {
val faviconUrl: Uri
get() = Uri.fromParts("favicon", source.name, null)
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Source
if (source != other.source) return false
if (summary != other.summary) return false
return true
}
override fun hashCode(): Int {
var result = source.hashCode()
result = 31 * result + summary.hashCode()
return result
}
}
}

@ -3,7 +3,9 @@ package org.koitharu.kotatsu.history.ui
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.ViewGroup
import androidx.core.graphics.Insets import androidx.core.graphics.Insets
import androidx.core.view.updateLayoutParams
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import androidx.fragment.app.commit import androidx.fragment.app.commit
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
@ -26,7 +28,11 @@ class HistoryActivity : BaseActivity<ActivityContainerBinding>() {
} }
override fun onWindowInsetsChanged(insets: Insets) { override fun onWindowInsetsChanged(insets: Insets) {
binding.toolbar.updatePadding( binding.toolbar.updateLayoutParams<ViewGroup.MarginLayoutParams> {
leftMargin = insets.left
rightMargin = insets.right
}
binding.root.updatePadding(
left = insets.left, left = insets.left,
right = insets.right, right = insets.right,
) )

@ -99,9 +99,11 @@ class LibraryFragment : BaseFragment<FragmentLibraryBinding>(), LibraryListEvent
override fun onEmptyActionClick() = Unit override fun onEmptyActionClick() = Unit
override fun onWindowInsetsChanged(insets: Insets) { override fun onWindowInsetsChanged(insets: Insets) {
binding.recyclerView.updatePadding( binding.root.updatePadding(
left = insets.left, left = insets.left,
right = insets.right, right = insets.right,
)
binding.recyclerView.updatePadding(
bottom = insets.bottom, bottom = insets.bottom,
) )
} }

@ -29,6 +29,7 @@ import org.koitharu.kotatsu.base.ui.BaseActivity
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.databinding.ActivityMainBinding import org.koitharu.kotatsu.databinding.ActivityMainBinding
import org.koitharu.kotatsu.details.ui.DetailsActivity import org.koitharu.kotatsu.details.ui.DetailsActivity
import org.koitharu.kotatsu.explore.ui.ExploreFragment
import org.koitharu.kotatsu.favourites.ui.FavouritesContainerFragment import org.koitharu.kotatsu.favourites.ui.FavouritesContainerFragment
import org.koitharu.kotatsu.library.ui.LibraryFragment import org.koitharu.kotatsu.library.ui.LibraryFragment
import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.Manga
@ -248,8 +249,8 @@ class MainActivity :
binding.root.isLiftAppBarOnScroll = true // придумать лучше binding.root.isLiftAppBarOnScroll = true // придумать лучше
} }
R.id.nav_explore -> { R.id.nav_explore -> {
setPrimaryFragment(FavouritesContainerFragment.newInstance()) setPrimaryFragment(ExploreFragment.newInstance())
binding.root.isLiftAppBarOnScroll = false // --//-- binding.root.isLiftAppBarOnScroll = true // --//--
} }
R.id.nav_feed -> { R.id.nav_feed -> {
setPrimaryFragment(FeedFragment.newInstance()) setPrimaryFragment(FeedFragment.newInstance())

@ -24,6 +24,7 @@ import org.koitharu.kotatsu.databinding.ActivitySettingsBinding
import org.koitharu.kotatsu.main.ui.AppBarOwner import org.koitharu.kotatsu.main.ui.AppBarOwner
import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.scrobbling.shikimori.ui.ShikimoriSettingsFragment import org.koitharu.kotatsu.scrobbling.shikimori.ui.ShikimoriSettingsFragment
import org.koitharu.kotatsu.settings.sources.SourcesSettingsFragment
import org.koitharu.kotatsu.utils.ext.isScrolledToTop import org.koitharu.kotatsu.utils.ext.isScrolledToTop
class SettingsActivity : class SettingsActivity :
@ -126,6 +127,7 @@ class SettingsActivity :
ACTION_SOURCE -> SourceSettingsFragment.newInstance( ACTION_SOURCE -> SourceSettingsFragment.newInstance(
intent.getSerializableExtra(EXTRA_SOURCE) as? MangaSource ?: MangaSource.LOCAL intent.getSerializableExtra(EXTRA_SOURCE) as? MangaSource ?: MangaSource.LOCAL
) )
ACTION_MANAGE_SOURCES -> SourcesSettingsFragment()
else -> SettingsHeadersFragment() else -> SettingsHeadersFragment()
} }
supportFragmentManager.commit { supportFragmentManager.commit {
@ -150,6 +152,7 @@ class SettingsActivity :
private const val ACTION_TRACKER = "${BuildConfig.APPLICATION_ID}.action.MANAGE_TRACKER" private const val ACTION_TRACKER = "${BuildConfig.APPLICATION_ID}.action.MANAGE_TRACKER"
private const val ACTION_SOURCE = "${BuildConfig.APPLICATION_ID}.action.MANAGE_SOURCE_SETTINGS" private const val ACTION_SOURCE = "${BuildConfig.APPLICATION_ID}.action.MANAGE_SOURCE_SETTINGS"
private const val ACTION_SHIKIMORI = "${BuildConfig.APPLICATION_ID}.action.MANAGE_SHIKIMORI_SETTINGS" private const val ACTION_SHIKIMORI = "${BuildConfig.APPLICATION_ID}.action.MANAGE_SHIKIMORI_SETTINGS"
private const val ACTION_MANAGE_SOURCES = "${BuildConfig.APPLICATION_ID}.action.MANAGE_SOURCES_LIST"
private const val EXTRA_SOURCE = "source" private const val EXTRA_SOURCE = "source"
private const val HOST_SHIKIMORI_AUTH = "shikimori-auth" private const val HOST_SHIKIMORI_AUTH = "shikimori-auth"
@ -172,6 +175,10 @@ class SettingsActivity :
Intent(context, SettingsActivity::class.java) Intent(context, SettingsActivity::class.java)
.setAction(ACTION_TRACKER) .setAction(ACTION_TRACKER)
fun newManageSourcesIntent(context: Context) =
Intent(context, SettingsActivity::class.java)
.setAction(ACTION_MANAGE_SOURCES)
fun newSourceSettingsIntent(context: Context, source: MangaSource) = fun newSourceSettingsIntent(context: Context, source: MangaSource) =
Intent(context, SettingsActivity::class.java) Intent(context, SettingsActivity::class.java)
.setAction(ACTION_SOURCE) .setAction(ACTION_SOURCE)

@ -0,0 +1,51 @@
<?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"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingHorizontal="8dp"
android:paddingVertical="4dp">
<com.google.android.material.button.MaterialButton
android:id="@+id/favourites"
style="@style/Widget.Kotatsu.ExploreButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:layout_weight="1"
android:text="@string/history"
app:icon="@drawable/ic_history" />
<com.google.android.material.button.MaterialButton
android:id="@+id/local_storage"
style="@style/Widget.Kotatsu.ExploreButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_weight="1"
android:text="@string/local_storage"
app:icon="@drawable/ic_storage" />
<com.google.android.material.button.MaterialButton
android:id="@+id/suggestions"
style="@style/Widget.Kotatsu.ExploreButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="8dp"
android:layout_weight="1"
android:text="@string/suggestions"
app:icon="@drawable/ic_suggestion" />
<com.google.android.material.button.MaterialButton
android:id="@+id/downloads"
style="@style/Widget.Kotatsu.ExploreButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_weight="1"
android:text="@string/bookmarks"
app:icon="@drawable/ic_bookmark" />
</LinearLayout>

@ -1,7 +1,17 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout <androidx.recyclerview.widget.RecyclerView
xmlns:android="http://schemas.android.com/apk/res/android" 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/recyclerView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent"
android:clipToPadding="false"
</androidx.constraintlayout.widget.ConstraintLayout> android:orientation="vertical"
android:paddingLeft="@dimen/list_spacing"
android:paddingRight="@dimen/list_spacing"
android:paddingTop="@dimen/grid_spacing_outer"
android:paddingBottom="@dimen/grid_spacing_outer"
android:scrollbars="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/item_explore_source" />

@ -0,0 +1,62 @@
<?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"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingHorizontal="8dp"
android:paddingVertical="4dp">
<com.google.android.material.button.MaterialButton
android:id="@+id/favourites"
style="@style/Widget.Kotatsu.ExploreButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:text="@string/history"
app:icon="@drawable/ic_history"
app:layout_constraintEnd_toStartOf="@+id/local_storage"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.button.MaterialButton
android:id="@+id/local_storage"
style="@style/Widget.Kotatsu.ExploreButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/local_storage"
app:icon="@drawable/ic_storage"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/favourites"
app:layout_constraintTop_toTopOf="@+id/favourites" />
<com.google.android.material.button.MaterialButton
android:id="@+id/suggestions"
style="@style/Widget.Kotatsu.ExploreButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:text="@string/suggestions"
app:icon="@drawable/ic_suggestion"
app:layout_constraintEnd_toStartOf="@+id/downloads"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="@+id/favourites"
app:layout_constraintTop_toBottomOf="@+id/favourites" />
<com.google.android.material.button.MaterialButton
android:id="@+id/downloads"
style="@style/Widget.Kotatsu.ExploreButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/bookmarks"
app:icon="@drawable/ic_bookmark"
app:layout_constraintEnd_toEndOf="@+id/local_storage"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/suggestions"
app:layout_constraintTop_toBottomOf="@+id/local_storage" />
</androidx.constraintlayout.widget.ConstraintLayout>

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.button.MaterialButton
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
style="@style/Widget.Kotatsu.ExploreButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:icon="@drawable/ic_history"
tools:text="@string/history" />

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
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:orientation="horizontal">
<TextView
android:id="@+id/textView_title"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:gravity="center_vertical|start"
android:padding="@dimen/grid_spacing"
android:singleLine="true"
android:textAppearance="@style/TextAppearance.Kotatsu.SectionHeader"
tools:text="@tools:sample/lorem[2]" />
<Button
android:id="@+id/button_more"
style="@style/Widget.Material3.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/manage" />
</LinearLayout>

@ -0,0 +1,38 @@
<?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="?attr/listPreferredItemHeightSmall"
android:background="?selectableItemBackground"
android:clipChildren="false"
android:padding="@dimen/list_spacing">
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/imageView_cover"
android:layout_width="0dp"
android:layout_height="0dp"
android:scaleType="centerCrop"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="h,1:1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Kotatsu.Cover.Small"
tools:src="@tools:sample/backgrounds/scenic" />
<TextView
android:id="@+id/textView_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:ellipsize="end"
android:maxLines="1"
android:textAppearance="?attr/textAppearanceBodyMedium"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/imageView_cover"
app:layout_constraintTop_toTopOf="@+id/imageView_cover"
tools:text="Title" />
</androidx.constraintlayout.widget.ConstraintLayout>

@ -2,12 +2,13 @@
<TextView <TextView
xmlns:android="http://schemas.android.com/apk/res/android" 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"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
xmlns:tools="http://schemas.android.com/tools" android:id="@+id/textView"
android:padding="8dp"
android:textAppearance="@style/TextAppearance.AppCompat" android:textAppearance="@style/TextAppearance.AppCompat"
android:textSize="24sp" android:textSize="20sp"
tools:text="History"
android:padding="12dp"
app:drawableEndCompat="@drawable/ic_arrow_forward" app:drawableEndCompat="@drawable/ic_arrow_forward"
app:drawableTint="?attr/colorPrimary" /> app:drawableTint="?attr/colorPrimary"
tools:text="History" />

@ -323,4 +323,5 @@
<string name="clear_all_history">Clear all history</string> <string name="clear_all_history">Clear all history</string>
<string name="last_2_hours">Last 2 hours</string> <string name="last_2_hours">Last 2 hours</string>
<string name="history_cleared">History cleared</string> <string name="history_cleared">History cleared</string>
<string name="manage">Manage</string>
</resources> </resources>

@ -127,13 +127,14 @@
<item name="android:insetBottom">2dp</item> <item name="android:insetBottom">2dp</item>
</style> </style>
<style name="Widget.Kotatsu.ExploreButton" parent="Widget.Material3.Button.ElevatedButton.Icon"> <style name="Widget.Kotatsu.ExploreButton" parent="Widget.Material3.Button.TonalButton.Icon">
<item name="android:paddingTop">16dp</item> <item name="android:paddingTop">16dp</item>
<item name="android:paddingBottom">16dp</item> <item name="android:paddingBottom">16dp</item>
<item name="android:textColor">?attr/colorOnSurface</item> <item name="android:textColor">?attr/colorOnSurface</item>
<item name="shapeAppearanceOverlay">@style/ShapeAppearanceOverlay.Material3.FloatingActionButton</item> <item name="shapeAppearanceOverlay">@style/ShapeAppearanceOverlay.Material3.FloatingActionButton</item>
<item name="iconPadding">16dp</item> <item name="iconPadding">16dp</item>
<item name="iconGravity">start</item> <item name="iconGravity">start</item>
<item name="iconTint">?attr/colorPrimary</item>
<item name="android:gravity">start|center_vertical</item> <item name="android:gravity">start|center_vertical</item>
<item name="android:textAppearance">@style/TextAppearance.Material3.BodyMedium</item> <item name="android:textAppearance">@style/TextAppearance.Material3.BodyMedium</item>
</style> </style>

Loading…
Cancel
Save