From a3bdb70ef1ea13c2319cebb6fb7fc1d69d8e070e Mon Sep 17 00:00:00 2001 From: tarsin Date: Wed, 10 May 2023 18:29:52 +0800 Subject: [PATCH] [Don't use] Initial move GalleryPreviewsScene to compose --- .../ui/scene/GalleryPreviewsAdapter.kt | 69 ----- .../ehviewer/ui/scene/GalleryPreviewsScene.kt | 275 +++++++----------- .../com/hippo/widget/ComposeContentLayout.kt | 57 +++- .../res/layout/scene_gallery_previews.xml | 23 -- 4 files changed, 160 insertions(+), 264 deletions(-) delete mode 100644 app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryPreviewsAdapter.kt delete mode 100644 app/src/main/res/layout/scene_gallery_previews.xml diff --git a/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryPreviewsAdapter.kt b/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryPreviewsAdapter.kt deleted file mode 100644 index b92ca6919..000000000 --- a/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryPreviewsAdapter.kt +++ /dev/null @@ -1,69 +0,0 @@ -package com.hippo.ehviewer.ui.scene - -import android.view.ViewGroup -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.aspectRatio -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.material3.Text -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.ComposeView -import androidx.recyclerview.widget.DiffUtil -import androidx.recyclerview.widget.ListAdapter -import androidx.recyclerview.widget.RecyclerView -import arrow.core.partially1 -import com.hippo.ehviewer.client.data.GalleryPreview -import com.hippo.ehviewer.ui.widget.CrystalCard -import com.hippo.ehviewer.ui.widget.EhAsyncPreview -import com.hippo.ehviewer.ui.widget.setMD3Content - -class GalleryPreviewsAdapter(private val onClick: (GalleryPreview) -> Unit) : ListAdapter(PreviewDiffCallback) { - class ComposeHolder(val composeView: ComposeView) : RecyclerView.ViewHolder(composeView) - - override fun onCreateViewHolder( - parent: ViewGroup, - viewType: Int, - ): ComposeHolder { - return ComposeHolder(ComposeView(parent.context)) - } - - override fun onBindViewHolder(holder: ComposeHolder, position: Int) { - val it = getItem(position) - holder.composeView.setMD3Content { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - ) { - Box(contentAlignment = Alignment.Center) { - CrystalCard( - onClick = onClick.partially1(it), - modifier = Modifier - .fillMaxWidth() - .aspectRatio(0.6666667F), - ) { - EhAsyncPreview( - model = it, - modifier = Modifier.fillMaxSize(), - ) - } - } - Text((it.position + 1).toString()) - } - } - } - - override fun getItemId(position: Int): Long { - return getItem(position).position.toLong() - } -} - -object PreviewDiffCallback : DiffUtil.ItemCallback() { - override fun areItemsTheSame(oldItem: GalleryPreview, newItem: GalleryPreview): Boolean { - return oldItem.position == newItem.position - } - - override fun areContentsTheSame(oldItem: GalleryPreview, newItem: GalleryPreview): Boolean { - return oldItem == newItem - } -} diff --git a/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryPreviewsScene.kt b/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryPreviewsScene.kt index 9cb5339c5..d61cf78bb 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryPreviewsScene.kt +++ b/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryPreviewsScene.kt @@ -18,205 +18,147 @@ package com.hippo.ehviewer.ui.scene import android.content.Context import android.os.Bundle import android.view.LayoutInflater -import android.view.MenuItem import android.view.View import android.view.ViewGroup +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.items +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material.icons.filled.CallMissedOutgoing +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.res.stringResource import androidx.lifecycle.lifecycleScope +import androidx.navigation.fragment.findNavController +import arrow.core.partially1 import coil.imageLoader import com.hippo.app.BaseDialogBuilder -import com.hippo.easyrecyclerview.MarginItemDecoration import com.hippo.ehviewer.R import com.hippo.ehviewer.Settings import com.hippo.ehviewer.client.EhEngine import com.hippo.ehviewer.client.EhUrl import com.hippo.ehviewer.client.data.GalleryDetail import com.hippo.ehviewer.client.data.GalleryPreview -import com.hippo.ehviewer.client.exception.EhException import com.hippo.ehviewer.coil.imageRequest import com.hippo.ehviewer.databinding.DialogGoToBinding -import com.hippo.ehviewer.databinding.SceneGalleryPreviewsBinding +import com.hippo.ehviewer.ui.widget.CrystalCard +import com.hippo.ehviewer.ui.widget.EhAsyncPreview +import com.hippo.ehviewer.ui.widget.setMD3Content import com.hippo.util.getParcelableCompat -import com.hippo.widget.ContentLayout.ContentHelper -import com.hippo.widget.recyclerview.AutoGridLayoutManager -import com.hippo.widget.recyclerview.STRATEGY_SUITABLE_SIZE +import com.hippo.widget.ContentLayout +import com.hippo.widget.recyclerview.getSpanCountForSuitableSize +import com.hippo.widget.rememberContentState import eu.kanade.tachiyomi.util.lang.launchIO -import eu.kanade.tachiyomi.util.lang.withUIContext -import moe.tarsin.coroutines.runSuspendCatching import java.util.Locale -class GalleryPreviewsScene : BaseToolbarScene() { - private var _binding: SceneGalleryPreviewsBinding? = null - private val binding - get() = _binding!! - private var mGalleryDetail: GalleryDetail? = null - private var mAdapter: GalleryPreviewsAdapter? = null - private var mHelper: GalleryPreviewHelper? = null - private var mHasFirstRefresh = false - private var mNextPage: Boolean = false - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - if (savedInstanceState == null) { - onInit() - } else { - onRestore(savedInstanceState) - } - } - - private fun onInit() { - val args = arguments ?: return - mGalleryDetail = args.getParcelableCompat(KEY_GALLERY_DETAIL) - mNextPage = args.getBoolean(KEY_NEXT_PAGE) - } - - private fun onRestore(savedInstanceState: Bundle) { - mGalleryDetail = savedInstanceState.getParcelableCompat(KEY_GALLERY_DETAIL) - mHasFirstRefresh = savedInstanceState.getBoolean(KEY_HAS_FIRST_REFRESH) - } - - override fun onSaveInstanceState(outState: Bundle) { - super.onSaveInstanceState(outState) - val hasFirstRefresh: Boolean = if (mHelper != null && 1 == mHelper!!.shownViewIndex) { - false - } else { - mHasFirstRefresh - } - outState.putBoolean(KEY_HAS_FIRST_REFRESH, hasFirstRefresh) - outState.putParcelable(KEY_GALLERY_DETAIL, mGalleryDetail) - } - - override fun onCreateViewWithToolbar( +class GalleryPreviewsScene : BaseScene() { + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?, ): View { - _binding = SceneGalleryPreviewsBinding.inflate(inflater, container, false) - binding.contentLayout.hideFastScroll() - setLiftOnScrollTargetView(binding.contentLayout.recyclerView) - mAdapter = GalleryPreviewsAdapter { - mainActivity!!.startReaderActivity(mGalleryDetail!!, it.position) - } - binding.contentLayout.recyclerView.adapter = mAdapter - val columnWidth = Settings.thumbSize - val layoutManager = AutoGridLayoutManager(context, columnWidth) - layoutManager.setStrategy(STRATEGY_SUITABLE_SIZE) - binding.contentLayout.recyclerView.layoutManager = layoutManager - binding.contentLayout.recyclerView.clipToPadding = false - val padding = resources.getDimensionPixelSize(R.dimen.gallery_grid_margin_h) - val decoration = MarginItemDecoration(padding, padding, padding, padding, padding) - binding.contentLayout.recyclerView.addItemDecoration(decoration) - mHelper = GalleryPreviewHelper() - binding.contentLayout.setHelper(mHelper!!) + return ComposeView(inflater.context).apply { + setMD3Content { + val galleryDetail = rememberSaveable { requireArguments().getParcelableCompat(KEY_GALLERY_DETAIL)!! } + fun onPreviewCLick(index: Int) = mainActivity!!.startReaderActivity(galleryDetail, index) + val toNextPage = rememberSaveable { requireArguments().getBoolean(KEY_NEXT_PAGE) } + val scrollBehaviour = TopAppBarDefaults.pinnedScrollBehavior() + val showJumpMenuButton by rememberSaveable { mutableStateOf(false) } + // Padding is not subtracted here to have the same column count as gallery list and preview + val totalSpace = LocalConfiguration.current.screenWidthDp + val columnCount = getSpanCountForSuitableSize(totalSpace, Settings.thumbSizeDp) - // Only refresh for the first time - if (!mHasFirstRefresh) { - mHasFirstRefresh = true - mHelper!!.onGetPageData( - 0, - mGalleryDetail!!.previewPages, - 0, - null, - null, - mGalleryDetail!!.previewList, - ) - } - return binding.root - } - - override fun onDestroyView() { - super.onDestroyView() - if (null != mHelper) { - if (1 == mHelper!!.shownViewIndex) { - mHasFirstRefresh = false - } - } - binding.contentLayout.recyclerView.stopScroll() - _binding = null - mAdapter = null - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - setTitle(R.string.gallery_previews) - setNavigationIcon(R.drawable.v_arrow_left_dark_x24) - if (mGalleryDetail != null) { - if (mGalleryDetail!!.previewPages > 2) showMenu(R.menu.scene_gallery_previews) - } - runCatching { - if (mNextPage && mHelper!!.pages > 1) mHelper!!.goTo(1) - }.onFailure { - it.printStackTrace() - } - } - - override fun onMenuItemClick(item: MenuItem): Boolean { - val context = context ?: return false - val id = item.itemId - if (id == R.id.action_go_to) { - if (mHelper == null) { - return true - } - val pages = mHelper!!.pages - if (pages > 1 && mHelper!!.canGoTo()) { - showGoToDialog(context, pages, mHelper!!.pageForBottom) - } - return true - } - return false - } - - private inner class GalleryPreviewHelper : ContentHelper() { - override fun getPageData( - taskId: Int, - type: Int, - page: Int, - index: String?, - isNext: Boolean, - ) { - val detail = mGalleryDetail ?: run { - onGetException(taskId, EhException(getString(R.string.error_cannot_find_gallery))) - return - } - detail.run { - val url = EhUrl.getGalleryDetailUrl(gid, token, page, false) - lifecycleScope.launchIO { - runSuspendCatching { - EhEngine.getPreviewList(url) - }.onSuccess { result -> - if (Settings.preloadThumbAggressively) { - lifecycleScope.launchIO { - val previewList = result.first.first - previewList.forEach { - context.run { - imageLoader.enqueue(imageRequest(it)) + val state = rememberContentState(galleryDetail.previewList) + Scaffold( + topBar = { + TopAppBar( + title = { Text(stringResource(R.string.gallery_previews)) }, + navigationIcon = { + IconButton(onClick = { findNavController().popBackStack() }) { + Icon(imageVector = Icons.Default.ArrowBack, contentDescription = null) + } + }, + actions = { + if (showJumpMenuButton) { + IconButton(onClick = ::showGoto) { + Icon(imageVector = Icons.Default.CallMissedOutgoing, contentDescription = null) } } + }, + scrollBehavior = scrollBehaviour, + ) + }, + ) { + ContentLayout(state, getData = { page -> getPreviewListByPage(galleryDetail, page) }, modifier = Modifier.padding(top = it.calculateTopPadding())) { data: Array -> + LazyVerticalGrid( + columns = GridCells.Fixed(columnCount), + modifier = Modifier.nestedScroll(scrollBehaviour.nestedScrollConnection), + contentPadding = it, + horizontalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.gallery_grid_margin_h)), + verticalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.gallery_grid_margin_v)), + ) { + items(data) { + Column(horizontalAlignment = Alignment.CenterHorizontally) { + Box(contentAlignment = Alignment.Center) { + CrystalCard( + onClick = ::onPreviewCLick.partially1(it.position), + modifier = Modifier.fillMaxWidth().aspectRatio(0.6666667F), + ) { + EhAsyncPreview( + model = it, + modifier = Modifier.fillMaxSize(), + ) + } + } + Text((it.position + 1).toString()) + } } } - withUIContext { - mHelper?.takeIf { it.isCurrentTask(taskId) }?.onGetPageData(taskId, result.second, 0, null, null, result.first.first) - } - }.onFailure { throwable -> - withUIContext { - mHelper?.takeIf { it.isCurrentTask(taskId) }?.onGetException(taskId, throwable) - } } } } } + } - override val context - get() = this@GalleryPreviewsScene.requireContext() - - override fun notifyDataSetChanged(callback: () -> Unit) { - mAdapter?.submitList(data.toMutableList(), callback) + private fun showGoto() { + /* + val pages = mHelper!!.pages + if (pages > 1 && mHelper!!.canGoTo()) { + showGoToDialog(requireContext(), pages, mHelper!!.pageForBottom) } - override fun notifyDataSetChanged() {} + */ + } - override fun notifyItemRangeInserted(positionStart: Int, itemCount: Int) {} - - override fun isDuplicate(d1: GalleryPreview, d2: GalleryPreview): Boolean { - return false + private suspend fun getPreviewListByPage(galleryDetail: GalleryDetail, page: Int): List { + galleryDetail.run { + val url = EhUrl.getGalleryDetailUrl(gid, token, page, false) + val result = EhEngine.getPreviewList(url) + if (Settings.preloadThumbAggressively) { + lifecycleScope.launchIO { context?.run { result.first.first.forEach { imageLoader.enqueue(imageRequest(it)) } } } + } + return result.first.first } } @@ -231,7 +173,7 @@ class GalleryPreviewsScene : BaseToolbarScene() { .setView(dialogBinding.root) .setPositiveButton(android.R.string.ok) { _, _ -> val page = (dialogBinding.slider.value - 1).toInt() - mHelper!!.goTo(page) + // mHelper!!.goTo(page) } .create() dialog.show() @@ -240,6 +182,5 @@ class GalleryPreviewsScene : BaseToolbarScene() { companion object { const val KEY_GALLERY_DETAIL = "gallery_detail" const val KEY_NEXT_PAGE = "next_page" - private const val KEY_HAS_FIRST_REFRESH = "has_first_refresh" } } diff --git a/app/src/main/java/com/hippo/widget/ComposeContentLayout.kt b/app/src/main/java/com/hippo/widget/ComposeContentLayout.kt index ed7f2e894..3d7ad39ee 100644 --- a/app/src/main/java/com/hippo/widget/ComposeContentLayout.kt +++ b/app/src/main/java/com/hippo/widget/ComposeContentLayout.kt @@ -3,28 +3,75 @@ package com.hippo.widget import android.os.Parcelable import androidx.compose.foundation.layout.Box import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.saveable.listSaver import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue +import androidx.compose.runtime.snapshots.SnapshotStateList +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import com.hippo.widget.pullrefresh.PullRefreshIndicator import com.hippo.widget.pullrefresh.pullRefresh import com.hippo.widget.pullrefresh.rememberPullRefreshState +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import moe.tarsin.coroutines.runSuspendCatching @Composable inline fun ContentLayout( - content: @Composable (Array) -> Unit, + state: ContentState, + noinline getData: suspend (Int) -> List, modifier: Modifier = Modifier, + content: @Composable (Array) -> Unit, ) { var refreshing by rememberSaveable { mutableStateOf(false) } + val coroutineScope = rememberCoroutineScope { Dispatchers.IO } val refreshState = rememberPullRefreshState(refreshing = refreshing, onRefresh = { refreshing = true + if (state.startPage == 0) { + coroutineScope.launch { + val result = runSuspendCatching { getData(0) }.getOrNull() + result?.let { + state.data.clear() + state.data.addAll(it) + } + }.invokeOnCompletion { refreshing = false } + } else { + TODO() + } }) - val data = rememberSaveable { mutableStateListOf() } - Box(modifier = modifier.pullRefresh(refreshState)) { - content(data.toTypedArray()) - PullRefreshIndicator(refreshing = refreshing, state = refreshState) + Box(modifier = Modifier.pullRefresh(refreshState)) { + content(state.data.toTypedArray()) + PullRefreshIndicator(refreshing = refreshing, state = refreshState, modifier = modifier.align(Alignment.TopCenter)) } } + +class ContentState( + val data: SnapshotStateList, + _startPage: MutableState, + _endPage: MutableState, +) { + var startPage by _startPage + var endPage by _endPage +} + +@Composable +fun rememberContentState( + initialData: List, +): ContentState { + val snapshotStateListSaver = remember { + listSaver( + save = { it.toList() }, + restore = { mutableStateListOf().apply { addAll(it) } }, + ) + } + val data = rememberSaveable(saver = snapshotStateListSaver) { mutableStateListOf().apply { addAll(initialData) } } + val startPage = rememberSaveable { mutableStateOf(0) } + val endPage = rememberSaveable { mutableStateOf(0) } + return remember { ContentState(data, startPage, endPage) } +} diff --git a/app/src/main/res/layout/scene_gallery_previews.xml b/app/src/main/res/layout/scene_gallery_previews.xml deleted file mode 100644 index e34af5659..000000000 --- a/app/src/main/res/layout/scene_gallery_previews.xml +++ /dev/null @@ -1,23 +0,0 @@ - - -