diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/ui/BookmarksFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/ui/BookmarksFragment.kt index 8ed2b3298..dbbe008b7 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/ui/BookmarksFragment.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/ui/BookmarksFragment.kt @@ -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() } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/AppSettings.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/AppSettings.kt index 573a17d0a..06dd791d8 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/AppSettings.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/AppSettings.kt @@ -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 + 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" diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/NavItem.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/NavItem.kt new file mode 100644 index 000000000..691c4ed59 --- /dev/null +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/NavItem.kt @@ -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 + } +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/ListItemType.kt b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/ListItemType.kt index 79b9b4d93..b937e0ae7 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/ListItemType.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/ListItemType.kt @@ -25,4 +25,5 @@ enum class ListItemType { DOWNLOAD, CATEGORY_LARGE, MANGA_SCROBBLING, + NAV_ITEM, } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/TypedListSpacingDecoration.kt b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/TypedListSpacingDecoration.kt index 6a99fc7be..72f2aebd4 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/TypedListSpacingDecoration.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/TypedListSpacingDecoration.kt @@ -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 } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/local/ui/LocalListFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/local/ui/LocalListFragment.kt index d02a89b92..44f799d45 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/local/ui/LocalListFragment.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/local/ui/LocalListFragment.kt @@ -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 } } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/main/ui/MainActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/main/ui/MainActivity.kt index c0c543272..ca149eb85 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/main/ui/MainActivity.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/main/ui/MainActivity.kt @@ -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(), AppBarOwner, BottomNavOwner, View.OnClickListener, - View.OnFocusChangeListener, SearchSuggestionListener, MainNavigationDelegate.OnFragmentChangedListener { +class MainActivity : BaseActivity(), AppBarOwner, BottomNavOwner, + View.OnClickListener, + View.OnFocusChangeListener, SearchSuggestionListener, + MainNavigationDelegate.OnFragmentChangedListener { @Inject lateinit var settings: AppSettings @@ -119,7 +122,7 @@ class MainActivity : BaseActivity(), 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(), 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(), 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(), 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) { + 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(), AppBarOwner, BottomNav } else { SCROLL_FLAG_SCROLL or SCROLL_FLAG_ENTER_ALWAYS or SCROLL_FLAG_SNAP } - viewBinding.toolbarCard.updateLayoutParams { scrollFlags = appBarScrollFlags } - viewBinding.insetsHolder.updateLayoutParams { scrollFlags = appBarScrollFlags } + viewBinding.toolbarCard.updateLayoutParams { + scrollFlags = appBarScrollFlags + } + viewBinding.insetsHolder.updateLayoutParams { + scrollFlags = appBarScrollFlags + } viewBinding.toolbarCard.background = if (isOpened) { null } else { @@ -387,7 +393,11 @@ class MainActivity : BaseActivity(), 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 + ) } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/main/ui/MainNavigationDelegate.kt b/app/src/main/kotlin/org/koitharu/kotatsu/main/ui/MainNavigationDelegate.kt index e7e432879..039bb68db 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/main/ui/MainNavigationDelegate.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/main/ui/MainNavigationDelegate.kt @@ -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, 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) { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/main/ui/MainViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/main/ui/MainViewModel.kt index 9b1757cb7..ce5e9748a 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/main/ui/MainViewModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/main/ui/MainViewModel.kt @@ -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::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(), ) init { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/settings/AppearanceSettingsFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/settings/AppearanceSettingsFragment.kt index 67d8a4b78..860078576 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/settings/AppearanceSettingsFragment.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/settings/AppearanceSettingsFragment.kt @@ -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(AppSettings.KEY_NAV_MAIN) ?: return + pref.summary = settings.mainNavItems.joinToString { + getString(it.title) + } + } + private class LocaleComparator(context: Context) : Comparator { private val deviceLocales = LocaleManagerCompat.getSystemLocales(context) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/settings/nav/NavConfigFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/settings/nav/NavConfigFragment.kt new file mode 100644 index 000000000..573232ea4 --- /dev/null +++ b/app/src/main/kotlin/org/koitharu/kotatsu/settings/nav/NavConfigFragment.kt @@ -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(), RecyclerViewOwner, + OnListItemClickListener, View.OnClickListener { + + private var reorderHelper: ItemTouchHelper? = null + private val viewModel by viewModels() + + 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() + .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 { item, _ -> + viewModel.addItem(item) + dialog?.dismiss() + } + dialog = RecyclerViewAlertDialog.Builder(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 + } +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/settings/nav/NavConfigViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/settings/nav/NavConfigViewModel.kt new file mode 100644 index 000000000..9b10d3b92 --- /dev/null +++ b/app/src/main/kotlin/org/koitharu/kotatsu/settings/nav/NavConfigViewModel.kt @@ -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> = 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) { + val prevJob = commitJob + commitJob = launchJob { + prevJob?.cancelAndJoin() + delay(500) + settings.mainNavItems = value + activityRecreationHandle.recreate(MainActivity::class.java) + } + } +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/settings/nav/adapter/NavConfigAD.kt b/app/src/main/kotlin/org/koitharu/kotatsu/settings/nav/adapter/NavConfigAD.kt new file mode 100644 index 000000000..968522805 --- /dev/null +++ b/app/src/main/kotlin/org/koitharu/kotatsu/settings/nav/adapter/NavConfigAD.kt @@ -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, +) = adapterDelegateViewBinding( + { 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, +) = adapterDelegateViewBinding( + { 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( + { 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 + } + } +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/settings/nav/model/NavItemAddModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/settings/nav/model/NavItemAddModel.kt new file mode 100644 index 000000000..7f5656b97 --- /dev/null +++ b/app/src/main/kotlin/org/koitharu/kotatsu/settings/nav/model/NavItemAddModel.kt @@ -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 +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/suggestions/ui/SuggestionsFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/suggestions/ui/SuggestionsFragment.kt index b0f2888e9..2aa161e98 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/suggestions/ui/SuggestionsFragment.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/suggestions/ui/SuggestionsFragment.kt @@ -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() } } diff --git a/app/src/main/res/drawable/ic_bookmark_checked.xml b/app/src/main/res/drawable/ic_bookmark_checked.xml new file mode 100644 index 000000000..fb9fc6467 --- /dev/null +++ b/app/src/main/res/drawable/ic_bookmark_checked.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_bookmark_selector.xml b/app/src/main/res/drawable/ic_bookmark_selector.xml new file mode 100644 index 000000000..e5fac6c34 --- /dev/null +++ b/app/src/main/res/drawable/ic_bookmark_selector.xml @@ -0,0 +1,12 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_storage_checked.xml b/app/src/main/res/drawable/ic_storage_checked.xml new file mode 100644 index 000000000..58a0d9ddc --- /dev/null +++ b/app/src/main/res/drawable/ic_storage_checked.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_storage_selector.xml b/app/src/main/res/drawable/ic_storage_selector.xml new file mode 100644 index 000000000..2f97ef9cf --- /dev/null +++ b/app/src/main/res/drawable/ic_storage_selector.xml @@ -0,0 +1,12 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_suggestion.xml b/app/src/main/res/drawable/ic_suggestion.xml index 877ae301f..d018a746e 100644 --- a/app/src/main/res/drawable/ic_suggestion.xml +++ b/app/src/main/res/drawable/ic_suggestion.xml @@ -9,4 +9,4 @@ - \ No newline at end of file + diff --git a/app/src/main/res/drawable/ic_suggestion_checked.xml b/app/src/main/res/drawable/ic_suggestion_checked.xml new file mode 100644 index 000000000..4712a5070 --- /dev/null +++ b/app/src/main/res/drawable/ic_suggestion_checked.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_suggestion_selector.xml b/app/src/main/res/drawable/ic_suggestion_selector.xml new file mode 100644 index 000000000..7dd48994c --- /dev/null +++ b/app/src/main/res/drawable/ic_suggestion_selector.xml @@ -0,0 +1,13 @@ + + + + + + + diff --git a/app/src/main/res/layout/item_nav_available.xml b/app/src/main/res/layout/item_nav_available.xml new file mode 100644 index 000000000..cd61e1c64 --- /dev/null +++ b/app/src/main/res/layout/item_nav_available.xml @@ -0,0 +1,19 @@ + + diff --git a/app/src/main/res/layout/item_nav_config.xml b/app/src/main/res/layout/item_nav_config.xml new file mode 100644 index 000000000..5da6630ee --- /dev/null +++ b/app/src/main/res/layout/item_nav_config.xml @@ -0,0 +1,47 @@ + + + + + + + + + + diff --git a/app/src/main/res/menu/nav_bottom.xml b/app/src/main/res/menu/nav_bottom.xml deleted file mode 100644 index a7a2e95dd..000000000 --- a/app/src/main/res/menu/nav_bottom.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - diff --git a/app/src/main/res/menu/nav_bottom_alt.xml b/app/src/main/res/menu/nav_bottom_alt.xml deleted file mode 100644 index 2408e6ec0..000000000 --- a/app/src/main/res/menu/nav_bottom_alt.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - diff --git a/app/src/main/res/menu/opt_local.xml b/app/src/main/res/menu/opt_local.xml index af3cce235..e00b4b7d3 100644 --- a/app/src/main/res/menu/opt_local.xml +++ b/app/src/main/res/menu/opt_local.xml @@ -10,9 +10,9 @@ app:showAsAction="never" /> diff --git a/app/src/main/res/menu/opt_suggestions.xml b/app/src/main/res/menu/opt_suggestions.xml index 5e665f49e..3e3b52493 100644 --- a/app/src/main/res/menu/opt_suggestions.xml +++ b/app/src/main/res/menu/opt_suggestions.xml @@ -10,9 +10,9 @@ app:showAsAction="never" /> - \ No newline at end of file + diff --git a/app/src/main/res/values/ids.xml b/app/src/main/res/values/ids.xml index 236011efe..50d4a3b3e 100644 --- a/app/src/main/res/values/ids.xml +++ b/app/src/main/res/values/ids.xml @@ -6,4 +6,12 @@ + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7d73f4bbb..fd893ee00 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -479,4 +479,8 @@ Default section Manga list Invalid data is returned or file is corrupted + On device + Directories + Main screen sections + No more items can be added diff --git a/app/src/main/res/xml/pref_appearance.xml b/app/src/main/res/xml/pref_appearance.xml index cd9182b39..e5dc6b4c0 100644 --- a/app/src/main/res/xml/pref_appearance.xml +++ b/app/src/main/res/xml/pref_appearance.xml @@ -55,14 +55,11 @@ - +