Initial conversion of browse tabs to full Compose

TODO:
- Global search should launch a controller with the search textfield focused. This is pending a Compose rewrite of that screen.
- Better migrate sort UI
- Extensions search
This commit is contained in:
arkon 2022-08-29 17:18:06 -04:00
parent 084e6a964e
commit 92e83f702c
32 changed files with 458 additions and 701 deletions

View file

@ -6,10 +6,9 @@ class SetMigrateSorting(
private val preferences: PreferencesHelper,
) {
fun await(mode: Mode, isAscending: Boolean) {
val direction = if (isAscending) Direction.ASCENDING else Direction.DESCENDING
preferences.migrationSortingDirection().set(direction)
fun await(mode: Mode, direction: Direction) {
preferences.migrationSortingMode().set(mode)
preferences.migrationSortingDirection().set(direction)
}
enum class Mode {

View file

@ -0,0 +1,84 @@
package eu.kanade.presentation.browse
import androidx.annotation.StringRes
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.material3.Tab
import androidx.compose.material3.TabRow
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import com.google.accompanist.pager.HorizontalPager
import com.google.accompanist.pager.rememberPagerState
import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.components.AppBarActions
import eu.kanade.presentation.components.Scaffold
import eu.kanade.presentation.components.TabIndicator
import eu.kanade.presentation.components.TabText
import eu.kanade.tachiyomi.R
import kotlinx.coroutines.launch
@Composable
fun BrowseScreen(
startIndex: Int? = null,
tabs: List<BrowseTab>,
) {
val scope = rememberCoroutineScope()
val state = rememberPagerState()
LaunchedEffect(startIndex) {
if (startIndex != null) {
state.scrollToPage(startIndex)
}
}
Scaffold(
modifier = Modifier.statusBarsPadding(),
topBar = {
AppBar(
title = stringResource(R.string.browse),
actions = {
AppBarActions(tabs[state.currentPage].actions)
},
)
},
) { paddingValues ->
Column(modifier = Modifier.padding(paddingValues)) {
TabRow(
selectedTabIndex = state.currentPage,
indicator = { TabIndicator(it[state.currentPage]) },
) {
tabs.forEachIndexed { index, tab ->
Tab(
selected = state.currentPage == index,
onClick = { scope.launch { state.animateScrollToPage(index) } },
text = {
TabText(stringResource(tab.titleRes), tab.badgeNumber, state.currentPage == index)
},
)
}
}
HorizontalPager(
count = tabs.size,
modifier = Modifier.fillMaxSize(),
state = state,
verticalAlignment = Alignment.Top,
) { page ->
tabs[page].content()
}
}
}
}
data class BrowseTab(
@StringRes val titleRes: Int,
val badgeNumber: Int? = null,
val actions: List<AppBar.Action> = emptyList(),
val content: @Composable () -> Unit,
)

View file

@ -22,15 +22,12 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
@ -57,7 +54,6 @@ import eu.kanade.tachiyomi.util.system.LocaleHelper
@Composable
fun ExtensionScreen(
nestedScrollInterop: NestedScrollConnection,
presenter: ExtensionsPresenter,
onLongClickItem: (Extension) -> Unit,
onClickItemCancel: (Extension) -> Unit,
@ -68,10 +64,8 @@ fun ExtensionScreen(
onOpenExtension: (Extension.Installed) -> Unit,
onClickUpdateAll: () -> Unit,
onRefresh: () -> Unit,
onLaunched: () -> Unit,
) {
SwipeRefresh(
modifier = Modifier.nestedScroll(nestedScrollInterop),
state = rememberSwipeRefreshState(presenter.isRefreshing),
indicator = { s, trigger -> SwipeRefreshIndicator(s, trigger) },
onRefresh = onRefresh,
@ -90,7 +84,6 @@ fun ExtensionScreen(
onTrustExtension = onTrustExtension,
onOpenExtension = onOpenExtension,
onClickUpdateAll = onClickUpdateAll,
onLaunched = onLaunched,
)
}
}
@ -108,7 +101,6 @@ fun ExtensionContent(
onTrustExtension: (Extension.Untrusted) -> Unit,
onOpenExtension: (Extension.Installed) -> Unit,
onClickUpdateAll: () -> Unit,
onLaunched: () -> Unit,
) {
var trustState by remember { mutableStateOf<Extension.Untrusted?>(null) }
@ -187,9 +179,6 @@ fun ExtensionContent(
}
},
)
LaunchedEffect(Unit) {
onLaunched()
}
}
}
}

View file

@ -10,6 +10,7 @@ interface ExtensionsState {
val isLoading: Boolean
val isRefreshing: Boolean
val items: List<ExtensionUiModel>
val updates: Int
val isEmpty: Boolean
}
@ -21,5 +22,6 @@ class ExtensionsStateImpl : ExtensionsState {
override var isLoading: Boolean by mutableStateOf(true)
override var isRefreshing: Boolean by mutableStateOf(false)
override var items: List<ExtensionUiModel> by mutableStateOf(emptyList())
override var updates: Int by mutableStateOf(0)
override val isEmpty: Boolean by derivedStateOf { items.isEmpty() }
}

View file

@ -8,17 +8,17 @@ import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import eu.kanade.domain.source.interactor.SetMigrateSorting
import eu.kanade.domain.source.model.Source
import eu.kanade.presentation.browse.components.BaseSourceItem
import eu.kanade.presentation.browse.components.SourceIcon
@ -39,7 +39,6 @@ import eu.kanade.tachiyomi.util.system.copyToClipboard
@Composable
fun MigrateSourceScreen(
nestedScrollInterop: NestedScrollConnection,
presenter: MigrationSourcesPresenter,
onClickItem: (Source) -> Unit,
) {
@ -49,28 +48,44 @@ fun MigrateSourceScreen(
presenter.isEmpty -> EmptyScreen(textResource = R.string.information_empty_library)
else ->
MigrateSourceList(
nestedScrollInterop = nestedScrollInterop,
list = presenter.items,
onClickItem = onClickItem,
onLongClickItem = { source ->
val sourceId = source.id.toString()
context.copyToClipboard(sourceId, sourceId)
},
sortingMode = presenter.sortingMode,
onToggleSortingMode = { presenter.toggleSortingMode() },
sortingDirection = presenter.sortingDirection,
onToggleSortingDirection = { presenter.toggleSortingDirection() },
)
}
}
@Composable
fun MigrateSourceList(
nestedScrollInterop: NestedScrollConnection,
list: List<Pair<Source, Long>>,
onClickItem: (Source) -> Unit,
onLongClickItem: (Source) -> Unit,
sortingMode: SetMigrateSorting.Mode,
onToggleSortingMode: () -> Unit,
sortingDirection: SetMigrateSorting.Direction,
onToggleSortingDirection: () -> Unit,
) {
ScrollbarLazyColumn(
modifier = Modifier.nestedScroll(nestedScrollInterop),
contentPadding = bottomNavPaddingValues + WindowInsets.navigationBars.asPaddingValues() + topPaddingValues,
) {
stickyHeader {
Row {
Button(onClick = onToggleSortingMode) {
Text(sortingMode.toString())
}
Button(onClick = onToggleSortingDirection) {
Text(sortingDirection.toString())
}
}
}
item(key = "title") {
Text(
text = stringResource(R.string.migration_selection_prompt),

View file

@ -4,12 +4,15 @@ import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import eu.kanade.domain.source.interactor.SetMigrateSorting
import eu.kanade.domain.source.model.Source
interface MigrateSourceState {
val isLoading: Boolean
val items: List<Pair<Source, Long>>
val isEmpty: Boolean
val sortingMode: SetMigrateSorting.Mode
val sortingDirection: SetMigrateSorting.Direction
}
fun MigrateSourceState(): MigrateSourceState {
@ -20,4 +23,6 @@ class MigrateSourceStateImpl : MigrateSourceState {
override var isLoading: Boolean by mutableStateOf(true)
override var items: List<Pair<Source, Long>> by mutableStateOf(emptyList())
override val isEmpty: Boolean by derivedStateOf { items.isEmpty() }
override var sortingMode: SetMigrateSorting.Mode by mutableStateOf(SetMigrateSorting.Mode.ALPHABETICAL)
override var sortingDirection: SetMigrateSorting.Direction by mutableStateOf(SetMigrateSorting.Direction.ASCENDING)
}

View file

@ -21,8 +21,6 @@ import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
@ -40,14 +38,12 @@ import eu.kanade.presentation.util.topPaddingValues
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.LocalSource
import eu.kanade.tachiyomi.ui.browse.source.SourcesPresenter
import eu.kanade.tachiyomi.ui.browse.source.SourcesPresenter.Dialog
import eu.kanade.tachiyomi.util.system.LocaleHelper
import eu.kanade.tachiyomi.util.system.toast
import kotlinx.coroutines.flow.collectLatest
@Composable
fun SourcesScreen(
nestedScrollInterop: NestedScrollConnection,
presenter: SourcesPresenter,
onClickItem: (Source) -> Unit,
onClickDisable: (Source) -> Unit,
@ -60,7 +56,6 @@ fun SourcesScreen(
presenter.isEmpty -> EmptyScreen(R.string.source_empty_screen)
else -> {
SourceList(
nestedScrollConnection = nestedScrollInterop,
state = presenter,
onClickItem = onClickItem,
onClickDisable = onClickDisable,
@ -82,7 +77,6 @@ fun SourcesScreen(
@Composable
fun SourceList(
nestedScrollConnection: NestedScrollConnection,
state: SourcesState,
onClickItem: (Source) -> Unit,
onClickDisable: (Source) -> Unit,
@ -90,7 +84,6 @@ fun SourceList(
onClickPin: (Source) -> Unit,
) {
ScrollbarLazyColumn(
modifier = Modifier.nestedScroll(nestedScrollConnection),
contentPadding = bottomNavPaddingValues + WindowInsets.navigationBars.asPaddingValues() + topPaddingValues,
) {
items(
@ -119,7 +112,7 @@ fun SourceList(
modifier = Modifier.animateItemPlacement(),
source = model.source,
onClickItem = onClickItem,
onLongClickItem = { state.dialog = Dialog(it) },
onLongClickItem = { state.dialog = SourcesPresenter.Dialog(it) },
onClickLatest = onClickLatest,
onClickPin = onClickPin,
)

View file

@ -0,0 +1,50 @@
package eu.kanade.presentation.components
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.TabPosition
import androidx.compose.material3.TabRowDefaults
import androidx.compose.material3.TabRowDefaults.tabIndicatorOffset
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@Composable
fun TabIndicator(currentTabPosition: TabPosition) {
TabRowDefaults.Indicator(
Modifier
.tabIndicatorOffset(currentTabPosition)
.clip(RoundedCornerShape(topStart = 3.dp, topEnd = 3.dp)),
)
}
@Composable
fun TabText(
text: String,
badgeCount: Int? = null,
isCurrentPage: Boolean,
) {
val pillAlpha = if (isSystemInDarkTheme()) 0.12f else 0.08f
Row(
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = text,
color = if (isCurrentPage) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onBackground,
)
if (badgeCount != null) {
Pill(
text = "$badgeCount",
color = MaterialTheme.colorScheme.onBackground.copy(alpha = pillAlpha),
fontSize = 10.sp,
)
}
}
}

View file

@ -2,31 +2,22 @@ package eu.kanade.presentation.library.components
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ScrollableTabRow
import androidx.compose.material3.Tab
import androidx.compose.material3.TabRowDefaults
import androidx.compose.material3.TabRowDefaults.tabIndicatorOffset
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.google.accompanist.pager.PagerState
import eu.kanade.domain.category.model.Category
import eu.kanade.presentation.category.visualName
import eu.kanade.presentation.components.DownloadedOnlyModeBanner
import eu.kanade.presentation.components.IncognitoModeBanner
import eu.kanade.presentation.components.Pill
import eu.kanade.presentation.components.TabIndicator
import eu.kanade.presentation.components.TabText
import kotlinx.coroutines.launch
@Composable
@ -46,13 +37,7 @@ fun LibraryTabs(
ScrollableTabRow(
selectedTabIndex = state.currentPage,
edgePadding = 0.dp,
indicator = { tabPositions ->
TabRowDefaults.Indicator(
Modifier
.tabIndicatorOffset(tabPositions[state.currentPage])
.clip(RoundedCornerShape(topStart = 3.dp, topEnd = 3.dp)),
)
},
indicator = { TabIndicator(it[state.currentPage]) },
) {
categories.forEachIndexed { index, category ->
val count by if (showMangaCount) {
@ -64,21 +49,7 @@ fun LibraryTabs(
selected = state.currentPage == index,
onClick = { scope.launch { state.animateScrollToPage(index) } },
text = {
Row(
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = category.visualName,
color = if (state.currentPage == index) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onBackground,
)
if (count != null) {
Pill(
text = "$count",
color = MaterialTheme.colorScheme.onBackground.copy(alpha = pillAlpha),
fontSize = 10.sp,
)
}
}
TabText(category.visualName, count, state.currentPage == index)
},
)
}

View file

@ -4,10 +4,7 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import androidx.compose.runtime.Composable
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.platform.rememberNestedScrollInteropConnection
import eu.kanade.tachiyomi.databinding.ComposeControllerBinding
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import eu.kanade.tachiyomi.util.view.setComposeContent
import nucleus.presenter.Presenter
@ -29,33 +26,11 @@ abstract class FullComposeController<P : Presenter<*>>(bundle: Bundle? = null) :
}
}
/**
* Compose controller with a Nucleus presenter.
*/
abstract class ComposeController<P : Presenter<*>>(bundle: Bundle? = null) :
NucleusController<ComposeControllerBinding, P>(bundle),
ComposeContentController {
override fun createBinding(inflater: LayoutInflater) =
ComposeControllerBinding.inflate(inflater)
override fun onViewCreated(view: View) {
super.onViewCreated(view)
binding.root.apply {
setComposeContent {
val nestedScrollInterop = rememberNestedScrollInteropConnection()
ComposeContent(nestedScrollInterop)
}
}
}
}
/**
* Basic Compose controller without a presenter.
*/
abstract class BasicFullComposeController :
BaseController<ComposeControllerBinding>(),
abstract class BasicFullComposeController(bundle: Bundle? = null) :
BaseController<ComposeControllerBinding>(bundle),
FullComposeContentController {
override fun createBinding(inflater: LayoutInflater) =
@ -72,29 +47,6 @@ abstract class BasicFullComposeController :
}
}
abstract class SearchableComposeController<P : BasePresenter<*>>(bundle: Bundle? = null) :
SearchableNucleusController<ComposeControllerBinding, P>(bundle),
ComposeContentController {
override fun createBinding(inflater: LayoutInflater) =
ComposeControllerBinding.inflate(inflater)
override fun onViewCreated(view: View) {
super.onViewCreated(view)
binding.root.apply {
setComposeContent {
val nestedScrollInterop = rememberNestedScrollInteropConnection()
ComposeContent(nestedScrollInterop)
}
}
}
}
interface FullComposeContentController {
@Composable fun ComposeContent()
}
interface ComposeContentController {
@Composable fun ComposeContent(nestedScrollInterop: NestedScrollConnection)
}

View file

@ -1,13 +0,0 @@
package eu.kanade.tachiyomi.ui.base.controller
import com.google.android.material.tabs.TabLayout
interface TabbedController {
/**
* @return true to let activity updates tabs visibility (to visible)
*/
fun configureTabs(tabs: TabLayout): Boolean = true
fun cleanupTabs(tabs: TabLayout) {}
}

View file

@ -1,149 +1,53 @@
package eu.kanade.tachiyomi.ui.browse
import android.Manifest
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.core.os.bundleOf
import com.bluelinelabs.conductor.Controller
import com.bluelinelabs.conductor.ControllerChangeHandler
import com.bluelinelabs.conductor.ControllerChangeType
import com.bluelinelabs.conductor.Router
import com.bluelinelabs.conductor.RouterTransaction
import com.bluelinelabs.conductor.viewpager.RouterPagerAdapter
import com.google.android.material.badge.BadgeDrawable
import com.google.android.material.tabs.TabLayout
import com.jakewharton.rxrelay.PublishRelay
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.databinding.PagerControllerBinding
import eu.kanade.presentation.browse.BrowseScreen
import eu.kanade.tachiyomi.ui.base.controller.FullComposeController
import eu.kanade.tachiyomi.ui.base.controller.RootController
import eu.kanade.tachiyomi.ui.base.controller.RxController
import eu.kanade.tachiyomi.ui.base.controller.TabbedController
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionsController
import eu.kanade.tachiyomi.ui.browse.migration.sources.MigrationSourcesController
import eu.kanade.tachiyomi.ui.browse.source.SourcesController
import eu.kanade.tachiyomi.ui.base.controller.requestPermissionsSafe
import eu.kanade.tachiyomi.ui.browse.extension.extensionsTab
import eu.kanade.tachiyomi.ui.browse.migration.sources.migrateSourcesTab
import eu.kanade.tachiyomi.ui.browse.source.sourcesTab
import eu.kanade.tachiyomi.ui.main.MainActivity
import uy.kohesive.injekt.injectLazy
class BrowseController :
RxController<PagerControllerBinding>,
RootController,
TabbedController {
class BrowseController : FullComposeController<BrowsePresenter>, RootController {
@Suppress("unused")
constructor(bundle: Bundle? = null) : this(bundle?.getBoolean(TO_EXTENSIONS_EXTRA) ?: false)
constructor(toExtensions: Boolean = false) : super(
bundleOf(TO_EXTENSIONS_EXTRA to toExtensions),
)
@Suppress("unused")
constructor(bundle: Bundle) : this(bundle.getBoolean(TO_EXTENSIONS_EXTRA))
private val preferences: PreferencesHelper by injectLazy()
private val toExtensions = args.getBoolean(TO_EXTENSIONS_EXTRA, false)
val extensionListUpdateRelay: PublishRelay<Boolean> = PublishRelay.create()
override fun createPresenter() = BrowsePresenter()
private var adapter: BrowseAdapter? = null
@Composable
override fun ComposeContent() {
BrowseScreen(
startIndex = 1.takeIf { toExtensions },
tabs = listOf(
sourcesTab(router, presenter.sourcesPresenter),
extensionsTab(router, presenter.extensionsPresenter),
migrateSourcesTab(router, presenter.migrationSourcesPresenter),
),
)
override fun getTitle(): String? {
return resources!!.getString(R.string.browse)
LaunchedEffect(Unit) {
(activity as? MainActivity)?.ready = true
}
}
override fun createBinding(inflater: LayoutInflater) = PagerControllerBinding.inflate(inflater)
override fun onViewCreated(view: View) {
super.onViewCreated(view)
adapter = BrowseAdapter()
binding.pager.adapter = adapter
if (toExtensions) {
binding.pager.currentItem = EXTENSIONS_CONTROLLER
}
}
override fun onDestroyView(view: View) {
super.onDestroyView(view)
adapter = null
}
override fun onChangeStarted(handler: ControllerChangeHandler, type: ControllerChangeType) {
super.onChangeStarted(handler, type)
if (type.isEnter) {
(activity as? MainActivity)?.binding?.tabs?.apply {
setupWithViewPager(binding.pager)
// Show badge on tab for extension updates
setExtensionUpdateBadge()
}
}
}
override fun configureTabs(tabs: TabLayout): Boolean {
with(tabs) {
tabGravity = TabLayout.GRAVITY_FILL
tabMode = TabLayout.MODE_FIXED
}
return true
}
override fun cleanupTabs(tabs: TabLayout) {
// Remove extension update badge
tabs.getTabAt(EXTENSIONS_CONTROLLER)?.removeBadge()
}
fun setExtensionUpdateBadge() {
/* It's possible to switch to the Library controller by the time setExtensionUpdateBadge
is called, resulting in a badge being put on the category tabs (if enabled).
This check prevents that from happening */
if (router.backstack.lastOrNull()?.controller !is BrowseController) return
(activity as? MainActivity)?.binding?.tabs?.apply {
val updates = preferences.extensionUpdatesCount().get()
if (updates > 0) {
val badge: BadgeDrawable? = getTabAt(1)?.orCreateBadge
badge?.isVisible = true
} else {
getTabAt(EXTENSIONS_CONTROLLER)?.removeBadge()
}
}
}
private inner class BrowseAdapter : RouterPagerAdapter(this@BrowseController) {
private val tabTitles = listOf(
R.string.label_sources,
R.string.label_extensions,
R.string.label_migration,
)
.map { resources!!.getString(it) }
override fun getCount(): Int {
return tabTitles.size
}
override fun configureRouter(router: Router, position: Int) {
if (!router.hasRootController()) {
val controller: Controller = when (position) {
SOURCES_CONTROLLER -> SourcesController()
EXTENSIONS_CONTROLLER -> ExtensionsController()
MIGRATION_CONTROLLER -> MigrationSourcesController()
else -> error("Wrong position $position")
}
router.setRoot(RouterTransaction.with(controller))
}
}
override fun getPageTitle(position: Int): CharSequence {
return tabTitles[position]
}
}
companion object {
const val TO_EXTENSIONS_EXTRA = "to_extensions"
const val SOURCES_CONTROLLER = 0
const val EXTENSIONS_CONTROLLER = 1
const val MIGRATION_CONTROLLER = 2
requestPermissionsSafe(arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), 301)
}
}
private const val TO_EXTENSIONS_EXTRA = "to_extensions"

View file

@ -0,0 +1,23 @@
package eu.kanade.tachiyomi.ui.browse
import android.os.Bundle
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionsPresenter
import eu.kanade.tachiyomi.ui.browse.migration.sources.MigrationSourcesPresenter
import eu.kanade.tachiyomi.ui.browse.source.SourcesPresenter
import uy.kohesive.injekt.api.get
class BrowsePresenter : BasePresenter<BrowseController>() {
val sourcesPresenter = SourcesPresenter(presenterScope)
val extensionsPresenter = ExtensionsPresenter(presenterScope)
val migrationSourcesPresenter = MigrationSourcesPresenter(presenterScope)
override fun onCreate(savedState: Bundle?) {
super.onCreate(savedState)
sourcesPresenter.onCreate()
extensionsPresenter.onCreate()
migrationSourcesPresenter.onCreate()
}
}

View file

@ -1,120 +0,0 @@
package eu.kanade.tachiyomi.ui.browse.extension
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import androidx.appcompat.widget.SearchView
import androidx.compose.runtime.Composable
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import com.bluelinelabs.conductor.ControllerChangeHandler
import com.bluelinelabs.conductor.ControllerChangeType
import eu.kanade.presentation.browse.ExtensionScreen
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.extension.model.Extension
import eu.kanade.tachiyomi.ui.base.controller.ComposeController
import eu.kanade.tachiyomi.ui.base.controller.pushController
import eu.kanade.tachiyomi.ui.browse.BrowseController
import eu.kanade.tachiyomi.ui.browse.extension.details.ExtensionDetailsController
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import reactivecircus.flowbinding.appcompat.queryTextChanges
class ExtensionsController : ComposeController<ExtensionsPresenter>() {
private var query = ""
init {
setHasOptionsMenu(true)
}
override fun getTitle() = applicationContext?.getString(R.string.label_extensions)
override fun createPresenter() = ExtensionsPresenter()
@Composable
override fun ComposeContent(nestedScrollInterop: NestedScrollConnection) {
ExtensionScreen(
nestedScrollInterop = nestedScrollInterop,
presenter = presenter,
onLongClickItem = { extension ->
when (extension) {
is Extension.Available -> presenter.installExtension(extension)
else -> presenter.uninstallExtension(extension.pkgName)
}
},
onClickItemCancel = { extension ->
presenter.cancelInstallUpdateExtension(extension)
},
onClickUpdateAll = {
presenter.updateAllExtensions()
},
onLaunched = {
val ctrl = parentController as BrowseController
ctrl.setExtensionUpdateBadge()
ctrl.extensionListUpdateRelay.call(true)
},
onInstallExtension = {
presenter.installExtension(it)
},
onOpenExtension = {
val controller = ExtensionDetailsController(it.pkgName)
parentController!!.router.pushController(controller)
},
onTrustExtension = {
presenter.trustSignature(it.signatureHash)
},
onUninstallExtension = {
presenter.uninstallExtension(it.pkgName)
},
onUpdateExtension = {
presenter.updateExtension(it)
},
onRefresh = {
presenter.findAvailableExtensions()
},
)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.action_search -> expandActionViewFromInteraction = true
R.id.action_settings -> {
parentController!!.router.pushController(ExtensionFilterController())
}
}
return super.onOptionsItemSelected(item)
}
override fun onChangeStarted(handler: ControllerChangeHandler, type: ControllerChangeType) {
super.onChangeStarted(handler, type)
if (type.isPush) {
presenter.findAvailableExtensions()
}
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.browse_extensions, menu)
val searchItem = menu.findItem(R.id.action_search)
val searchView = searchItem.actionView as SearchView
searchView.maxWidth = Int.MAX_VALUE
// Fixes problem with the overflow icon showing up in lieu of search
searchItem.fixExpand(onExpand = { invalidateMenuOnExpand() })
if (query.isNotEmpty()) {
searchItem.expandActionView()
searchView.setQuery(query, true)
searchView.clearFocus()
}
searchView.queryTextChanges()
.filter { router.backstack.lastOrNull()?.controller == this }
.onEach {
query = it.toString()
presenter.search(query)
}
.launchIn(viewScope)
}
}

View file

@ -1,41 +1,43 @@
package eu.kanade.tachiyomi.ui.browse.extension
import android.app.Application
import android.os.Bundle
import androidx.annotation.StringRes
import eu.kanade.domain.extension.interactor.GetExtensionsByType
import eu.kanade.presentation.browse.ExtensionState
import eu.kanade.presentation.browse.ExtensionsState
import eu.kanade.presentation.browse.ExtensionsStateImpl
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.extension.ExtensionManager
import eu.kanade.tachiyomi.extension.model.Extension
import eu.kanade.tachiyomi.extension.model.InstallStep
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.system.LocaleHelper
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.update
import rx.Observable
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class ExtensionsPresenter(
private val presenterScope: CoroutineScope,
private val state: ExtensionsStateImpl = ExtensionState() as ExtensionsStateImpl,
private val preferences: PreferencesHelper = Injekt.get(),
private val extensionManager: ExtensionManager = Injekt.get(),
private val getExtensions: GetExtensionsByType = Injekt.get(),
) : BasePresenter<ExtensionsController>(), ExtensionsState by state {
) : ExtensionsState by state {
private val _query: MutableStateFlow<String> = MutableStateFlow("")
private var _currentDownloads = MutableStateFlow<Map<String, InstallStep>>(hashMapOf())
override fun onCreate(savedState: Bundle?) {
super.onCreate(savedState)
fun onCreate() {
val context = Injekt.get<Application>()
val extensionMapper: (Map<String, InstallStep>) -> ((Extension) -> ExtensionUiModel) = { map ->
{
@ -114,6 +116,10 @@ class ExtensionsPresenter(
}
presenterScope.launchIO { findAvailableExtensions() }
preferences.extensionUpdatesCount().asFlow()
.onEach { state.updates = it }
.launchIn(presenterScope)
}
fun search(query: String) {

View file

@ -0,0 +1,75 @@
package eu.kanade.tachiyomi.ui.browse.extension
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.FilterList
import androidx.compose.material.icons.outlined.Search
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import com.bluelinelabs.conductor.Router
import eu.kanade.presentation.browse.BrowseTab
import eu.kanade.presentation.browse.ExtensionScreen
import eu.kanade.presentation.components.AppBar
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.extension.model.Extension
import eu.kanade.tachiyomi.ui.base.controller.pushController
import eu.kanade.tachiyomi.ui.browse.extension.details.ExtensionDetailsController
@Composable
fun extensionsTab(
router: Router?,
presenter: ExtensionsPresenter,
) = BrowseTab(
titleRes = R.string.label_extensions,
badgeNumber = presenter.updates.takeIf { it > 0 },
actions = listOf(
AppBar.Action(
title = stringResource(R.string.action_search),
icon = Icons.Outlined.Search,
onClick = {
// TODO: extensions search
// presenter.search(query)
},
),
AppBar.Action(
title = stringResource(R.string.action_filter),
icon = Icons.Outlined.FilterList,
onClick = { router?.pushController(ExtensionFilterController()) },
),
),
content = {
ExtensionScreen(
presenter = presenter,
onLongClickItem = { extension ->
when (extension) {
is Extension.Available -> presenter.installExtension(extension)
else -> presenter.uninstallExtension(extension.pkgName)
}
},
onClickItemCancel = { extension ->
presenter.cancelInstallUpdateExtension(extension)
},
onClickUpdateAll = {
presenter.updateAllExtensions()
},
onInstallExtension = {
presenter.installExtension(it)
},
onOpenExtension = {
router?.pushController(ExtensionDetailsController(it.pkgName))
},
onTrustExtension = {
presenter.trustSignature(it.signatureHash)
},
onUninstallExtension = {
presenter.uninstallExtension(it.pkgName)
},
onUpdateExtension = {
presenter.updateExtension(it)
},
onRefresh = {
presenter.findAvailableExtensions()
},
)
},
)

View file

@ -0,0 +1,48 @@
package eu.kanade.tachiyomi.ui.browse.migration.sources
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.HelpOutline
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.stringResource
import com.bluelinelabs.conductor.Router
import eu.kanade.presentation.browse.BrowseTab
import eu.kanade.presentation.browse.MigrateSourceScreen
import eu.kanade.presentation.components.AppBar
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.base.controller.pushController
import eu.kanade.tachiyomi.ui.browse.migration.manga.MigrationMangaController
@Composable
fun migrateSourcesTab(
router: Router?,
presenter: MigrationSourcesPresenter,
): BrowseTab {
val uriHandler = LocalUriHandler.current
return BrowseTab(
titleRes = R.string.label_migration,
actions = listOf(
AppBar.Action(
title = stringResource(R.string.migration_help_guide),
icon = Icons.Outlined.HelpOutline,
onClick = {
uriHandler.openUri("https://tachiyomi.org/help/guides/source-migration/")
},
),
),
content = {
MigrateSourceScreen(
presenter = presenter,
onClickItem = { source ->
router?.pushController(
MigrationMangaController(
source.id,
source.name,
),
)
},
)
},
)
}

View file

@ -1,65 +0,0 @@
package eu.kanade.tachiyomi.ui.browse.migration.sources
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import androidx.compose.runtime.Composable
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import eu.kanade.presentation.browse.MigrateSourceScreen
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.base.controller.ComposeController
import eu.kanade.tachiyomi.ui.base.controller.pushController
import eu.kanade.tachiyomi.ui.browse.migration.manga.MigrationMangaController
import eu.kanade.tachiyomi.util.system.openInBrowser
class MigrationSourcesController : ComposeController<MigrationSourcesPresenter>() {
init {
setHasOptionsMenu(true)
}
override fun createPresenter() = MigrationSourcesPresenter()
@Composable
override fun ComposeContent(nestedScrollInterop: NestedScrollConnection) {
MigrateSourceScreen(
nestedScrollInterop = nestedScrollInterop,
presenter = presenter,
onClickItem = { source ->
parentController!!.router.pushController(
MigrationMangaController(
source.id,
source.name,
),
)
},
)
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) =
inflater.inflate(R.menu.browse_migrate, menu)
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (val itemId = item.itemId) {
R.id.action_source_migration_help -> {
activity?.openInBrowser(HELP_URL)
true
}
R.id.asc_alphabetical,
R.id.desc_alphabetical,
-> {
presenter.setAlphabeticalSorting(itemId == R.id.asc_alphabetical)
true
}
R.id.asc_count,
R.id.desc_count,
-> {
presenter.setTotalSorting(itemId == R.id.asc_count)
true
}
else -> super.onOptionsItemSelected(item)
}
}
}
private const val HELP_URL = "https://tachiyomi.org/help/guides/source-migration/"

View file

@ -1,33 +1,35 @@
package eu.kanade.tachiyomi.ui.browse.migration.sources
import android.os.Bundle
import eu.kanade.domain.source.interactor.GetSourcesWithFavoriteCount
import eu.kanade.domain.source.interactor.SetMigrateSorting
import eu.kanade.presentation.browse.MigrateSourceState
import eu.kanade.presentation.browse.MigrateSourceStateImpl
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.system.logcat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.receiveAsFlow
import logcat.LogPriority
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class MigrationSourcesPresenter(
private val presenterScope: CoroutineScope,
private val state: MigrateSourceStateImpl = MigrateSourceState() as MigrateSourceStateImpl,
private val preferences: PreferencesHelper = Injekt.get(),
private val getSourcesWithFavoriteCount: GetSourcesWithFavoriteCount = Injekt.get(),
private val setMigrateSorting: SetMigrateSorting = Injekt.get(),
) : BasePresenter<MigrationSourcesController>(), MigrateSourceState by state {
) : MigrateSourceState by state {
private val _channel = Channel<Event>(Int.MAX_VALUE)
val channel = _channel.receiveAsFlow()
override fun onCreate(savedState: Bundle?) {
super.onCreate(savedState)
fun onCreate() {
presenterScope.launchIO {
getSourcesWithFavoriteCount.subscribe()
.catch { exception ->
@ -39,14 +41,32 @@ class MigrationSourcesPresenter(
state.isLoading = false
}
}
preferences.migrationSortingDirection().asFlow()
.onEach { state.sortingDirection = it }
.launchIn(presenterScope)
preferences.migrationSortingMode().asFlow()
.onEach { state.sortingMode = it }
.launchIn(presenterScope)
}
fun setAlphabeticalSorting(isAscending: Boolean) {
setMigrateSorting.await(SetMigrateSorting.Mode.ALPHABETICAL, isAscending)
fun toggleSortingMode() {
val newMode = when (state.sortingMode) {
SetMigrateSorting.Mode.ALPHABETICAL -> SetMigrateSorting.Mode.TOTAL
SetMigrateSorting.Mode.TOTAL -> SetMigrateSorting.Mode.ALPHABETICAL
}
fun setTotalSorting(isAscending: Boolean) {
setMigrateSorting.await(SetMigrateSorting.Mode.TOTAL, isAscending)
setMigrateSorting.await(newMode, state.sortingDirection)
}
fun toggleSortingDirection() {
val newDirection = when (state.sortingDirection) {
SetMigrateSorting.Direction.ASCENDING -> SetMigrateSorting.Direction.DESCENDING
SetMigrateSorting.Direction.DESCENDING -> SetMigrateSorting.Direction.ASCENDING
}
setMigrateSorting.await(state.sortingMode, newDirection)
}
sealed class Event {

View file

@ -1,106 +0,0 @@
package eu.kanade.tachiyomi.ui.browse.source
import android.Manifest.permission.WRITE_EXTERNAL_STORAGE
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import eu.kanade.domain.source.model.Source
import eu.kanade.presentation.browse.SourcesScreen
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.ui.base.controller.SearchableComposeController
import eu.kanade.tachiyomi.ui.base.controller.pushController
import eu.kanade.tachiyomi.ui.base.controller.requestPermissionsSafe
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchController
import eu.kanade.tachiyomi.ui.browse.source.latest.LatestUpdatesController
import eu.kanade.tachiyomi.ui.main.MainActivity
import uy.kohesive.injekt.injectLazy
class SourcesController : SearchableComposeController<SourcesPresenter>() {
private val preferences: PreferencesHelper by injectLazy()
init {
setHasOptionsMenu(true)
}
override fun getTitle() = resources?.getString(R.string.label_sources)
override fun createPresenter() = SourcesPresenter()
@Composable
override fun ComposeContent(nestedScrollInterop: NestedScrollConnection) {
SourcesScreen(
nestedScrollInterop = nestedScrollInterop,
presenter = presenter,
onClickItem = { source ->
openSource(source, BrowseSourceController(source))
},
onClickDisable = { source ->
presenter.toggleSource(source)
},
onClickLatest = { source ->
openSource(source, LatestUpdatesController(source))
},
onClickPin = { source ->
presenter.togglePin(source)
},
)
LaunchedEffect(Unit) {
(activity as? MainActivity)?.ready = true
}
}
override fun onViewCreated(view: View) {
super.onViewCreated(view)
requestPermissionsSafe(arrayOf(WRITE_EXTERNAL_STORAGE), 301)
}
/**
* Opens a catalogue with the given controller.
*/
private fun openSource(source: Source, controller: BrowseSourceController) {
if (!preferences.incognitoMode().get()) {
preferences.lastUsedSource().set(source.id)
}
parentController!!.router.pushController(controller)
}
/**
* Called when an option menu item has been selected by the user.
*
* @param item The selected item.
* @return True if this event has been consumed, false if it has not.
*/
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
// Initialize option to open catalogue settings.
R.id.action_settings -> {
parentController!!.router.pushController(SourceFilterController())
true
}
else -> super.onOptionsItemSelected(item)
}
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
createOptionsMenu(
menu,
inflater,
R.menu.browse_sources,
R.id.action_search,
R.string.action_global_search_hint,
false, // GlobalSearch handles the searching here
)
}
override fun onSearchViewQueryTextSubmit(query: String?) {
parentController!!.router.pushController(GlobalSearchController(query))
}
}

View file

@ -1,6 +1,5 @@
package eu.kanade.tachiyomi.ui.browse.source
import android.os.Bundle
import eu.kanade.domain.source.interactor.GetEnabledSources
import eu.kanade.domain.source.interactor.ToggleSource
import eu.kanade.domain.source.interactor.ToggleSourcePin
@ -9,9 +8,10 @@ import eu.kanade.domain.source.model.Source
import eu.kanade.presentation.browse.SourceUiModel
import eu.kanade.presentation.browse.SourcesState
import eu.kanade.presentation.browse.SourcesStateImpl
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.system.logcat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.collectLatest
@ -22,17 +22,18 @@ import uy.kohesive.injekt.api.get
import java.util.TreeMap
class SourcesPresenter(
private val presenterScope: CoroutineScope,
private val state: SourcesStateImpl = SourcesState() as SourcesStateImpl,
private val preferences: PreferencesHelper = Injekt.get(),
private val getEnabledSources: GetEnabledSources = Injekt.get(),
private val toggleSource: ToggleSource = Injekt.get(),
private val toggleSourcePin: ToggleSourcePin = Injekt.get(),
) : BasePresenter<SourcesController>(), SourcesState by state {
) : SourcesState by state {
private val _events = Channel<Event>(Int.MAX_VALUE)
val events = _events.receiveAsFlow()
override fun onCreate(savedState: Bundle?) {
super.onCreate(savedState)
fun onCreate() {
presenterScope.launchIO {
getEnabledSources.subscribe()
.catch { exception ->
@ -76,6 +77,12 @@ class SourcesPresenter(
state.items = uiModels
}
fun onOpenSource(source: Source) {
if (!preferences.incognitoMode().get()) {
preferences.lastUsedSource().set(source.id)
}
}
fun toggleSource(source: Source) {
toggleSource.await(source)
}

View file

@ -0,0 +1,55 @@
package eu.kanade.tachiyomi.ui.browse.source
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.FilterList
import androidx.compose.material.icons.outlined.TravelExplore
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import com.bluelinelabs.conductor.Router
import eu.kanade.presentation.browse.BrowseTab
import eu.kanade.presentation.browse.SourcesScreen
import eu.kanade.presentation.components.AppBar
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.base.controller.pushController
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchController
import eu.kanade.tachiyomi.ui.browse.source.latest.LatestUpdatesController
@Composable
fun sourcesTab(
router: Router?,
presenter: SourcesPresenter,
) = BrowseTab(
titleRes = R.string.label_sources,
actions = listOf(
AppBar.Action(
title = stringResource(R.string.action_global_search),
icon = Icons.Outlined.TravelExplore,
onClick = { router?.pushController(GlobalSearchController()) },
),
AppBar.Action(
title = stringResource(R.string.action_filter),
icon = Icons.Outlined.FilterList,
onClick = { router?.pushController(SourceFilterController()) },
),
),
content = {
SourcesScreen(
presenter = presenter,
onClickItem = { source ->
presenter.onOpenSource(source)
router?.pushController(BrowseSourceController(source))
},
onClickDisable = { source ->
presenter.toggleSource(source)
},
onClickLatest = { source ->
presenter.onOpenSource(source)
router?.pushController(LatestUpdatesController(source))
},
onClickPin = { source ->
presenter.togglePin(source)
},
)
},
)

View file

@ -45,7 +45,6 @@ import eu.kanade.tachiyomi.ui.base.controller.DialogController
import eu.kanade.tachiyomi.ui.base.controller.FabController
import eu.kanade.tachiyomi.ui.base.controller.FullComposeContentController
import eu.kanade.tachiyomi.ui.base.controller.RootController
import eu.kanade.tachiyomi.ui.base.controller.TabbedController
import eu.kanade.tachiyomi.ui.base.controller.pushController
import eu.kanade.tachiyomi.ui.base.controller.setRoot
import eu.kanade.tachiyomi.ui.browse.BrowseController
@ -162,7 +161,7 @@ class MainActivity : BaseActivity() {
R.id.nav_library -> router.setRoot(LibraryController(), id)
R.id.nav_updates -> router.setRoot(UpdatesController(), id)
R.id.nav_history -> router.setRoot(HistoryController(), id)
R.id.nav_browse -> router.setRoot(BrowseController(), id)
R.id.nav_browse -> router.setRoot(BrowseController(toExtensions = false), id)
R.id.nav_more -> router.setRoot(MoreController(), id)
}
} else if (!isHandlingShortcut) {
@ -590,17 +589,6 @@ class MainActivity : BaseActivity() {
showNav(true)
}
if (from is TabbedController) {
from.cleanupTabs(binding.tabs)
}
if (internalTo is TabbedController) {
if (internalTo.configureTabs(binding.tabs)) {
binding.tabs.isVisible = true
}
} else {
binding.tabs.isVisible = false
}
if (from is FabController) {
from.cleanupFab(binding.fabLayout.rootFab)
}

View file

@ -1,10 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:autoMirrored="true"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/black"
android:pathData="M3,18h6v-2L3,16v2zM3,6v2h18L21,6L3,6zM3,13h12v-2L3,11v2z" />
</vector>

View file

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/black"
android:pathData="M12.87,15.07l-2.54,-2.51 0.03,-0.03c1.74,-1.94 2.98,-4.17 3.71,-6.53L17,6L17,4h-7L10,2L8,2v2L1,4v1.99h11.17C11.5,7.92 10.44,9.75 9,11.35 8.07,10.32 7.3,9.19 6.69,8h-2c0.73,1.63 1.73,3.17 2.98,4.56l-5.09,5.02L4,19l5,-5 3.11,3.11 0.76,-2.04zM18.5,10h-2L12,22h2l1.12,-3h4.75L21,22h2l-4.5,-12zM15.88,17l1.62,-4.33L19.12,17h-3.24z"/>
</vector>

View file

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/black"
android:pathData="M19.3,16.9c0.4,-0.7 0.7,-1.5 0.7,-2.4c0,-2.5 -2,-4.5 -4.5,-4.5S11,12 11,14.5s2,4.5 4.5,4.5c0.9,0 1.7,-0.3 2.4,-0.7l3.2,3.2l1.4,-1.4L19.3,16.9zM15.5,17c-1.4,0 -2.5,-1.1 -2.5,-2.5s1.1,-2.5 2.5,-2.5s2.5,1.1 2.5,2.5S16.9,17 15.5,17zM12,20v2C6.48,22 2,17.52 2,12C2,6.48 6.48,2 12,2c4.84,0 8.87,3.44 9.8,8h-2.07c-0.64,-2.46 -2.4,-4.47 -4.73,-5.41V5c0,1.1 -0.9,2 -2,2h-2v2c0,0.55 -0.45,1 -1,1H8v2h2v3H9l-4.79,-4.79C4.08,10.79 4,11.38 4,12C4,16.41 7.59,20 12,20z"/>
</vector>

View file

@ -26,11 +26,6 @@
android:layout_height="?attr/actionBarSize"
android:theme="?attr/actionBarTheme" />
<com.google.android.material.tabs.TabLayout
android:id="@+id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/downloaded_only"
android:layout_width="match_parent"

View file

@ -25,12 +25,6 @@
android:layout_height="?attr/actionBarSize"
android:theme="?attr/actionBarTheme" />
<com.google.android.material.tabs.TabLayout
android:id="@+id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone" />
<TextView
android:id="@+id/downloaded_only"
android:layout_width="match_parent"

View file

@ -1,19 +0,0 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_search"
android:icon="@drawable/ic_search_24dp"
android:title="@string/action_search"
app:actionViewClass="eu.kanade.tachiyomi.widget.TachiyomiSearchView"
app:iconTint="?attr/colorOnSurface"
app:showAsAction="collapseActionView|ifRoom" />
<item
android:id="@+id/action_settings"
android:icon="@drawable/ic_translate_24dp"
android:title="@string/action_filter"
app:iconTint="?attr/colorOnSurface"
app:showAsAction="ifRoom" />
</menu>

View file

@ -1,47 +0,0 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_sort"
android:icon="@drawable/ic_sort_24dp"
android:title="@string/action_sort"
app:iconTint="?attr/colorOnSurface"
app:showAsAction="collapseActionView|ifRoom" >
<menu>
<item
android:id="@+id/action_sort_alphabetical"
android:title="@string/action_sort_alpha"
app:showAsAction="never">
<menu>
<item
android:id="@+id/asc_alphabetical"
android:title="@string/action_asc" />
<item
android:id="@+id/desc_alphabetical"
android:title="@string/action_desc" />
</menu>
</item>
<item
android:id="@+id/action_sort_count"
android:title="@string/action_sort_count"
app:showAsAction="never">
<menu>
<item
android:id="@+id/asc_count"
android:title="@string/action_asc" />
<item
android:id="@+id/desc_count"
android:title="@string/action_desc" />
</menu>
</item>
</menu>
</item>
<item
android:id="@+id/action_source_migration_help"
android:icon="@drawable/ic_help_24dp"
android:title="@string/migration_help_guide"
app:iconTint="?attr/colorOnSurface"
app:showAsAction="ifRoom" />
</menu>

View file

@ -1,19 +0,0 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_search"
android:icon="@drawable/ic_travel_explore_24dp"
android:title="@string/action_global_search"
app:actionViewClass="eu.kanade.tachiyomi.widget.TachiyomiSearchView"
app:iconTint="?attr/colorOnSurface"
app:showAsAction="collapseActionView|ifRoom" />
<item
android:id="@+id/action_settings"
android:icon="@drawable/ic_filter_list_24dp"
android:title="@string/action_filter"
app:iconTint="?attr/colorOnSurface"
app:showAsAction="ifRoom" />
</menu>

View file

@ -64,7 +64,6 @@ directionalviewpager = "com.github.tachiyomiorg:DirectionalViewPager:1.0.0"
insetter = "dev.chrisbanes.insetter:insetter:0.6.1"
conductor-core = { module = "com.bluelinelabs:conductor", version.ref = "conductor_version" }
conductor-viewpager = { module = "com.bluelinelabs:conductor-viewpager", version.ref = "conductor_version" }
conductor-support-preference = { module = "com.github.tachiyomiorg:conductor-support-preference", version.ref = "conductor_version" }
flowbinding-android = { module = "io.github.reactivecircus.flowbinding:flowbinding-android", version.ref = "flowbinding_version" }
@ -99,7 +98,7 @@ sqlite = ["sqlitektx", "sqlite-android"]
nucleus = ["nucleus-core", "nucleus-supportv7"]
coil = ["coil-core", "coil-gif", "coil-compose"]
flowbinding = ["flowbinding-android", "flowbinding-appcompat"]
conductor = ["conductor-core", "conductor-viewpager", "conductor-support-preference"]
conductor = ["conductor-core", "conductor-support-preference"]
shizuku = ["shizuku-api", "shizuku-provider"]
[plugins]