Configurable main navigation
This commit is contained in:
@@ -61,11 +61,17 @@ class BookmarksFragment :
|
||||
private var bookmarksAdapter: BookmarksAdapter? = null
|
||||
private var selectionController: ListSelectionController? = null
|
||||
|
||||
override fun onCreateViewBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentListSimpleBinding {
|
||||
override fun onCreateViewBinding(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
): FragmentListSimpleBinding {
|
||||
return FragmentListSimpleBinding.inflate(inflater, container, false)
|
||||
}
|
||||
|
||||
override fun onViewBindingCreated(binding: FragmentListSimpleBinding, savedInstanceState: Bundle?) {
|
||||
override fun onViewBindingCreated(
|
||||
binding: FragmentListSimpleBinding,
|
||||
savedInstanceState: Bundle?,
|
||||
) {
|
||||
super.onViewBindingCreated(binding, savedInstanceState)
|
||||
selectionController = ListSelectionController(
|
||||
activity = requireActivity(),
|
||||
@@ -95,7 +101,10 @@ class BookmarksFragment :
|
||||
viewModel.content.observe(viewLifecycleOwner) {
|
||||
bookmarksAdapter?.setItems(it, spanSizeLookup)
|
||||
}
|
||||
viewModel.onError.observeEvent(viewLifecycleOwner, SnackbarErrorObserver(binding.recyclerView, this))
|
||||
viewModel.onError.observeEvent(
|
||||
viewLifecycleOwner,
|
||||
SnackbarErrorObserver(binding.recyclerView, this)
|
||||
)
|
||||
viewModel.onActionDone.observeEvent(viewLifecycleOwner, ::onActionDone)
|
||||
}
|
||||
|
||||
@@ -139,12 +148,20 @@ class BookmarksFragment :
|
||||
requireViewBinding().recyclerView.invalidateItemDecorations()
|
||||
}
|
||||
|
||||
override fun onCreateActionMode(controller: ListSelectionController, mode: ActionMode, menu: Menu): Boolean {
|
||||
override fun onCreateActionMode(
|
||||
controller: ListSelectionController,
|
||||
mode: ActionMode,
|
||||
menu: Menu,
|
||||
): Boolean {
|
||||
mode.menuInflater.inflate(R.menu.mode_bookmarks, menu)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onActionItemClicked(controller: ListSelectionController, mode: ActionMode, item: MenuItem): Boolean {
|
||||
override fun onActionItemClicked(
|
||||
controller: ListSelectionController,
|
||||
mode: ActionMode,
|
||||
item: MenuItem,
|
||||
): Boolean {
|
||||
return when (item.itemId) {
|
||||
R.id.action_remove -> {
|
||||
val ids = selectionController?.snapshot() ?: return false
|
||||
@@ -170,7 +187,8 @@ class BookmarksFragment :
|
||||
private fun onActionDone(action: ReversibleAction) {
|
||||
val handle = action.handle
|
||||
val length = if (handle == null) Snackbar.LENGTH_SHORT else Snackbar.LENGTH_LONG
|
||||
val snackbar = Snackbar.make((activity as SnackbarOwner).snackbarHost, action.stringResId, length)
|
||||
val snackbar =
|
||||
Snackbar.make((activity as SnackbarOwner).snackbarHost, action.stringResId, length)
|
||||
if (handle != null) {
|
||||
snackbar.setAction(R.string.undo) { handle.reverseAsync() }
|
||||
}
|
||||
@@ -185,7 +203,8 @@ class BookmarksFragment :
|
||||
}
|
||||
|
||||
override fun getSpanSize(position: Int): Int {
|
||||
val total = (viewBinding?.recyclerView?.layoutManager as? GridLayoutManager)?.spanCount ?: return 1
|
||||
val total = (viewBinding?.recyclerView?.layoutManager as? GridLayoutManager)?.spanCount
|
||||
?: return 1
|
||||
return when (bookmarksAdapter?.getItemViewType(position)) {
|
||||
ListItemType.PAGE_THUMB.ordinal -> 1
|
||||
else -> total
|
||||
@@ -200,6 +219,12 @@ class BookmarksFragment :
|
||||
|
||||
companion object {
|
||||
|
||||
@Deprecated(
|
||||
"", ReplaceWith(
|
||||
"BookmarksFragment()",
|
||||
"org.koitharu.kotatsu.bookmarks.ui.BookmarksFragment"
|
||||
)
|
||||
)
|
||||
fun newInstance() = BookmarksFragment()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import org.koitharu.kotatsu.core.util.ext.takeIfReadable
|
||||
import org.koitharu.kotatsu.core.util.ext.toUriOrNull
|
||||
import org.koitharu.kotatsu.history.domain.model.HistoryOrder
|
||||
import org.koitharu.kotatsu.parsers.model.SortOrder
|
||||
import org.koitharu.kotatsu.parsers.util.find
|
||||
import org.koitharu.kotatsu.parsers.util.mapNotNullToSet
|
||||
import org.koitharu.kotatsu.parsers.util.mapToSet
|
||||
import java.io.File
|
||||
@@ -43,7 +44,8 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
|
||||
set(value) = prefs.edit { putEnumValue(KEY_LIST_MODE, value) }
|
||||
|
||||
val theme: Int
|
||||
get() = prefs.getString(KEY_THEME, null)?.toIntOrNull() ?: AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
|
||||
get() = prefs.getString(KEY_THEME, null)?.toIntOrNull()
|
||||
?: AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
|
||||
|
||||
val colorScheme: ColorScheme
|
||||
get() = prefs.getEnumValue(KEY_COLOR_THEME, ColorScheme.default)
|
||||
@@ -51,8 +53,20 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
|
||||
val isAmoledTheme: Boolean
|
||||
get() = prefs.getBoolean(KEY_THEME_AMOLED, false)
|
||||
|
||||
val isFavoritesNavItemFirst: Boolean
|
||||
get() = (prefs.getString(KEY_FIRST_NAV_ITEM, null)?.toIntOrNull() ?: 0) == 1
|
||||
var mainNavItems: List<NavItem>
|
||||
get() {
|
||||
val raw = prefs.getString(KEY_NAV_MAIN, null)?.split(',')
|
||||
return if (raw.isNullOrEmpty()) {
|
||||
listOf(NavItem.HISTORY, NavItem.FAVORITES, NavItem.EXPLORE, NavItem.FEED)
|
||||
} else {
|
||||
raw.mapNotNull { x -> NavItem.entries.find(x) }.ifEmpty { listOf(NavItem.EXPLORE) }
|
||||
}
|
||||
}
|
||||
set(value) {
|
||||
prefs.edit {
|
||||
putString(KEY_NAV_MAIN, value.joinToString(",") { it.name })
|
||||
}
|
||||
}
|
||||
|
||||
var gridSize: Int
|
||||
get() = prefs.getInt(KEY_GRID_SIZE, 100)
|
||||
@@ -145,7 +159,11 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
|
||||
|
||||
var appPassword: String?
|
||||
get() = prefs.getString(KEY_APP_PASSWORD, null)
|
||||
set(value) = prefs.edit { if (value != null) putString(KEY_APP_PASSWORD, value) else remove(KEY_APP_PASSWORD) }
|
||||
set(value) = prefs.edit {
|
||||
if (value != null) putString(KEY_APP_PASSWORD, value) else remove(
|
||||
KEY_APP_PASSWORD
|
||||
)
|
||||
}
|
||||
|
||||
val isLoggingEnabled: Boolean
|
||||
get() = prefs.getBoolean(KEY_LOGGING_ENABLED, false)
|
||||
@@ -171,7 +189,8 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
|
||||
if (isBackgroundNetworkRestricted()) {
|
||||
return false
|
||||
}
|
||||
val policy = NetworkPolicy.from(prefs.getString(KEY_PREFETCH_CONTENT, null), NetworkPolicy.NEVER)
|
||||
val policy =
|
||||
NetworkPolicy.from(prefs.getString(KEY_PREFETCH_CONTENT, null), NetworkPolicy.NEVER)
|
||||
return policy.isNetworkAllowed(connectivityManager)
|
||||
}
|
||||
|
||||
@@ -292,14 +311,22 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
|
||||
@get:FloatRange(from = 0.0, to = 1.0)
|
||||
var readerAutoscrollSpeed: Float
|
||||
get() = prefs.getFloat(KEY_READER_AUTOSCROLL_SPEED, 0f)
|
||||
set(@FloatRange(from = 0.0, to = 1.0) value) = prefs.edit { putFloat(KEY_READER_AUTOSCROLL_SPEED, value) }
|
||||
set(@FloatRange(from = 0.0, to = 1.0) value) = prefs.edit {
|
||||
putFloat(
|
||||
KEY_READER_AUTOSCROLL_SPEED,
|
||||
value
|
||||
)
|
||||
}
|
||||
|
||||
val isPagesPreloadEnabled: Boolean
|
||||
get() {
|
||||
if (isBackgroundNetworkRestricted()) {
|
||||
return false
|
||||
}
|
||||
val policy = NetworkPolicy.from(prefs.getString(KEY_PAGES_PRELOAD, null), NetworkPolicy.NON_METERED)
|
||||
val policy = NetworkPolicy.from(
|
||||
prefs.getString(KEY_PAGES_PRELOAD, null),
|
||||
NetworkPolicy.NON_METERED
|
||||
)
|
||||
return policy.isNetworkAllowed(connectivityManager)
|
||||
}
|
||||
|
||||
@@ -455,7 +482,7 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
|
||||
const val KEY_LOCAL_MANGA_DIRS = "local_manga_dirs"
|
||||
const val KEY_DISABLE_NSFW = "no_nsfw"
|
||||
const val KEY_RELATED_MANGA = "related_manga"
|
||||
const val KEY_FIRST_NAV_ITEM = "nav_first"
|
||||
const val KEY_NAV_MAIN = "nav_main"
|
||||
|
||||
// About
|
||||
const val KEY_APP_UPDATE = "app_update"
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package org.koitharu.kotatsu.core.prefs
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.IdRes
|
||||
import androidx.annotation.StringRes
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
|
||||
enum class NavItem(
|
||||
@IdRes val id: Int,
|
||||
@StringRes val title: Int,
|
||||
@DrawableRes val icon: Int,
|
||||
) : ListModel {
|
||||
|
||||
HISTORY(R.id.nav_history, R.string.history, R.drawable.ic_history_selector),
|
||||
FAVORITES(R.id.nav_favorites, R.string.favourites, R.drawable.ic_favourites_selector),
|
||||
LOCAL(R.id.nav_local, R.string.on_device, R.drawable.ic_storage_selector),
|
||||
EXPLORE(R.id.nav_explore, R.string.explore, R.drawable.ic_explore_selector),
|
||||
SUGGESTIONS(R.id.nav_suggestions, R.string.suggestions, R.drawable.ic_suggestion_selector),
|
||||
FEED(R.id.nav_feed, R.string.feed, R.drawable.ic_feed_selector),
|
||||
BOOKMARKS(R.id.nav_bookmarks, R.string.bookmarks, R.drawable.ic_bookmark_selector),
|
||||
;
|
||||
|
||||
override fun areItemsTheSame(other: ListModel): Boolean {
|
||||
return other is NavItem && ordinal == other.ordinal
|
||||
}
|
||||
|
||||
fun isAvailable(settings: AppSettings): Boolean = when (this) {
|
||||
SUGGESTIONS -> settings.isSuggestionsEnabled
|
||||
FEED -> settings.isTrackerEnabled
|
||||
else -> true
|
||||
}
|
||||
}
|
||||
@@ -25,4 +25,5 @@ enum class ListItemType {
|
||||
DOWNLOAD,
|
||||
CATEGORY_LARGE,
|
||||
MANGA_SCROBBLING,
|
||||
NAV_ITEM,
|
||||
}
|
||||
|
||||
@@ -13,34 +13,39 @@ class TypedListSpacingDecoration(
|
||||
) : ItemDecoration() {
|
||||
|
||||
private val spacingSmall = context.resources.getDimensionPixelOffset(R.dimen.list_spacing_small)
|
||||
private val spacingNormal = context.resources.getDimensionPixelOffset(R.dimen.list_spacing_normal)
|
||||
private val spacingNormal =
|
||||
context.resources.getDimensionPixelOffset(R.dimen.list_spacing_normal)
|
||||
private val spacingLarge = context.resources.getDimensionPixelOffset(R.dimen.list_spacing_large)
|
||||
|
||||
override fun getItemOffsets(
|
||||
outRect: Rect,
|
||||
view: View,
|
||||
parent: RecyclerView,
|
||||
state: RecyclerView.State
|
||||
state: RecyclerView.State,
|
||||
) {
|
||||
val itemType = parent.getChildViewHolder(view)?.itemViewType?.let {
|
||||
ListItemType.entries.getOrNull(it)
|
||||
}
|
||||
when (itemType) {
|
||||
ListItemType.FILTER_SORT,
|
||||
ListItemType.FILTER_TAG -> outRect.set(0)
|
||||
ListItemType.FILTER_TAG,
|
||||
-> outRect.set(0)
|
||||
|
||||
ListItemType.HEADER,
|
||||
ListItemType.FEED,
|
||||
ListItemType.EXPLORE_SOURCE_LIST,
|
||||
ListItemType.MANGA_SCROBBLING,
|
||||
ListItemType.MANGA_LIST -> outRect.set(0)
|
||||
ListItemType.MANGA_LIST,
|
||||
-> outRect.set(0)
|
||||
|
||||
ListItemType.DOWNLOAD,
|
||||
ListItemType.HINT_EMPTY,
|
||||
ListItemType.MANGA_LIST_DETAILED -> outRect.set(spacingNormal)
|
||||
ListItemType.MANGA_LIST_DETAILED,
|
||||
-> outRect.set(spacingNormal)
|
||||
|
||||
ListItemType.PAGE_THUMB,
|
||||
ListItemType.MANGA_GRID -> outRect.set(spacingNormal)
|
||||
ListItemType.MANGA_GRID,
|
||||
-> outRect.set(spacingNormal)
|
||||
|
||||
ListItemType.EXPLORE_BUTTONS -> outRect.set(spacingNormal)
|
||||
|
||||
@@ -53,7 +58,9 @@ class TypedListSpacingDecoration(
|
||||
ListItemType.EXPLORE_SUGGESTION,
|
||||
ListItemType.MANGA_NESTED_GROUP,
|
||||
ListItemType.CATEGORY_LARGE,
|
||||
null -> outRect.set(0)
|
||||
ListItemType.NAV_ITEM,
|
||||
null,
|
||||
-> outRect.set(0)
|
||||
|
||||
ListItemType.TIP -> outRect.set(0) // TODO
|
||||
}
|
||||
@@ -70,6 +77,6 @@ class TypedListSpacingDecoration(
|
||||
private fun Rect.set(spacing: Int) = set(spacing, spacing, spacing, spacing)
|
||||
|
||||
private fun ListItemType?.isEdgeToEdge() = this == ListItemType.MANGA_NESTED_GROUP
|
||||
|| this == ListItemType.FILTER_SORT
|
||||
|| this == ListItemType.FILTER_TAG
|
||||
|| this == ListItemType.FILTER_SORT
|
||||
|| this == ListItemType.FILTER_TAG
|
||||
}
|
||||
|
||||
@@ -47,12 +47,20 @@ class LocalListFragment : MangaListFragment(), FilterOwner {
|
||||
|
||||
override fun onScrolledToEnd() = viewModel.loadNextPage()
|
||||
|
||||
override fun onCreateActionMode(controller: ListSelectionController, mode: ActionMode, menu: Menu): Boolean {
|
||||
override fun onCreateActionMode(
|
||||
controller: ListSelectionController,
|
||||
mode: ActionMode,
|
||||
menu: Menu,
|
||||
): Boolean {
|
||||
mode.menuInflater.inflate(R.menu.mode_local, menu)
|
||||
return super.onCreateActionMode(controller, mode, menu)
|
||||
}
|
||||
|
||||
override fun onActionItemClicked(controller: ListSelectionController, mode: ActionMode, item: MenuItem): Boolean {
|
||||
override fun onActionItemClicked(
|
||||
controller: ListSelectionController,
|
||||
mode: ActionMode,
|
||||
item: MenuItem,
|
||||
): Boolean {
|
||||
return when (item.itemId) {
|
||||
R.id.action_remove -> {
|
||||
showDeletionConfirm(selectedItemsIds, mode)
|
||||
@@ -83,13 +91,20 @@ class LocalListFragment : MangaListFragment(), FilterOwner {
|
||||
}
|
||||
|
||||
private fun onItemRemoved() {
|
||||
Snackbar.make(requireViewBinding().recyclerView, R.string.removal_completed, Snackbar.LENGTH_SHORT).show()
|
||||
Snackbar.make(
|
||||
requireViewBinding().recyclerView,
|
||||
R.string.removal_completed,
|
||||
Snackbar.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
fun newInstance() = LocalListFragment().withArgs(1) {
|
||||
putSerializable(RemoteListFragment.ARG_SOURCE, MangaSource.LOCAL) // required by FilterCoordinator
|
||||
putSerializable(
|
||||
RemoteListFragment.ARG_SOURCE,
|
||||
MangaSource.LOCAL
|
||||
) // required by FilterCoordinator
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ import kotlinx.coroutines.withContext
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.core.prefs.NavItem
|
||||
import org.koitharu.kotatsu.core.ui.BaseActivity
|
||||
import org.koitharu.kotatsu.core.ui.util.MenuInvalidator
|
||||
import org.koitharu.kotatsu.core.ui.util.OptionsMenuBadgeHelper
|
||||
@@ -71,8 +72,10 @@ import com.google.android.material.R as materialR
|
||||
private const val TAG_SEARCH = "search"
|
||||
|
||||
@AndroidEntryPoint
|
||||
class MainActivity : BaseActivity<ActivityMainBinding>(), AppBarOwner, BottomNavOwner, View.OnClickListener,
|
||||
View.OnFocusChangeListener, SearchSuggestionListener, MainNavigationDelegate.OnFragmentChangedListener {
|
||||
class MainActivity : BaseActivity<ActivityMainBinding>(), AppBarOwner, BottomNavOwner,
|
||||
View.OnClickListener,
|
||||
View.OnFocusChangeListener, SearchSuggestionListener,
|
||||
MainNavigationDelegate.OnFragmentChangedListener {
|
||||
|
||||
@Inject
|
||||
lateinit var settings: AppSettings
|
||||
@@ -119,7 +122,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>(), AppBarOwner, BottomNav
|
||||
settings = settings,
|
||||
)
|
||||
navigationDelegate.addOnFragmentChangedListener(this)
|
||||
navigationDelegate.onCreate(savedInstanceState)
|
||||
navigationDelegate.onCreate(this, savedInstanceState)
|
||||
|
||||
appUpdateBadge = OptionsMenuBadgeHelper(viewBinding.toolbar, R.id.action_app_update)
|
||||
|
||||
@@ -137,8 +140,11 @@ class MainActivity : BaseActivity<ActivityMainBinding>(), AppBarOwner, BottomNav
|
||||
viewModel.isResumeEnabled.observe(this, this::onResumeEnabledChanged)
|
||||
viewModel.counters.observe(this, ::onCountersChanged)
|
||||
viewModel.appUpdate.observe(this, MenuInvalidator(this))
|
||||
viewModel.onFirstStart.observeEvent(this) { OnboardDialogFragment.showWelcome(supportFragmentManager) }
|
||||
viewModel.isFeedAvailable.observe(this, ::onFeedAvailabilityChanged)
|
||||
viewModel.onFirstStart.observeEvent(this) {
|
||||
OnboardDialogFragment.showWelcome(
|
||||
supportFragmentManager
|
||||
)
|
||||
}
|
||||
searchSuggestionViewModel.isIncognitoModeEnabled.observe(this, this::onIncognitoModeChanged)
|
||||
}
|
||||
|
||||
@@ -166,7 +172,8 @@ class MainActivity : BaseActivity<ActivityMainBinding>(), AppBarOwner, BottomNav
|
||||
if (menu == null) {
|
||||
return false
|
||||
}
|
||||
menu.findItem(R.id.action_incognito)?.isChecked = searchSuggestionViewModel.isIncognitoModeEnabled.value
|
||||
menu.findItem(R.id.action_incognito)?.isChecked =
|
||||
searchSuggestionViewModel.isIncognitoModeEnabled.value
|
||||
val hasAppUpdate = viewModel.appUpdate.value != null
|
||||
menu.findItem(R.id.action_app_update)?.isVisible = hasAppUpdate
|
||||
appUpdateBadge.setBadgeVisible(hasAppUpdate)
|
||||
@@ -279,17 +286,12 @@ class MainActivity : BaseActivity<ActivityMainBinding>(), AppBarOwner, BottomNav
|
||||
startActivity(IntentBuilder(this).manga(manga).build(), options)
|
||||
}
|
||||
|
||||
private fun onCountersChanged(counters: IntArray) {
|
||||
repeat(counters.size) { i ->
|
||||
val counter = counters[i]
|
||||
navigationDelegate.setCounterAt(i, counter)
|
||||
private fun onCountersChanged(counters: Map<NavItem, Int>) {
|
||||
counters.forEach { (navItem, counter) ->
|
||||
navigationDelegate.setCounter(navItem, counter)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onFeedAvailabilityChanged(isFeedAvailable: Boolean) {
|
||||
navigationDelegate.setItemVisibility(R.id.nav_feed, isFeedAvailable)
|
||||
}
|
||||
|
||||
private fun onIncognitoModeChanged(isIncognito: Boolean) {
|
||||
var options = viewBinding.searchView.imeOptions
|
||||
options = if (isIncognito) {
|
||||
@@ -362,8 +364,12 @@ class MainActivity : BaseActivity<ActivityMainBinding>(), AppBarOwner, BottomNav
|
||||
} else {
|
||||
SCROLL_FLAG_SCROLL or SCROLL_FLAG_ENTER_ALWAYS or SCROLL_FLAG_SNAP
|
||||
}
|
||||
viewBinding.toolbarCard.updateLayoutParams<AppBarLayout.LayoutParams> { scrollFlags = appBarScrollFlags }
|
||||
viewBinding.insetsHolder.updateLayoutParams<AppBarLayout.LayoutParams> { scrollFlags = appBarScrollFlags }
|
||||
viewBinding.toolbarCard.updateLayoutParams<AppBarLayout.LayoutParams> {
|
||||
scrollFlags = appBarScrollFlags
|
||||
}
|
||||
viewBinding.insetsHolder.updateLayoutParams<AppBarLayout.LayoutParams> {
|
||||
scrollFlags = appBarScrollFlags
|
||||
}
|
||||
viewBinding.toolbarCard.background = if (isOpened) {
|
||||
null
|
||||
} else {
|
||||
@@ -387,7 +393,11 @@ class MainActivity : BaseActivity<ActivityMainBinding>(), AppBarOwner, BottomNav
|
||||
Manifest.permission.POST_NOTIFICATIONS,
|
||||
) != PERMISSION_GRANTED
|
||||
) {
|
||||
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.POST_NOTIFICATIONS), 1)
|
||||
ActivityCompat.requestPermissions(
|
||||
this,
|
||||
arrayOf(Manifest.permission.POST_NOTIFICATIONS),
|
||||
1
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.koitharu.kotatsu.main.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.annotation.IdRes
|
||||
@@ -8,16 +9,28 @@ import androidx.core.view.isEmpty
|
||||
import androidx.core.view.iterator
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.google.android.material.navigation.NavigationBarView
|
||||
import com.google.android.material.transition.MaterialFadeThrough
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.onStart
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.bookmarks.ui.BookmarksFragment
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.core.prefs.NavItem
|
||||
import org.koitharu.kotatsu.core.ui.util.RecyclerViewOwner
|
||||
import org.koitharu.kotatsu.core.util.ext.firstVisibleItemPosition
|
||||
import org.koitharu.kotatsu.core.util.ext.isAnimationsEnabled
|
||||
import org.koitharu.kotatsu.explore.ui.ExploreFragment
|
||||
import org.koitharu.kotatsu.favourites.ui.container.FavouritesContainerFragment
|
||||
import org.koitharu.kotatsu.history.ui.HistoryListFragment
|
||||
import org.koitharu.kotatsu.local.ui.LocalListFragment
|
||||
import org.koitharu.kotatsu.suggestions.ui.SuggestionsFragment
|
||||
import org.koitharu.kotatsu.tracker.ui.feed.FeedFragment
|
||||
import java.util.LinkedList
|
||||
|
||||
@@ -62,11 +75,11 @@ class MainNavigationDelegate(
|
||||
navBar.selectedItemId = R.id.nav_history
|
||||
}
|
||||
|
||||
fun onCreate(savedInstanceState: Bundle?) {
|
||||
fun onCreate(lifecycleOwner: LifecycleOwner, savedInstanceState: Bundle?) {
|
||||
if (navBar.menu.isEmpty()) {
|
||||
val menuRes = if (settings.isFavoritesNavItemFirst) R.menu.nav_bottom_alt else R.menu.nav_bottom
|
||||
navBar.inflateMenu(menuRes)
|
||||
createMenu(settings.mainNavItems, navBar.menu)
|
||||
}
|
||||
observeSettings(lifecycleOwner)
|
||||
val fragment = primaryFragment
|
||||
if (fragment != null) {
|
||||
onFragmentChanged(fragment, fromUser = false)
|
||||
@@ -84,12 +97,11 @@ class MainNavigationDelegate(
|
||||
}
|
||||
}
|
||||
|
||||
fun setCounterAt(position: Int, counter: Int) {
|
||||
val id = navBar.menu.getItem(position).itemId
|
||||
setCounter(id, counter)
|
||||
fun setCounter(item: NavItem, counter: Int) {
|
||||
setCounter(item.id, counter)
|
||||
}
|
||||
|
||||
fun setCounter(@IdRes id: Int, counter: Int) {
|
||||
private fun setCounter(@IdRes id: Int, counter: Int) {
|
||||
if (counter == 0) {
|
||||
navBar.getBadge(id)?.isVisible = false
|
||||
} else {
|
||||
@@ -123,9 +135,12 @@ class MainNavigationDelegate(
|
||||
return setPrimaryFragment(
|
||||
when (itemId) {
|
||||
R.id.nav_history -> HistoryListFragment()
|
||||
R.id.nav_favourites -> FavouritesContainerFragment()
|
||||
R.id.nav_favorites -> FavouritesContainerFragment()
|
||||
R.id.nav_explore -> ExploreFragment()
|
||||
R.id.nav_feed -> FeedFragment()
|
||||
R.id.nav_local -> LocalListFragment.newInstance()
|
||||
R.id.nav_suggestions -> SuggestionsFragment()
|
||||
R.id.nav_bookmarks -> BookmarksFragment()
|
||||
else -> return false
|
||||
},
|
||||
)
|
||||
@@ -133,9 +148,12 @@ class MainNavigationDelegate(
|
||||
|
||||
private fun getItemId(fragment: Fragment) = when (fragment) {
|
||||
is HistoryListFragment -> R.id.nav_history
|
||||
is FavouritesContainerFragment -> R.id.nav_favourites
|
||||
is FavouritesContainerFragment -> R.id.nav_favorites
|
||||
is ExploreFragment -> R.id.nav_explore
|
||||
is FeedFragment -> R.id.nav_feed
|
||||
is LocalListFragment -> R.id.nav_local
|
||||
is SuggestionsFragment -> R.id.nav_suggestions
|
||||
is BookmarksFragment -> R.id.nav_bookmarks
|
||||
else -> 0
|
||||
}
|
||||
|
||||
@@ -157,6 +175,24 @@ class MainNavigationDelegate(
|
||||
listeners.forEach { it.onFragmentChanged(fragment, fromUser) }
|
||||
}
|
||||
|
||||
private fun createMenu(items: List<NavItem>, menu: Menu) {
|
||||
for (item in items) {
|
||||
menu.add(Menu.NONE, item.id, Menu.NONE, item.title)
|
||||
.setIcon(item.icon)
|
||||
}
|
||||
}
|
||||
|
||||
private fun observeSettings(lifecycleOwner: LifecycleOwner) {
|
||||
settings.observe()
|
||||
.filter { x -> x == AppSettings.KEY_TRACKER_ENABLED || x == AppSettings.KEY_SUGGESTIONS }
|
||||
.onStart { emit("") }
|
||||
.flowOn(Dispatchers.Default)
|
||||
.onEach {
|
||||
setItemVisibility(R.id.nav_suggestions, settings.isSuggestionsEnabled)
|
||||
setItemVisibility(R.id.nav_feed, settings.isTrackerEnabled)
|
||||
}.launchIn(lifecycleOwner.lifecycleScope)
|
||||
}
|
||||
|
||||
private fun firstItem(): MenuItem? {
|
||||
val menu = navBar.menu
|
||||
for (item in menu) {
|
||||
|
||||
@@ -12,7 +12,7 @@ import kotlinx.coroutines.plus
|
||||
import org.koitharu.kotatsu.core.exceptions.EmptyHistoryException
|
||||
import org.koitharu.kotatsu.core.github.AppUpdateRepository
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.core.prefs.observeAsStateFlow
|
||||
import org.koitharu.kotatsu.core.prefs.NavItem
|
||||
import org.koitharu.kotatsu.core.ui.BaseViewModel
|
||||
import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
|
||||
import org.koitharu.kotatsu.core.util.ext.call
|
||||
@@ -21,6 +21,7 @@ import org.koitharu.kotatsu.history.data.HistoryRepository
|
||||
import org.koitharu.kotatsu.main.domain.ReadingResumeEnabledUseCase
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.tracker.domain.TrackingRepository
|
||||
import java.util.EnumMap
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
@@ -42,23 +43,20 @@ class MainViewModel @Inject constructor(
|
||||
initialValue = false,
|
||||
)
|
||||
|
||||
val isFeedAvailable = settings.observeAsStateFlow(
|
||||
scope = viewModelScope + Dispatchers.Default,
|
||||
key = AppSettings.KEY_TRACKER_ENABLED,
|
||||
valueProducer = { isTrackerEnabled },
|
||||
)
|
||||
|
||||
val appUpdate = appUpdateRepository.observeAvailableUpdate()
|
||||
|
||||
val counters = combine(
|
||||
trackingRepository.observeUpdatedMangaCount(),
|
||||
observeNewSourcesCount(),
|
||||
) { tracks, newSources ->
|
||||
intArrayOf(0, 0, newSources, tracks)
|
||||
val em = EnumMap<NavItem, Int>(NavItem::class.java)
|
||||
em[NavItem.EXPLORE] = newSources
|
||||
em[NavItem.FEED] = tracks
|
||||
em
|
||||
}.stateIn(
|
||||
scope = viewModelScope + Dispatchers.Default,
|
||||
started = SharingStarted.WhileSubscribed(5000),
|
||||
initialValue = IntArray(4),
|
||||
initialValue = emptyMap<NavItem, Int>(),
|
||||
)
|
||||
|
||||
init {
|
||||
|
||||
@@ -23,7 +23,6 @@ import org.koitharu.kotatsu.core.util.ext.map
|
||||
import org.koitharu.kotatsu.core.util.ext.postDelayed
|
||||
import org.koitharu.kotatsu.core.util.ext.setDefaultValueCompat
|
||||
import org.koitharu.kotatsu.core.util.ext.toList
|
||||
import org.koitharu.kotatsu.main.ui.MainActivity
|
||||
import org.koitharu.kotatsu.parsers.util.names
|
||||
import org.koitharu.kotatsu.parsers.util.toTitleCase
|
||||
import org.koitharu.kotatsu.settings.utils.ActivityListPreference
|
||||
@@ -67,6 +66,7 @@ class AppearanceSettingsFragment :
|
||||
}
|
||||
setDefaultValueCompat("")
|
||||
}
|
||||
bindNavSummary()
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
@@ -86,7 +86,8 @@ class AppearanceSettingsFragment :
|
||||
}
|
||||
|
||||
AppSettings.KEY_COLOR_THEME,
|
||||
AppSettings.KEY_THEME_AMOLED -> {
|
||||
AppSettings.KEY_THEME_AMOLED,
|
||||
-> {
|
||||
postRestart()
|
||||
}
|
||||
|
||||
@@ -94,8 +95,8 @@ class AppearanceSettingsFragment :
|
||||
AppCompatDelegate.setApplicationLocales(settings.appLocales)
|
||||
}
|
||||
|
||||
AppSettings.KEY_FIRST_NAV_ITEM -> {
|
||||
activityRecreationHandle.recreate(MainActivity::class.java)
|
||||
AppSettings.KEY_NAV_MAIN -> {
|
||||
bindNavSummary()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -127,6 +128,13 @@ class AppearanceSettingsFragment :
|
||||
}
|
||||
}
|
||||
|
||||
private fun bindNavSummary() {
|
||||
val pref = findPreference<Preference>(AppSettings.KEY_NAV_MAIN) ?: return
|
||||
pref.summary = settings.mainNavItems.joinToString {
|
||||
getString(it.title)
|
||||
}
|
||||
}
|
||||
|
||||
private class LocaleComparator(context: Context) : Comparator<Locale> {
|
||||
|
||||
private val deviceLocales = LocaleManagerCompat.getSystemLocales(context)
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
package org.koitharu.kotatsu.settings.nav
|
||||
|
||||
import android.content.DialogInterface
|
||||
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.fragment.app.viewModels
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.prefs.NavItem
|
||||
import org.koitharu.kotatsu.core.ui.BaseFragment
|
||||
import org.koitharu.kotatsu.core.ui.BaseListAdapter
|
||||
import org.koitharu.kotatsu.core.ui.dialog.RecyclerViewAlertDialog
|
||||
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.core.ui.util.RecyclerViewOwner
|
||||
import org.koitharu.kotatsu.core.util.ext.observe
|
||||
import org.koitharu.kotatsu.databinding.FragmentSettingsSourcesBinding
|
||||
import org.koitharu.kotatsu.list.ui.adapter.ListItemType
|
||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
import org.koitharu.kotatsu.settings.nav.adapter.navAddAD
|
||||
import org.koitharu.kotatsu.settings.nav.adapter.navAvailableAD
|
||||
import org.koitharu.kotatsu.settings.nav.adapter.navConfigAD
|
||||
|
||||
@AndroidEntryPoint
|
||||
class NavConfigFragment : BaseFragment<FragmentSettingsSourcesBinding>(), RecyclerViewOwner,
|
||||
OnListItemClickListener<NavItem>, View.OnClickListener {
|
||||
|
||||
private var reorderHelper: ItemTouchHelper? = null
|
||||
private val viewModel by viewModels<NavConfigViewModel>()
|
||||
|
||||
override val recyclerView: RecyclerView
|
||||
get() = requireViewBinding().recyclerView
|
||||
|
||||
override fun onCreateViewBinding(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
): FragmentSettingsSourcesBinding {
|
||||
return FragmentSettingsSourcesBinding.inflate(inflater, container, false)
|
||||
}
|
||||
|
||||
override fun onViewBindingCreated(
|
||||
binding: FragmentSettingsSourcesBinding,
|
||||
savedInstanceState: Bundle?,
|
||||
) {
|
||||
super.onViewBindingCreated(binding, savedInstanceState)
|
||||
val navConfigAdapter = BaseListAdapter<ListModel>()
|
||||
.addDelegate(ListItemType.NAV_ITEM, navConfigAD(this))
|
||||
.addDelegate(ListItemType.FOOTER_LOADING, navAddAD(this))
|
||||
with(binding.recyclerView) {
|
||||
setHasFixedSize(true)
|
||||
adapter = navConfigAdapter
|
||||
reorderHelper = ItemTouchHelper(ReorderCallback()).also {
|
||||
it.attachToRecyclerView(this)
|
||||
}
|
||||
}
|
||||
viewModel.content.observe(viewLifecycleOwner, navConfigAdapter)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
activity?.setTitle(R.string.main_screen_sections)
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
reorderHelper = null
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
override fun onWindowInsetsChanged(insets: Insets) {
|
||||
requireViewBinding().recyclerView.updatePadding(
|
||||
bottom = insets.bottom,
|
||||
left = insets.left,
|
||||
right = insets.right,
|
||||
)
|
||||
}
|
||||
|
||||
override fun onClick(v: View) {
|
||||
var dialog: DialogInterface? = null
|
||||
val listener = OnListItemClickListener<NavItem> { item, _ ->
|
||||
viewModel.addItem(item)
|
||||
dialog?.dismiss()
|
||||
}
|
||||
dialog = RecyclerViewAlertDialog.Builder<NavItem>(v.context)
|
||||
.setTitle(R.string.add)
|
||||
.addAdapterDelegate(navAvailableAD(listener))
|
||||
.setCancelable(true)
|
||||
.setItems(viewModel.availableItems)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.create()
|
||||
.apply { show() }
|
||||
}
|
||||
|
||||
override fun onItemClick(item: NavItem, view: View) {
|
||||
viewModel.removeItem(item)
|
||||
}
|
||||
|
||||
override fun onItemLongClick(item: NavItem, view: View): Boolean {
|
||||
val holder = viewBinding?.recyclerView?.findContainingViewHolder(view) ?: return false
|
||||
reorderHelper?.startDrag(holder)
|
||||
return true
|
||||
}
|
||||
|
||||
private inner class ReorderCallback : ItemTouchHelper.SimpleCallback(
|
||||
ItemTouchHelper.DOWN or ItemTouchHelper.UP,
|
||||
0,
|
||||
) {
|
||||
|
||||
override fun onMove(
|
||||
recyclerView: RecyclerView,
|
||||
viewHolder: RecyclerView.ViewHolder,
|
||||
target: RecyclerView.ViewHolder,
|
||||
): Boolean = true
|
||||
|
||||
override fun onMoved(
|
||||
recyclerView: RecyclerView,
|
||||
viewHolder: RecyclerView.ViewHolder,
|
||||
fromPos: Int,
|
||||
target: RecyclerView.ViewHolder,
|
||||
toPos: Int,
|
||||
x: Int,
|
||||
y: Int,
|
||||
) {
|
||||
super.onMoved(recyclerView, viewHolder, fromPos, target, toPos, x, y)
|
||||
viewModel.reorder(fromPos, toPos)
|
||||
}
|
||||
|
||||
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) = Unit
|
||||
|
||||
override fun isLongPressDragEnabled() = false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package org.koitharu.kotatsu.settings.nav
|
||||
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.cancelAndJoin
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.plus
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.core.prefs.NavItem
|
||||
import org.koitharu.kotatsu.core.ui.BaseViewModel
|
||||
import org.koitharu.kotatsu.core.ui.util.ActivityRecreationHandle
|
||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
import org.koitharu.kotatsu.main.ui.MainActivity
|
||||
import org.koitharu.kotatsu.parsers.util.move
|
||||
import org.koitharu.kotatsu.settings.nav.model.NavItemAddModel
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class NavConfigViewModel @Inject constructor(
|
||||
private val settings: AppSettings,
|
||||
private val activityRecreationHandle: ActivityRecreationHandle,
|
||||
) : BaseViewModel() {
|
||||
|
||||
private val items = MutableStateFlow(settings.mainNavItems)
|
||||
|
||||
val content: StateFlow<List<ListModel>> = items.map { snapshot ->
|
||||
if (snapshot.size < NavItem.entries.size) {
|
||||
snapshot + NavItemAddModel(snapshot.size < 5)
|
||||
} else {
|
||||
snapshot
|
||||
}
|
||||
}.stateIn(
|
||||
viewModelScope + Dispatchers.Default,
|
||||
SharingStarted.WhileSubscribed(5000),
|
||||
emptyList()
|
||||
)
|
||||
|
||||
private var commitJob: Job? = null
|
||||
|
||||
val availableItems
|
||||
get() = items.value.let { snapshot ->
|
||||
NavItem.entries.filterNot { x -> x in snapshot }
|
||||
}
|
||||
|
||||
fun reorder(fromPos: Int, toPos: Int) {
|
||||
items.value = items.value.toMutableList().apply {
|
||||
move(fromPos, toPos)
|
||||
commit(this)
|
||||
}
|
||||
}
|
||||
|
||||
fun addItem(item: NavItem) {
|
||||
items.value = items.value.plus(item).also {
|
||||
commit(it)
|
||||
}
|
||||
}
|
||||
|
||||
fun removeItem(item: NavItem) {
|
||||
items.value = items.value.minus(item).also {
|
||||
commit(it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun commit(value: List<NavItem>) {
|
||||
val prevJob = commitJob
|
||||
commitJob = launchJob {
|
||||
prevJob?.cancelAndJoin()
|
||||
delay(500)
|
||||
settings.mainNavItems = value
|
||||
activityRecreationHandle.recreate(MainActivity::class.java)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package org.koitharu.kotatsu.settings.nav.adapter
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.prefs.NavItem
|
||||
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.databinding.ItemNavAvailableBinding
|
||||
import org.koitharu.kotatsu.databinding.ItemNavConfigBinding
|
||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
import org.koitharu.kotatsu.settings.nav.model.NavItemAddModel
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
fun navConfigAD(
|
||||
clickListener: OnListItemClickListener<NavItem>,
|
||||
) = adapterDelegateViewBinding<NavItem, ListModel, ItemNavConfigBinding>(
|
||||
{ layoutInflater, parent -> ItemNavConfigBinding.inflate(layoutInflater, parent, false) },
|
||||
) {
|
||||
|
||||
val eventListener = object : View.OnClickListener, View.OnTouchListener {
|
||||
override fun onClick(v: View) = clickListener.onItemClick(item, v)
|
||||
|
||||
override fun onTouch(v: View?, event: MotionEvent): Boolean =
|
||||
event.actionMasked == MotionEvent.ACTION_DOWN &&
|
||||
clickListener.onItemLongClick(item, itemView)
|
||||
}
|
||||
binding.imageViewRemove.setOnClickListener(eventListener)
|
||||
binding.imageViewReorder.setOnTouchListener(eventListener)
|
||||
|
||||
bind {
|
||||
with(binding.textViewTitle) {
|
||||
setText(item.title)
|
||||
setCompoundDrawablesRelativeWithIntrinsicBounds(item.icon, 0, 0, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun navAvailableAD(
|
||||
clickListener: OnListItemClickListener<NavItem>,
|
||||
) = adapterDelegateViewBinding<NavItem, NavItem, ItemNavAvailableBinding>(
|
||||
{ layoutInflater, parent -> ItemNavAvailableBinding.inflate(layoutInflater, parent, false) },
|
||||
) {
|
||||
|
||||
binding.root.setOnClickListener { v ->
|
||||
clickListener.onItemClick(item, v)
|
||||
}
|
||||
|
||||
bind {
|
||||
with(binding.root) {
|
||||
setText(item.title)
|
||||
setCompoundDrawablesRelativeWithIntrinsicBounds(item.icon, 0, 0, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun navAddAD(
|
||||
clickListener: View.OnClickListener,
|
||||
) = adapterDelegateViewBinding<NavItemAddModel, ListModel, ItemNavAvailableBinding>(
|
||||
{ layoutInflater, parent -> ItemNavAvailableBinding.inflate(layoutInflater, parent, false) },
|
||||
) {
|
||||
|
||||
binding.root.setOnClickListener(clickListener)
|
||||
binding.root.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_add, 0, 0, 0)
|
||||
|
||||
bind {
|
||||
with(binding.root) {
|
||||
setText(if (item.canAdd) R.string.add else R.string.items_limit_exceeded)
|
||||
isEnabled = item.canAdd
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package org.koitharu.kotatsu.settings.nav.model
|
||||
|
||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
|
||||
data class NavItemAddModel(
|
||||
val canAdd: Boolean,
|
||||
) : ListModel {
|
||||
|
||||
override fun areItemsTheSame(other: ListModel): Boolean = other is NavItemAddModel
|
||||
}
|
||||
@@ -27,7 +27,11 @@ class SuggestionsFragment : MangaListFragment() {
|
||||
|
||||
override fun onScrolledToEnd() = Unit
|
||||
|
||||
override fun onCreateActionMode(controller: ListSelectionController, mode: ActionMode, menu: Menu): Boolean {
|
||||
override fun onCreateActionMode(
|
||||
controller: ListSelectionController,
|
||||
mode: ActionMode,
|
||||
menu: Menu,
|
||||
): Boolean {
|
||||
mode.menuInflater.inflate(R.menu.mode_remote, menu)
|
||||
return super.onCreateActionMode(controller, mode, menu)
|
||||
}
|
||||
@@ -38,6 +42,12 @@ class SuggestionsFragment : MangaListFragment() {
|
||||
menuInflater.inflate(R.menu.opt_suggestions, menu)
|
||||
}
|
||||
|
||||
override fun onPrepareMenu(menu: Menu) {
|
||||
super.onPrepareMenu(menu)
|
||||
menu.findItem(R.id.action_settings_suggestions)?.isVisible =
|
||||
menu.findItem(R.id.action_settings) == null
|
||||
}
|
||||
|
||||
override fun onMenuItemSelected(menuItem: MenuItem): Boolean = when (menuItem.itemId) {
|
||||
R.id.action_update -> {
|
||||
viewModel.updateSuggestions()
|
||||
@@ -49,7 +59,7 @@ class SuggestionsFragment : MangaListFragment() {
|
||||
true
|
||||
}
|
||||
|
||||
R.id.action_settings -> {
|
||||
R.id.action_settings_suggestions -> {
|
||||
startActivity(SettingsActivity.newSuggestionsSettingsIntent(requireContext()))
|
||||
true
|
||||
}
|
||||
@@ -60,6 +70,12 @@ class SuggestionsFragment : MangaListFragment() {
|
||||
|
||||
companion object {
|
||||
|
||||
@Deprecated(
|
||||
"", ReplaceWith(
|
||||
"SuggestionsFragment()",
|
||||
"org.koitharu.kotatsu.suggestions.ui.SuggestionsFragment"
|
||||
)
|
||||
)
|
||||
fun newInstance() = SuggestionsFragment()
|
||||
}
|
||||
}
|
||||
|
||||
12
app/src/main/res/drawable/ic_bookmark_checked.xml
Normal file
12
app/src/main/res/drawable/ic_bookmark_checked.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- drawable/bookmark.xml -->
|
||||
<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="#000000"
|
||||
android:pathData="M17,3H7A2,2 0 0,0 5,5V21L12,18L19,21V5C19,3.89 18.1,3 17,3Z" />
|
||||
</vector>
|
||||
12
app/src/main/res/drawable/ic_bookmark_selector.xml
Normal file
12
app/src/main/res/drawable/ic_bookmark_selector.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item
|
||||
android:id="@+id/normal"
|
||||
android:drawable="@drawable/ic_bookmark"
|
||||
android:state_checked="false" />
|
||||
|
||||
<item
|
||||
android:id="@+id/checked"
|
||||
android:drawable="@drawable/ic_bookmark_checked"
|
||||
android:state_checked="true" />
|
||||
</selector>
|
||||
12
app/src/main/res/drawable/ic_storage_checked.xml
Normal file
12
app/src/main/res/drawable/ic_storage_checked.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- drawable/sd.xml -->
|
||||
<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="#000000"
|
||||
android:pathData="M18,8H16V4H18M15,8H13V4H15M12,8H10V4H12M18,2H10L4,8V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V4A2,2 0 0,0 18,2Z" />
|
||||
</vector>
|
||||
12
app/src/main/res/drawable/ic_storage_selector.xml
Normal file
12
app/src/main/res/drawable/ic_storage_selector.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item
|
||||
android:id="@+id/normal"
|
||||
android:drawable="@drawable/ic_storage"
|
||||
android:state_checked="false" />
|
||||
|
||||
<item
|
||||
android:id="@+id/checked"
|
||||
android:drawable="@drawable/ic_storage_checked"
|
||||
android:state_checked="true" />
|
||||
</selector>
|
||||
@@ -9,4 +9,4 @@
|
||||
<path
|
||||
android:fillColor="#000"
|
||||
android:pathData="M12 2a7 7 0 0 1 7 7c0 2.38-1.19 4.47-3 5.74V17a1 1 0 0 1-1 1H9a1 1 0 0 1-1-1v-2.26C6.19 13.47 5 11.38 5 9a7 7 0 0 1 7-7M9 21v-1h6v1a1 1 0 0 1-1 1h-4a1 1 0 0 1-1-1m3-17a5 5 0 0 0-5 5c0 2.05 1.23 3.81 3 4.58V16h4v-2.42c1.77-0.77 3-2.53 3-4.58a5 5 0 0 0-5-5z" />
|
||||
</vector>
|
||||
</vector>
|
||||
|
||||
12
app/src/main/res/drawable/ic_suggestion_checked.xml
Normal file
12
app/src/main/res/drawable/ic_suggestion_checked.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- drawable/lightbulb.xml -->
|
||||
<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="#000000"
|
||||
android:pathData="M12,2A7,7 0 0,0 5,9C5,11.38 6.19,13.47 8,14.74V17A1,1 0 0,0 9,18H15A1,1 0 0,0 16,17V14.74C17.81,13.47 19,11.38 19,9A7,7 0 0,0 12,2M9,21A1,1 0 0,0 10,22H14A1,1 0 0,0 15,21V20H9V21Z" />
|
||||
</vector>
|
||||
13
app/src/main/res/drawable/ic_suggestion_selector.xml
Normal file
13
app/src/main/res/drawable/ic_suggestion_selector.xml
Normal file
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item
|
||||
android:id="@+id/normal"
|
||||
android:drawable="@drawable/ic_suggestion"
|
||||
android:state_checked="false" />
|
||||
|
||||
<item
|
||||
android:id="@+id/checked"
|
||||
android:drawable="@drawable/ic_suggestion_checked"
|
||||
android:state_checked="true" />
|
||||
</selector>
|
||||
|
||||
19
app/src/main/res/layout/item_nav_available.xml
Normal file
19
app/src/main/res/layout/item_nav_available.xml
Normal file
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<TextView
|
||||
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="?selectableItemBackground"
|
||||
android:drawablePadding="?listPreferredItemPaddingStart"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center_vertical"
|
||||
android:minHeight="?android:listPreferredItemHeightSmall"
|
||||
android:orientation="horizontal"
|
||||
android:paddingVertical="@dimen/margin_small"
|
||||
android:paddingStart="?listPreferredItemPaddingStart"
|
||||
android:paddingEnd="?listPreferredItemPaddingEnd"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="?attr/textAppearanceBodyLarge"
|
||||
tools:drawableStartCompat="@drawable/ic_feed"
|
||||
tools:text="@string/feed" />
|
||||
47
app/src/main/res/layout/item_nav_config.xml
Normal file
47
app/src/main/res/layout/item_nav_config.xml
Normal file
@@ -0,0 +1,47 @@
|
||||
<?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:background="?android:windowBackground"
|
||||
android:baselineAligned="false"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:paddingVertical="@dimen/margin_small"
|
||||
android:paddingStart="?listPreferredItemPaddingStart"
|
||||
android:paddingEnd="?listPreferredItemPaddingEnd">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView_title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:drawablePadding="?listPreferredItemPaddingStart"
|
||||
android:ellipsize="end"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="?attr/textAppearanceBodyLarge"
|
||||
tools:drawableStart="@drawable/ic_explore_selector"
|
||||
tools:text="@string/explore" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imageView_remove"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?selectableItemBackgroundBorderless"
|
||||
android:contentDescription="@string/remove"
|
||||
android:padding="@dimen/margin_small"
|
||||
android:scaleType="center"
|
||||
android:src="@drawable/ic_delete" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imageView_reorder"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?selectableItemBackgroundBorderless"
|
||||
android:contentDescription="@string/reorder"
|
||||
android:padding="@dimen/margin_small"
|
||||
android:scaleType="center"
|
||||
android:src="@drawable/ic_reorder_handle" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -1,25 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item
|
||||
android:id="@+id/nav_history"
|
||||
android:icon="@drawable/ic_history_selector"
|
||||
android:title="@string/history" />
|
||||
|
||||
<item
|
||||
android:id="@+id/nav_favourites"
|
||||
android:icon="@drawable/ic_favourites_selector"
|
||||
android:title="@string/favourites" />
|
||||
|
||||
<item
|
||||
android:id="@+id/nav_explore"
|
||||
android:icon="@drawable/ic_explore_selector"
|
||||
android:title="@string/explore" />
|
||||
|
||||
<item
|
||||
android:id="@+id/nav_feed"
|
||||
android:icon="@drawable/ic_feed_selector"
|
||||
android:title="@string/feed" />
|
||||
|
||||
</menu>
|
||||
@@ -1,25 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item
|
||||
android:id="@+id/nav_favourites"
|
||||
android:icon="@drawable/ic_favourites_selector"
|
||||
android:title="@string/favourites" />
|
||||
|
||||
<item
|
||||
android:id="@+id/nav_history"
|
||||
android:icon="@drawable/ic_history_selector"
|
||||
android:title="@string/history" />
|
||||
|
||||
<item
|
||||
android:id="@+id/nav_explore"
|
||||
android:icon="@drawable/ic_explore_selector"
|
||||
android:title="@string/explore" />
|
||||
|
||||
<item
|
||||
android:id="@+id/nav_feed"
|
||||
android:icon="@drawable/ic_feed_selector"
|
||||
android:title="@string/feed" />
|
||||
|
||||
</menu>
|
||||
@@ -10,9 +10,9 @@
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_settings"
|
||||
android:orderInCategory="100"
|
||||
android:title="@string/settings"
|
||||
android:id="@+id/action_directories"
|
||||
android:orderInCategory="96"
|
||||
android:title="@string/directories"
|
||||
app:showAsAction="never" />
|
||||
|
||||
</menu>
|
||||
|
||||
@@ -10,9 +10,9 @@
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_settings"
|
||||
android:id="@+id/action_settings_suggestions"
|
||||
android:orderInCategory="90"
|
||||
android:title="@string/settings"
|
||||
app:showAsAction="never" />
|
||||
|
||||
</menu>
|
||||
</menu>
|
||||
|
||||
@@ -6,4 +6,12 @@
|
||||
<item name="fast_scroller" type="id" />
|
||||
<item name="group_branches" type="id" />
|
||||
<item name="layout_tip" type="id" />
|
||||
<!-- Navigation -->
|
||||
<item name="nav_history" type="id" />
|
||||
<item name="nav_favorites" type="id" />
|
||||
<item name="nav_local" type="id" />
|
||||
<item name="nav_explore" type="id" />
|
||||
<item name="nav_feed" type="id" />
|
||||
<item name="nav_suggestions" type="id" />
|
||||
<item name="nav_bookmarks" type="id" />
|
||||
</resources>
|
||||
|
||||
@@ -479,4 +479,8 @@
|
||||
<string name="default_section">Default section</string>
|
||||
<string name="manga_list">Manga list</string>
|
||||
<string name="error_corrupted_file">Invalid data is returned or file is corrupted</string>
|
||||
<string name="on_device">On device</string>
|
||||
<string name="directories">Directories</string>
|
||||
<string name="main_screen_sections">Main screen sections</string>
|
||||
<string name="items_limit_exceeded">No more items can be added</string>
|
||||
</resources>
|
||||
|
||||
@@ -55,14 +55,11 @@
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
<ListPreference
|
||||
android:defaultValue="0"
|
||||
android:entries="@array/first_nav_item"
|
||||
android:entryValues="@array/values_first_nav_item"
|
||||
android:key="nav_first"
|
||||
android:title="@string/default_section"
|
||||
app:allowDividerAbove="true"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
<PreferenceScreen
|
||||
android:fragment="org.koitharu.kotatsu.settings.nav.NavConfigFragment"
|
||||
android:key="nav_main"
|
||||
android:title="@string/main_screen_sections"
|
||||
app:allowDividerAbove="true" />
|
||||
|
||||
<org.koitharu.kotatsu.settings.utils.ActivityListPreference
|
||||
android:key="app_locale"
|
||||
|
||||
Reference in New Issue
Block a user