Configurable main navigation

pull/488/head
Koitharu 3 years ago
parent 4c2197aa5d
commit 95547a8d03
Signed by: Koitharu
GPG Key ID: 676DEE768C17A9D7

@ -61,11 +61,17 @@ class BookmarksFragment :
private var bookmarksAdapter: BookmarksAdapter? = null private var bookmarksAdapter: BookmarksAdapter? = null
private var selectionController: ListSelectionController? = 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) return FragmentListSimpleBinding.inflate(inflater, container, false)
} }
override fun onViewBindingCreated(binding: FragmentListSimpleBinding, savedInstanceState: Bundle?) { override fun onViewBindingCreated(
binding: FragmentListSimpleBinding,
savedInstanceState: Bundle?,
) {
super.onViewBindingCreated(binding, savedInstanceState) super.onViewBindingCreated(binding, savedInstanceState)
selectionController = ListSelectionController( selectionController = ListSelectionController(
activity = requireActivity(), activity = requireActivity(),
@ -95,7 +101,10 @@ class BookmarksFragment :
viewModel.content.observe(viewLifecycleOwner) { viewModel.content.observe(viewLifecycleOwner) {
bookmarksAdapter?.setItems(it, spanSizeLookup) 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) viewModel.onActionDone.observeEvent(viewLifecycleOwner, ::onActionDone)
} }
@ -139,12 +148,20 @@ class BookmarksFragment :
requireViewBinding().recyclerView.invalidateItemDecorations() 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) mode.menuInflater.inflate(R.menu.mode_bookmarks, menu)
return true 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) { return when (item.itemId) {
R.id.action_remove -> { R.id.action_remove -> {
val ids = selectionController?.snapshot() ?: return false val ids = selectionController?.snapshot() ?: return false
@ -170,7 +187,8 @@ class BookmarksFragment :
private fun onActionDone(action: ReversibleAction) { private fun onActionDone(action: ReversibleAction) {
val handle = action.handle val handle = action.handle
val length = if (handle == null) Snackbar.LENGTH_SHORT else Snackbar.LENGTH_LONG 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) { if (handle != null) {
snackbar.setAction(R.string.undo) { handle.reverseAsync() } snackbar.setAction(R.string.undo) { handle.reverseAsync() }
} }
@ -185,7 +203,8 @@ class BookmarksFragment :
} }
override fun getSpanSize(position: Int): Int { 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)) { return when (bookmarksAdapter?.getItemViewType(position)) {
ListItemType.PAGE_THUMB.ordinal -> 1 ListItemType.PAGE_THUMB.ordinal -> 1
else -> total else -> total
@ -200,6 +219,12 @@ class BookmarksFragment :
companion object { companion object {
@Deprecated(
"", ReplaceWith(
"BookmarksFragment()",
"org.koitharu.kotatsu.bookmarks.ui.BookmarksFragment"
)
)
fun newInstance() = 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.core.util.ext.toUriOrNull
import org.koitharu.kotatsu.history.domain.model.HistoryOrder import org.koitharu.kotatsu.history.domain.model.HistoryOrder
import org.koitharu.kotatsu.parsers.model.SortOrder 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.mapNotNullToSet
import org.koitharu.kotatsu.parsers.util.mapToSet import org.koitharu.kotatsu.parsers.util.mapToSet
import java.io.File import java.io.File
@ -43,7 +44,8 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
set(value) = prefs.edit { putEnumValue(KEY_LIST_MODE, value) } set(value) = prefs.edit { putEnumValue(KEY_LIST_MODE, value) }
val theme: Int 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 val colorScheme: ColorScheme
get() = prefs.getEnumValue(KEY_COLOR_THEME, ColorScheme.default) get() = prefs.getEnumValue(KEY_COLOR_THEME, ColorScheme.default)
@ -51,8 +53,20 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
val isAmoledTheme: Boolean val isAmoledTheme: Boolean
get() = prefs.getBoolean(KEY_THEME_AMOLED, false) get() = prefs.getBoolean(KEY_THEME_AMOLED, false)
val isFavoritesNavItemFirst: Boolean var mainNavItems: List<NavItem>
get() = (prefs.getString(KEY_FIRST_NAV_ITEM, null)?.toIntOrNull() ?: 0) == 1 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 var gridSize: Int
get() = prefs.getInt(KEY_GRID_SIZE, 100) get() = prefs.getInt(KEY_GRID_SIZE, 100)
@ -145,7 +159,11 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
var appPassword: String? var appPassword: String?
get() = prefs.getString(KEY_APP_PASSWORD, null) 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 val isLoggingEnabled: Boolean
get() = prefs.getBoolean(KEY_LOGGING_ENABLED, false) get() = prefs.getBoolean(KEY_LOGGING_ENABLED, false)
@ -171,7 +189,8 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
if (isBackgroundNetworkRestricted()) { if (isBackgroundNetworkRestricted()) {
return false 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) return policy.isNetworkAllowed(connectivityManager)
} }
@ -292,14 +311,22 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
@get:FloatRange(from = 0.0, to = 1.0) @get:FloatRange(from = 0.0, to = 1.0)
var readerAutoscrollSpeed: Float var readerAutoscrollSpeed: Float
get() = prefs.getFloat(KEY_READER_AUTOSCROLL_SPEED, 0f) 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 val isPagesPreloadEnabled: Boolean
get() { get() {
if (isBackgroundNetworkRestricted()) { if (isBackgroundNetworkRestricted()) {
return false 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) 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_LOCAL_MANGA_DIRS = "local_manga_dirs"
const val KEY_DISABLE_NSFW = "no_nsfw" const val KEY_DISABLE_NSFW = "no_nsfw"
const val KEY_RELATED_MANGA = "related_manga" const val KEY_RELATED_MANGA = "related_manga"
const val KEY_FIRST_NAV_ITEM = "nav_first" const val KEY_NAV_MAIN = "nav_main"
// About // About
const val KEY_APP_UPDATE = "app_update" 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, DOWNLOAD,
CATEGORY_LARGE, CATEGORY_LARGE,
MANGA_SCROBBLING, MANGA_SCROBBLING,
NAV_ITEM,
} }

@ -13,34 +13,39 @@ class TypedListSpacingDecoration(
) : ItemDecoration() { ) : ItemDecoration() {
private val spacingSmall = context.resources.getDimensionPixelOffset(R.dimen.list_spacing_small) 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) private val spacingLarge = context.resources.getDimensionPixelOffset(R.dimen.list_spacing_large)
override fun getItemOffsets( override fun getItemOffsets(
outRect: Rect, outRect: Rect,
view: View, view: View,
parent: RecyclerView, parent: RecyclerView,
state: RecyclerView.State state: RecyclerView.State,
) { ) {
val itemType = parent.getChildViewHolder(view)?.itemViewType?.let { val itemType = parent.getChildViewHolder(view)?.itemViewType?.let {
ListItemType.entries.getOrNull(it) ListItemType.entries.getOrNull(it)
} }
when (itemType) { when (itemType) {
ListItemType.FILTER_SORT, ListItemType.FILTER_SORT,
ListItemType.FILTER_TAG -> outRect.set(0) ListItemType.FILTER_TAG,
-> outRect.set(0)
ListItemType.HEADER, ListItemType.HEADER,
ListItemType.FEED, ListItemType.FEED,
ListItemType.EXPLORE_SOURCE_LIST, ListItemType.EXPLORE_SOURCE_LIST,
ListItemType.MANGA_SCROBBLING, ListItemType.MANGA_SCROBBLING,
ListItemType.MANGA_LIST -> outRect.set(0) ListItemType.MANGA_LIST,
-> outRect.set(0)
ListItemType.DOWNLOAD, ListItemType.DOWNLOAD,
ListItemType.HINT_EMPTY, ListItemType.HINT_EMPTY,
ListItemType.MANGA_LIST_DETAILED -> outRect.set(spacingNormal) ListItemType.MANGA_LIST_DETAILED,
-> outRect.set(spacingNormal)
ListItemType.PAGE_THUMB, ListItemType.PAGE_THUMB,
ListItemType.MANGA_GRID -> outRect.set(spacingNormal) ListItemType.MANGA_GRID,
-> outRect.set(spacingNormal)
ListItemType.EXPLORE_BUTTONS -> outRect.set(spacingNormal) ListItemType.EXPLORE_BUTTONS -> outRect.set(spacingNormal)
@ -53,7 +58,9 @@ class TypedListSpacingDecoration(
ListItemType.EXPLORE_SUGGESTION, ListItemType.EXPLORE_SUGGESTION,
ListItemType.MANGA_NESTED_GROUP, ListItemType.MANGA_NESTED_GROUP,
ListItemType.CATEGORY_LARGE, ListItemType.CATEGORY_LARGE,
null -> outRect.set(0) ListItemType.NAV_ITEM,
null,
-> outRect.set(0)
ListItemType.TIP -> outRect.set(0) // TODO ListItemType.TIP -> outRect.set(0) // TODO
} }

@ -47,12 +47,20 @@ class LocalListFragment : MangaListFragment(), FilterOwner {
override fun onScrolledToEnd() = viewModel.loadNextPage() 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) mode.menuInflater.inflate(R.menu.mode_local, menu)
return super.onCreateActionMode(controller, mode, 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) { return when (item.itemId) {
R.id.action_remove -> { R.id.action_remove -> {
showDeletionConfirm(selectedItemsIds, mode) showDeletionConfirm(selectedItemsIds, mode)
@ -83,13 +91,20 @@ class LocalListFragment : MangaListFragment(), FilterOwner {
} }
private fun onItemRemoved() { 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 { companion object {
fun newInstance() = LocalListFragment().withArgs(1) { 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.R
import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver
import org.koitharu.kotatsu.core.prefs.AppSettings 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.BaseActivity
import org.koitharu.kotatsu.core.ui.util.MenuInvalidator import org.koitharu.kotatsu.core.ui.util.MenuInvalidator
import org.koitharu.kotatsu.core.ui.util.OptionsMenuBadgeHelper 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" private const val TAG_SEARCH = "search"
@AndroidEntryPoint @AndroidEntryPoint
class MainActivity : BaseActivity<ActivityMainBinding>(), AppBarOwner, BottomNavOwner, View.OnClickListener, class MainActivity : BaseActivity<ActivityMainBinding>(), AppBarOwner, BottomNavOwner,
View.OnFocusChangeListener, SearchSuggestionListener, MainNavigationDelegate.OnFragmentChangedListener { View.OnClickListener,
View.OnFocusChangeListener, SearchSuggestionListener,
MainNavigationDelegate.OnFragmentChangedListener {
@Inject @Inject
lateinit var settings: AppSettings lateinit var settings: AppSettings
@ -119,7 +122,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>(), AppBarOwner, BottomNav
settings = settings, settings = settings,
) )
navigationDelegate.addOnFragmentChangedListener(this) navigationDelegate.addOnFragmentChangedListener(this)
navigationDelegate.onCreate(savedInstanceState) navigationDelegate.onCreate(this, savedInstanceState)
appUpdateBadge = OptionsMenuBadgeHelper(viewBinding.toolbar, R.id.action_app_update) 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.isResumeEnabled.observe(this, this::onResumeEnabledChanged)
viewModel.counters.observe(this, ::onCountersChanged) viewModel.counters.observe(this, ::onCountersChanged)
viewModel.appUpdate.observe(this, MenuInvalidator(this)) viewModel.appUpdate.observe(this, MenuInvalidator(this))
viewModel.onFirstStart.observeEvent(this) { OnboardDialogFragment.showWelcome(supportFragmentManager) } viewModel.onFirstStart.observeEvent(this) {
viewModel.isFeedAvailable.observe(this, ::onFeedAvailabilityChanged) OnboardDialogFragment.showWelcome(
supportFragmentManager
)
}
searchSuggestionViewModel.isIncognitoModeEnabled.observe(this, this::onIncognitoModeChanged) searchSuggestionViewModel.isIncognitoModeEnabled.observe(this, this::onIncognitoModeChanged)
} }
@ -166,7 +172,8 @@ class MainActivity : BaseActivity<ActivityMainBinding>(), AppBarOwner, BottomNav
if (menu == null) { if (menu == null) {
return false 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 val hasAppUpdate = viewModel.appUpdate.value != null
menu.findItem(R.id.action_app_update)?.isVisible = hasAppUpdate menu.findItem(R.id.action_app_update)?.isVisible = hasAppUpdate
appUpdateBadge.setBadgeVisible(hasAppUpdate) appUpdateBadge.setBadgeVisible(hasAppUpdate)
@ -279,17 +286,12 @@ class MainActivity : BaseActivity<ActivityMainBinding>(), AppBarOwner, BottomNav
startActivity(IntentBuilder(this).manga(manga).build(), options) startActivity(IntentBuilder(this).manga(manga).build(), options)
} }
private fun onCountersChanged(counters: IntArray) { private fun onCountersChanged(counters: Map<NavItem, Int>) {
repeat(counters.size) { i -> counters.forEach { (navItem, counter) ->
val counter = counters[i] navigationDelegate.setCounter(navItem, counter)
navigationDelegate.setCounterAt(i, counter)
} }
} }
private fun onFeedAvailabilityChanged(isFeedAvailable: Boolean) {
navigationDelegate.setItemVisibility(R.id.nav_feed, isFeedAvailable)
}
private fun onIncognitoModeChanged(isIncognito: Boolean) { private fun onIncognitoModeChanged(isIncognito: Boolean) {
var options = viewBinding.searchView.imeOptions var options = viewBinding.searchView.imeOptions
options = if (isIncognito) { options = if (isIncognito) {
@ -362,8 +364,12 @@ class MainActivity : BaseActivity<ActivityMainBinding>(), AppBarOwner, BottomNav
} else { } else {
SCROLL_FLAG_SCROLL or SCROLL_FLAG_ENTER_ALWAYS or SCROLL_FLAG_SNAP SCROLL_FLAG_SCROLL or SCROLL_FLAG_ENTER_ALWAYS or SCROLL_FLAG_SNAP
} }
viewBinding.toolbarCard.updateLayoutParams<AppBarLayout.LayoutParams> { scrollFlags = appBarScrollFlags } viewBinding.toolbarCard.updateLayoutParams<AppBarLayout.LayoutParams> {
viewBinding.insetsHolder.updateLayoutParams<AppBarLayout.LayoutParams> { scrollFlags = appBarScrollFlags } scrollFlags = appBarScrollFlags
}
viewBinding.insetsHolder.updateLayoutParams<AppBarLayout.LayoutParams> {
scrollFlags = appBarScrollFlags
}
viewBinding.toolbarCard.background = if (isOpened) { viewBinding.toolbarCard.background = if (isOpened) {
null null
} else { } else {
@ -387,7 +393,11 @@ class MainActivity : BaseActivity<ActivityMainBinding>(), AppBarOwner, BottomNav
Manifest.permission.POST_NOTIFICATIONS, Manifest.permission.POST_NOTIFICATIONS,
) != PERMISSION_GRANTED ) != 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 package org.koitharu.kotatsu.main.ui
import android.os.Bundle import android.os.Bundle
import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import androidx.activity.OnBackPressedCallback import androidx.activity.OnBackPressedCallback
import androidx.annotation.IdRes import androidx.annotation.IdRes
@ -8,16 +9,28 @@ import androidx.core.view.isEmpty
import androidx.core.view.iterator import androidx.core.view.iterator
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager 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.navigation.NavigationBarView
import com.google.android.material.transition.MaterialFadeThrough 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.R
import org.koitharu.kotatsu.bookmarks.ui.BookmarksFragment
import org.koitharu.kotatsu.core.prefs.AppSettings 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.ui.util.RecyclerViewOwner
import org.koitharu.kotatsu.core.util.ext.firstVisibleItemPosition import org.koitharu.kotatsu.core.util.ext.firstVisibleItemPosition
import org.koitharu.kotatsu.core.util.ext.isAnimationsEnabled import org.koitharu.kotatsu.core.util.ext.isAnimationsEnabled
import org.koitharu.kotatsu.explore.ui.ExploreFragment import org.koitharu.kotatsu.explore.ui.ExploreFragment
import org.koitharu.kotatsu.favourites.ui.container.FavouritesContainerFragment import org.koitharu.kotatsu.favourites.ui.container.FavouritesContainerFragment
import org.koitharu.kotatsu.history.ui.HistoryListFragment 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 org.koitharu.kotatsu.tracker.ui.feed.FeedFragment
import java.util.LinkedList import java.util.LinkedList
@ -62,11 +75,11 @@ class MainNavigationDelegate(
navBar.selectedItemId = R.id.nav_history navBar.selectedItemId = R.id.nav_history
} }
fun onCreate(savedInstanceState: Bundle?) { fun onCreate(lifecycleOwner: LifecycleOwner, savedInstanceState: Bundle?) {
if (navBar.menu.isEmpty()) { if (navBar.menu.isEmpty()) {
val menuRes = if (settings.isFavoritesNavItemFirst) R.menu.nav_bottom_alt else R.menu.nav_bottom createMenu(settings.mainNavItems, navBar.menu)
navBar.inflateMenu(menuRes)
} }
observeSettings(lifecycleOwner)
val fragment = primaryFragment val fragment = primaryFragment
if (fragment != null) { if (fragment != null) {
onFragmentChanged(fragment, fromUser = false) onFragmentChanged(fragment, fromUser = false)
@ -84,12 +97,11 @@ class MainNavigationDelegate(
} }
} }
fun setCounterAt(position: Int, counter: Int) { fun setCounter(item: NavItem, counter: Int) {
val id = navBar.menu.getItem(position).itemId setCounter(item.id, counter)
setCounter(id, counter)
} }
fun setCounter(@IdRes id: Int, counter: Int) { private fun setCounter(@IdRes id: Int, counter: Int) {
if (counter == 0) { if (counter == 0) {
navBar.getBadge(id)?.isVisible = false navBar.getBadge(id)?.isVisible = false
} else { } else {
@ -123,9 +135,12 @@ class MainNavigationDelegate(
return setPrimaryFragment( return setPrimaryFragment(
when (itemId) { when (itemId) {
R.id.nav_history -> HistoryListFragment() R.id.nav_history -> HistoryListFragment()
R.id.nav_favourites -> FavouritesContainerFragment() R.id.nav_favorites -> FavouritesContainerFragment()
R.id.nav_explore -> ExploreFragment() R.id.nav_explore -> ExploreFragment()
R.id.nav_feed -> FeedFragment() R.id.nav_feed -> FeedFragment()
R.id.nav_local -> LocalListFragment.newInstance()
R.id.nav_suggestions -> SuggestionsFragment()
R.id.nav_bookmarks -> BookmarksFragment()
else -> return false else -> return false
}, },
) )
@ -133,9 +148,12 @@ class MainNavigationDelegate(
private fun getItemId(fragment: Fragment) = when (fragment) { private fun getItemId(fragment: Fragment) = when (fragment) {
is HistoryListFragment -> R.id.nav_history 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 ExploreFragment -> R.id.nav_explore
is FeedFragment -> R.id.nav_feed 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 else -> 0
} }
@ -157,6 +175,24 @@ class MainNavigationDelegate(
listeners.forEach { it.onFragmentChanged(fragment, fromUser) } 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? { private fun firstItem(): MenuItem? {
val menu = navBar.menu val menu = navBar.menu
for (item in 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.exceptions.EmptyHistoryException
import org.koitharu.kotatsu.core.github.AppUpdateRepository import org.koitharu.kotatsu.core.github.AppUpdateRepository
import org.koitharu.kotatsu.core.prefs.AppSettings 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.ui.BaseViewModel
import org.koitharu.kotatsu.core.util.ext.MutableEventFlow import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
import org.koitharu.kotatsu.core.util.ext.call 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.main.domain.ReadingResumeEnabledUseCase
import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.tracker.domain.TrackingRepository import org.koitharu.kotatsu.tracker.domain.TrackingRepository
import java.util.EnumMap
import javax.inject.Inject import javax.inject.Inject
@HiltViewModel @HiltViewModel
@ -42,23 +43,20 @@ class MainViewModel @Inject constructor(
initialValue = false, initialValue = false,
) )
val isFeedAvailable = settings.observeAsStateFlow(
scope = viewModelScope + Dispatchers.Default,
key = AppSettings.KEY_TRACKER_ENABLED,
valueProducer = { isTrackerEnabled },
)
val appUpdate = appUpdateRepository.observeAvailableUpdate() val appUpdate = appUpdateRepository.observeAvailableUpdate()
val counters = combine( val counters = combine(
trackingRepository.observeUpdatedMangaCount(), trackingRepository.observeUpdatedMangaCount(),
observeNewSourcesCount(), observeNewSourcesCount(),
) { tracks, newSources -> ) { 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( }.stateIn(
scope = viewModelScope + Dispatchers.Default, scope = viewModelScope + Dispatchers.Default,
started = SharingStarted.WhileSubscribed(5000), started = SharingStarted.WhileSubscribed(5000),
initialValue = IntArray(4), initialValue = emptyMap<NavItem, Int>(),
) )
init { 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.postDelayed
import org.koitharu.kotatsu.core.util.ext.setDefaultValueCompat import org.koitharu.kotatsu.core.util.ext.setDefaultValueCompat
import org.koitharu.kotatsu.core.util.ext.toList 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.names
import org.koitharu.kotatsu.parsers.util.toTitleCase import org.koitharu.kotatsu.parsers.util.toTitleCase
import org.koitharu.kotatsu.settings.utils.ActivityListPreference import org.koitharu.kotatsu.settings.utils.ActivityListPreference
@ -67,6 +66,7 @@ class AppearanceSettingsFragment :
} }
setDefaultValueCompat("") setDefaultValueCompat("")
} }
bindNavSummary()
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -86,7 +86,8 @@ class AppearanceSettingsFragment :
} }
AppSettings.KEY_COLOR_THEME, AppSettings.KEY_COLOR_THEME,
AppSettings.KEY_THEME_AMOLED -> { AppSettings.KEY_THEME_AMOLED,
-> {
postRestart() postRestart()
} }
@ -94,8 +95,8 @@ class AppearanceSettingsFragment :
AppCompatDelegate.setApplicationLocales(settings.appLocales) AppCompatDelegate.setApplicationLocales(settings.appLocales)
} }
AppSettings.KEY_FIRST_NAV_ITEM -> { AppSettings.KEY_NAV_MAIN -> {
activityRecreationHandle.recreate(MainActivity::class.java) 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 class LocaleComparator(context: Context) : Comparator<Locale> {
private val deviceLocales = LocaleManagerCompat.getSystemLocales(context) 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 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) mode.menuInflater.inflate(R.menu.mode_remote, menu)
return super.onCreateActionMode(controller, mode, menu) return super.onCreateActionMode(controller, mode, menu)
} }
@ -38,6 +42,12 @@ class SuggestionsFragment : MangaListFragment() {
menuInflater.inflate(R.menu.opt_suggestions, menu) 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) { override fun onMenuItemSelected(menuItem: MenuItem): Boolean = when (menuItem.itemId) {
R.id.action_update -> { R.id.action_update -> {
viewModel.updateSuggestions() viewModel.updateSuggestions()
@ -49,7 +59,7 @@ class SuggestionsFragment : MangaListFragment() {
true true
} }
R.id.action_settings -> { R.id.action_settings_suggestions -> {
startActivity(SettingsActivity.newSuggestionsSettingsIntent(requireContext())) startActivity(SettingsActivity.newSuggestionsSettingsIntent(requireContext()))
true true
} }
@ -60,6 +70,12 @@ class SuggestionsFragment : MangaListFragment() {
companion object { companion object {
@Deprecated(
"", ReplaceWith(
"SuggestionsFragment()",
"org.koitharu.kotatsu.suggestions.ui.SuggestionsFragment"
)
)
fun newInstance() = SuggestionsFragment() fun newInstance() = SuggestionsFragment()
} }
} }

@ -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>

@ -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>

@ -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>

@ -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>

@ -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>

@ -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>

@ -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" />

@ -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" /> app:showAsAction="never" />
<item <item
android:id="@+id/action_settings" android:id="@+id/action_directories"
android:orderInCategory="100" android:orderInCategory="96"
android:title="@string/settings" android:title="@string/directories"
app:showAsAction="never" /> app:showAsAction="never" />
</menu> </menu>

@ -10,7 +10,7 @@
app:showAsAction="never" /> app:showAsAction="never" />
<item <item
android:id="@+id/action_settings" android:id="@+id/action_settings_suggestions"
android:orderInCategory="90" android:orderInCategory="90"
android:title="@string/settings" android:title="@string/settings"
app:showAsAction="never" /> app:showAsAction="never" />

@ -6,4 +6,12 @@
<item name="fast_scroller" type="id" /> <item name="fast_scroller" type="id" />
<item name="group_branches" type="id" /> <item name="group_branches" type="id" />
<item name="layout_tip" 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> </resources>

@ -479,4 +479,8 @@
<string name="default_section">Default section</string> <string name="default_section">Default section</string>
<string name="manga_list">Manga list</string> <string name="manga_list">Manga list</string>
<string name="error_corrupted_file">Invalid data is returned or file is corrupted</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> </resources>

@ -55,14 +55,11 @@
</PreferenceCategory> </PreferenceCategory>
<ListPreference <PreferenceScreen
android:defaultValue="0" android:fragment="org.koitharu.kotatsu.settings.nav.NavConfigFragment"
android:entries="@array/first_nav_item" android:key="nav_main"
android:entryValues="@array/values_first_nav_item" android:title="@string/main_screen_sections"
android:key="nav_first" app:allowDividerAbove="true" />
android:title="@string/default_section"
app:allowDividerAbove="true"
app:useSimpleSummaryProvider="true" />
<org.koitharu.kotatsu.settings.utils.ActivityListPreference <org.koitharu.kotatsu.settings.utils.ActivityListPreference
android:key="app_locale" android:key="app_locale"

Loading…
Cancel
Save