diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 9b6ee3686..b936a49d0 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -269,6 +269,24 @@ + + + + + + + + + + + + + + + (), + AppBarOwner, + SnackbarOwner { + + @Inject + lateinit var pageSaveHelperFactory: PageSaveHelper.Factory + + override val appBar: AppBarLayout + get() = viewBinding.appbar + + override val snackbarHost: CoordinatorLayout + get() = viewBinding.root + + private lateinit var pageSaveHelper: PageSaveHelper + private val viewModel by viewModels() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(ActivityPickerBinding.inflate(layoutInflater)) + setDisplayHomeAsUp(isEnabled = true, showUpAsClose = false) + pageSaveHelper = pageSaveHelperFactory.create(this) + viewModel.onError.observeEvent(this, DialogErrorObserver(viewBinding.container, null)) + viewModel.onFileReady.observeEvent(this, ::finishWithResult) + viewModel.isLoading.observe(this, ::onLoadingStateChanged) + val fm = supportFragmentManager + if (fm.findFragmentById(R.id.container) == null) { + fm.commit { + setReorderingAllowed(true) + if (intent?.hasExtra(AppRouter.KEY_MANGA) == true) { + replace(R.id.container, PagePickerFragment::class.java, intent.extras) + } else { + replace(R.id.container, MangaPickerFragment::class.java, null) + } + } + } + } + + override fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat { + val typeMask = WindowInsetsCompat.Type.systemBars() + val bars = insets.getInsets(typeMask) + viewBinding.appbar.updatePadding( + left = bars.left, + right = bars.right, + top = bars.top, + ) + return insets.consume(v, typeMask, top = true) + } + + fun onMangaPicked(manga: Manga) { + val args = Bundle(1) + args.putLong(AppRouter.KEY_ID, manga.id) + supportFragmentManager.commit { + setReorderingAllowed(true) + replace(R.id.container, PagePickerFragment::class.java, args) + addToBackStack(null) + } + } + + fun onPagePicked(manga: Manga, page: ReaderPage) { + val task = PageSaveHelper.Task( + manga = manga, + chapterId = page.chapterId, + pageNumber = page.index + 1, + page = page.toMangaPage(), + ) + viewModel.savePageToTempFile(pageSaveHelper, task) + } + + private fun onLoadingStateChanged(isLoading: Boolean) { + viewBinding.container.isGone = isLoading + viewBinding.progressBar.isVisible = isLoading + } + + private fun finishWithResult(file: File) { + val uri = FileProvider.getUriForFile(applicationContext, "${BuildConfig.APPLICATION_ID}.files", file) + val result = Intent() + result.setData(uri) + result.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + setResult(RESULT_OK, result) + finish() + } +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/picker/ui/PageImagePickContract.kt b/app/src/main/kotlin/org/koitharu/kotatsu/picker/ui/PageImagePickContract.kt new file mode 100644 index 000000000..8c72d5ecf --- /dev/null +++ b/app/src/main/kotlin/org/koitharu/kotatsu/picker/ui/PageImagePickContract.kt @@ -0,0 +1,18 @@ +package org.koitharu.kotatsu.picker.ui + +import android.content.Context +import android.content.Intent +import android.net.Uri +import androidx.activity.result.contract.ActivityResultContract +import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga +import org.koitharu.kotatsu.core.nav.AppRouter +import org.koitharu.kotatsu.parsers.model.Manga + +class PageImagePickContract : ActivityResultContract() { + + override fun createIntent(context: Context, input: Manga?): Intent = + Intent(context, PageImagePickActivity::class.java) + .putExtra(AppRouter.KEY_MANGA, input?.let { ParcelableManga(it) }) + + override fun parseResult(resultCode: Int, intent: Intent?): Uri? = intent?.data +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/picker/ui/PageImagePickViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/picker/ui/PageImagePickViewModel.kt new file mode 100644 index 000000000..b89993cf5 --- /dev/null +++ b/app/src/main/kotlin/org/koitharu/kotatsu/picker/ui/PageImagePickViewModel.kt @@ -0,0 +1,23 @@ +package org.koitharu.kotatsu.picker.ui + +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.Dispatchers +import org.koitharu.kotatsu.core.ui.BaseViewModel +import org.koitharu.kotatsu.core.util.ext.MutableEventFlow +import org.koitharu.kotatsu.core.util.ext.call +import org.koitharu.kotatsu.reader.ui.PageSaveHelper +import java.io.File +import javax.inject.Inject + +@HiltViewModel +class PageImagePickViewModel @Inject constructor() : BaseViewModel() { + + val onFileReady = MutableEventFlow() + + fun savePageToTempFile(pageSaveHelper: PageSaveHelper, task: PageSaveHelper.Task) { + launchLoadingJob(Dispatchers.Default) { + val file = pageSaveHelper.saveToTempFile(task) + onFileReady.call(file) + } + } +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/picker/ui/manga/MangaPickerFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/picker/ui/manga/MangaPickerFragment.kt new file mode 100644 index 000000000..358f47fed --- /dev/null +++ b/app/src/main/kotlin/org/koitharu/kotatsu/picker/ui/manga/MangaPickerFragment.kt @@ -0,0 +1,32 @@ +package org.koitharu.kotatsu.picker.ui.manga + +import android.view.View +import androidx.fragment.app.viewModels +import dagger.hilt.android.AndroidEntryPoint +import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.list.ui.MangaListFragment +import org.koitharu.kotatsu.parsers.model.Manga +import org.koitharu.kotatsu.picker.ui.PageImagePickActivity + +@AndroidEntryPoint +class MangaPickerFragment : MangaListFragment() { + + override val isSwipeRefreshEnabled = false + + override val viewModel by viewModels() + + override fun onScrolledToEnd() = Unit + + override fun onItemClick(item: Manga, view: View) { + (activity as PageImagePickActivity).onMangaPicked(item) + } + + override fun onResume() { + super.onResume() + activity?.setTitle(R.string.pick_manga_page) + } + + override fun onItemLongClick(item: Manga, view: View): Boolean = false + + override fun onItemContextClick(item: Manga, view: View): Boolean = false +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/picker/ui/manga/MangaPickerViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/picker/ui/manga/MangaPickerViewModel.kt new file mode 100644 index 000000000..a8b692989 --- /dev/null +++ b/app/src/main/kotlin/org/koitharu/kotatsu/picker/ui/manga/MangaPickerViewModel.kt @@ -0,0 +1,57 @@ +package org.koitharu.kotatsu.picker.ui.manga + +import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.plus +import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.core.parser.MangaDataRepository +import org.koitharu.kotatsu.core.prefs.AppSettings +import org.koitharu.kotatsu.favourites.domain.FavouritesRepository +import org.koitharu.kotatsu.history.data.HistoryRepository +import org.koitharu.kotatsu.list.domain.MangaListMapper +import org.koitharu.kotatsu.list.ui.MangaListViewModel +import org.koitharu.kotatsu.list.ui.model.ListHeader +import org.koitharu.kotatsu.list.ui.model.ListModel +import org.koitharu.kotatsu.list.ui.model.LoadingState +import javax.inject.Inject + +@HiltViewModel +class MangaPickerViewModel @Inject constructor( + private val settings: AppSettings, + mangaDataRepository: MangaDataRepository, + private val historyRepository: HistoryRepository, + private val favouritesRepository: FavouritesRepository, + private val mangaListMapper: MangaListMapper, +) : MangaListViewModel(settings, mangaDataRepository) { + + override val content: StateFlow> + get() = flow { + emit(loadList()) + }.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Lazily, listOf(LoadingState)) + + override fun onRefresh() = Unit + + override fun onRetry() = Unit + + private suspend fun loadList() = buildList { + val history = historyRepository.getList(0, Int.MAX_VALUE) + if (history.isNotEmpty()) { + add(ListHeader(R.string.history)) + mangaListMapper.toListModelList(this, history, settings.listMode) + } + val categories = favouritesRepository.observeCategoriesForLibrary().first() + for (category in categories) { + val favorites = favouritesRepository.getManga(category.id) + if (favorites.isNotEmpty()) { + add(ListHeader(category.title)) + mangaListMapper.toListModelList(this, favorites, settings.listMode) + } + } + } +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/picker/ui/page/PagePickerFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/picker/ui/page/PagePickerFragment.kt new file mode 100644 index 000000000..a8497fa4b --- /dev/null +++ b/app/src/main/kotlin/org/koitharu/kotatsu/picker/ui/page/PagePickerFragment.kt @@ -0,0 +1,165 @@ +package org.koitharu.kotatsu.picker.ui.page + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.view.WindowInsetsCompat +import androidx.core.view.isInvisible +import androidx.core.view.isVisible +import androidx.fragment.app.viewModels +import androidx.lifecycle.Lifecycle +import androidx.recyclerview.widget.GridLayoutManager +import androidx.recyclerview.widget.RecyclerView +import dagger.hilt.android.AndroidEntryPoint +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.ui.BaseFragment +import org.koitharu.kotatsu.core.ui.list.BoundsScrollListener +import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener +import org.koitharu.kotatsu.core.ui.util.PagerNestedScrollHelper +import org.koitharu.kotatsu.core.util.ext.consumeAll +import org.koitharu.kotatsu.core.util.ext.observe +import org.koitharu.kotatsu.core.util.ext.observeEvent +import org.koitharu.kotatsu.core.util.ext.showOrHide +import org.koitharu.kotatsu.databinding.FragmentPagesBinding +import org.koitharu.kotatsu.details.ui.pager.pages.PageThumbnail +import org.koitharu.kotatsu.details.ui.pager.pages.PageThumbnailAdapter +import org.koitharu.kotatsu.list.ui.GridSpanResolver +import org.koitharu.kotatsu.list.ui.adapter.ListItemType +import org.koitharu.kotatsu.list.ui.adapter.TypedListSpacingDecoration +import org.koitharu.kotatsu.list.ui.model.ListModel +import org.koitharu.kotatsu.parsers.util.ifNullOrEmpty +import org.koitharu.kotatsu.picker.ui.PageImagePickActivity +import javax.inject.Inject + +@AndroidEntryPoint +class PagePickerFragment : + BaseFragment(), + OnListItemClickListener { + + @Inject + lateinit var settings: AppSettings + + private val viewModel by viewModels() + + private var thumbnailsAdapter: PageThumbnailAdapter? = null + private var spanResolver: GridSpanResolver? = null + private var scrollListener: ScrollListener? = null + + private val spanSizeLookup = SpanSizeLookup() + + override fun onCreateViewBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentPagesBinding { + return FragmentPagesBinding.inflate(inflater, container, false) + } + + override fun onViewBindingCreated(binding: FragmentPagesBinding, savedInstanceState: Bundle?) { + super.onViewBindingCreated(binding, savedInstanceState) + spanResolver = GridSpanResolver(binding.root.resources) + thumbnailsAdapter = PageThumbnailAdapter( + clickListener = this@PagePickerFragment, + ) + viewModel.gridScale.observe(viewLifecycleOwner, ::onGridScaleChanged) // before rv initialization + with(binding.recyclerView) { + addItemDecoration(TypedListSpacingDecoration(context, false)) + adapter = thumbnailsAdapter + setHasFixedSize(true) + PagerNestedScrollHelper(this).bind(viewLifecycleOwner) + addOnLayoutChangeListener(spanResolver) + addOnScrollListener(ScrollListener().also { scrollListener = it }) + (layoutManager as GridLayoutManager).let { + it.spanSizeLookup = spanSizeLookup + it.spanCount = checkNotNull(spanResolver).spanCount + } + } + viewModel.thumbnails.observe(viewLifecycleOwner, ::onThumbnailsChanged) + viewModel.isNoChapters.observe(viewLifecycleOwner, ::onNoChaptersChanged) + viewModel.onError.observeEvent(viewLifecycleOwner, SnackbarErrorObserver(binding.recyclerView, this)) + viewModel.isLoading.observe(viewLifecycleOwner) { binding.progressBar.showOrHide(it) } + viewModel.isLoadingDown.observe(viewLifecycleOwner) { binding.progressBarBottom.showOrHide(it) } + viewModel.manga.observe(viewLifecycleOwner, Lifecycle.State.RESUMED) { + activity?.title = it?.toManga()?.title.ifNullOrEmpty { getString(R.string.pick_manga_page) } + } + } + + override fun onDestroyView() { + spanResolver = null + scrollListener = null + thumbnailsAdapter = null + spanSizeLookup.invalidateCache() + super.onDestroyView() + } + + override fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat { + val typeBask = WindowInsetsCompat.Type.systemBars() + val barsInsets = insets.getInsets(typeBask) + viewBinding?.recyclerView?.setPadding( + barsInsets.left, + barsInsets.top, + barsInsets.right, + barsInsets.bottom, + ) + return insets.consumeAll(typeBask) + } + + override fun onItemClick(item: PageThumbnail, view: View) { + val manga = viewModel.manga.value?.toManga() ?: return + (activity as PageImagePickActivity).onPagePicked(manga, item.page) + } + + override fun onItemLongClick(item: PageThumbnail, view: View): Boolean = false + + override fun onItemContextClick(item: PageThumbnail, view: View): Boolean = false + + private suspend fun onThumbnailsChanged(list: List) { + val adapter = thumbnailsAdapter ?: return + adapter.emit(list) + spanSizeLookup.invalidateCache() + viewBinding?.recyclerView?.let { + scrollListener?.postInvalidate(it) + } + } + + private fun onGridScaleChanged(scale: Float) { + spanSizeLookup.invalidateCache() + spanResolver?.setGridSize(scale, requireViewBinding().recyclerView) + } + + private fun onNoChaptersChanged(isNoChapters: Boolean) { + with(viewBinding ?: return) { + textViewHolder.isVisible = isNoChapters + recyclerView.isInvisible = isNoChapters + } + } + + private inner class ScrollListener : BoundsScrollListener(3, 3) { + + override fun onScrolledToStart(recyclerView: RecyclerView) = Unit + + override fun onScrolledToEnd(recyclerView: RecyclerView) { + viewModel.loadNextChapter() + } + } + + private inner class SpanSizeLookup : GridLayoutManager.SpanSizeLookup() { + + init { + isSpanIndexCacheEnabled = true + isSpanGroupIndexCacheEnabled = true + } + + override fun getSpanSize(position: Int): Int { + val total = (viewBinding?.recyclerView?.layoutManager as? GridLayoutManager)?.spanCount ?: return 1 + return when (thumbnailsAdapter?.getItemViewType(position)) { + ListItemType.PAGE_THUMB.ordinal -> 1 + else -> total + } + } + + fun invalidateCache() { + invalidateSpanGroupIndexCache() + invalidateSpanIndexCache() + } + } +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/picker/ui/page/PagePickerViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/picker/ui/page/PagePickerViewModel.kt new file mode 100644 index 000000000..c8821652b --- /dev/null +++ b/app/src/main/kotlin/org/koitharu/kotatsu/picker/ui/page/PagePickerViewModel.kt @@ -0,0 +1,106 @@ +package org.koitharu.kotatsu.picker.ui.page + +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.plus +import org.koitharu.kotatsu.core.nav.MangaIntent +import org.koitharu.kotatsu.core.prefs.AppSettings +import org.koitharu.kotatsu.core.prefs.observeAsStateFlow +import org.koitharu.kotatsu.core.ui.BaseViewModel +import org.koitharu.kotatsu.core.util.ext.firstNotNull +import org.koitharu.kotatsu.details.data.MangaDetails +import org.koitharu.kotatsu.details.domain.DetailsLoadUseCase +import org.koitharu.kotatsu.details.ui.pager.pages.PageThumbnail +import org.koitharu.kotatsu.list.ui.model.ListHeader +import org.koitharu.kotatsu.list.ui.model.ListModel +import org.koitharu.kotatsu.reader.domain.ChaptersLoader +import javax.inject.Inject + +@HiltViewModel +class PagePickerViewModel @Inject constructor( + savedStateHandle: SavedStateHandle, + private val chaptersLoader: ChaptersLoader, + private val detailsLoadUseCase: DetailsLoadUseCase, + settings: AppSettings, +) : BaseViewModel() { + + private val intent = MangaIntent(savedStateHandle) + + private var loadingJob: Job? = null + private var loadingNextJob: Job? = null + + val thumbnails = MutableStateFlow>(emptyList()) + val isLoadingDown = MutableStateFlow(false) + val manga = MutableStateFlow(intent.manga?.let { MangaDetails(it) }) + + val isNoChapters = manga.map { + it != null && it.isLoaded && it.allChapters.isEmpty() + } + + val gridScale = settings.observeAsStateFlow( + scope = viewModelScope + Dispatchers.Default, + key = AppSettings.KEY_GRID_SIZE_PAGES, + valueProducer = { gridSizePages / 100f }, + ) + + init { + loadingJob = launchLoadingJob(Dispatchers.Default) { + doInit() + } + } + + private suspend fun doInit() { + val details = detailsLoadUseCase.invoke(intent, force = false) + .onEach { manga.value = it } + .first { x -> x.isLoaded } + chaptersLoader.init(details) + val initialChapterId = details.allChapters.firstOrNull()?.id ?: return + if (!chaptersLoader.hasPages(initialChapterId)) { + chaptersLoader.loadSingleChapter(initialChapterId) + } + updateList() + } + + fun loadNextChapter() { + if (loadingJob?.isActive == true || loadingNextJob?.isActive == true) { + return + } + loadingNextJob = launchJob(Dispatchers.Default) { + isLoadingDown.value = true + try { + val currentId = chaptersLoader.last().chapterId + chaptersLoader.loadPrevNextChapter(manga.firstNotNull(), currentId, isNext = true) + updateList() + } finally { + isLoadingDown.value = false + } + } + } + + private fun updateList() { + val snapshot = chaptersLoader.snapshot() + val pages = buildList(snapshot.size + chaptersLoader.size + 2) { + var previousChapterId = 0L + for (page in snapshot) { + if (page.chapterId != previousChapterId) { + chaptersLoader.peekChapter(page.chapterId)?.let { + add(ListHeader(it)) + } + previousChapterId = page.chapterId + } + this += PageThumbnail( + isCurrent = false, + page = page, + ) + } + } + thumbnails.value = pages + } +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/PageSaveHelper.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/PageSaveHelper.kt index ac34f89b3..d1d593ead 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/PageSaveHelper.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/PageSaveHelper.kt @@ -35,7 +35,6 @@ import org.koitharu.kotatsu.core.util.ext.toFileNameSafe import org.koitharu.kotatsu.core.util.ext.toFileOrNull import org.koitharu.kotatsu.core.util.ext.writeAllCancellable import org.koitharu.kotatsu.parsers.model.Manga -import org.koitharu.kotatsu.parsers.model.MangaChapter import org.koitharu.kotatsu.parsers.model.MangaPage import org.koitharu.kotatsu.reader.domain.PageLoader import java.io.File @@ -72,6 +71,16 @@ class PageSaveHelper @AssistedInject constructor( else -> saveImpl(tasks) } + suspend fun saveToTempFile(task: Task): File { + val pageLoader = getPageLoader() + val pageUrl = pageLoader.getPageUrl(task.page).toUri() + val pageUri = pageLoader.loadPage(task.page, force = false) + val proposedName = task.getFileBaseName() + "." + getPageExtension(pageUrl, pageUri) + val destination = File(checkNotNull(context.getExternalFilesDir(TEMP_DIR)), proposedName) + copyImpl(pageUri, destination.toUri()) + return destination + } + private suspend fun saveImpl(task: Task): Uri { val pageLoader = getPageLoader() val pageUrl = pageLoader.getPageUrl(task.page).toUri() @@ -206,5 +215,6 @@ class PageSaveHelper @AssistedInject constructor( private const val MAX_BASENAME_LENGTH = 12 private const val EXTENSION_FALLBACK = "png" + private const val TEMP_DIR = "pages" } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt index 985b05a60..fba48bc93 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt @@ -108,7 +108,7 @@ class ReaderViewModel @Inject constructor( init { selectedBranch.value = savedStateHandle.get(ReaderIntent.EXTRA_BRANCH) readingState.value = savedStateHandle[ReaderIntent.EXTRA_STATE] - mangaDetails.value = intent.manga?.let { MangaDetails(it, null, null, null, false) } + mangaDetails.value = intent.manga?.let { MangaDetails(it) } } val readerMode = MutableStateFlow(null) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/settings/override/OverrideConfigActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/settings/override/OverrideConfigActivity.kt index 667823721..3b8a97495 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/settings/override/OverrideConfigActivity.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/settings/override/OverrideConfigActivity.kt @@ -1,10 +1,11 @@ package org.koitharu.kotatsu.settings.override import android.content.Intent +import android.net.Uri import android.os.Bundle import android.view.View -import androidx.activity.result.PickVisualMediaRequest -import androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia +import androidx.activity.result.ActivityResultCallback +import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.viewModels import androidx.core.view.WindowInsetsCompat import androidx.core.view.isVisible @@ -16,29 +17,23 @@ import org.koitharu.kotatsu.core.ui.BaseActivity import org.koitharu.kotatsu.core.ui.model.MangaOverride import org.koitharu.kotatsu.core.util.ext.consumeAll import org.koitharu.kotatsu.core.util.ext.getDisplayMessage -import org.koitharu.kotatsu.core.util.ext.getThemeColor import org.koitharu.kotatsu.core.util.ext.observe import org.koitharu.kotatsu.core.util.ext.observeEvent import org.koitharu.kotatsu.core.util.ext.tryLaunch import org.koitharu.kotatsu.databinding.ActivityOverrideEditBinding import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.util.ifNullOrEmpty -import androidx.appcompat.R as appcompatR +import org.koitharu.kotatsu.picker.ui.PageImagePickContract import com.google.android.material.R as materialR @AndroidEntryPoint -class OverrideConfigActivity : BaseActivity(), View.OnClickListener { +class OverrideConfigActivity : BaseActivity(), View.OnClickListener, + ActivityResultCallback { private val viewModel: OverrideConfigViewModel by viewModels() - private val pickCoverFileLauncher = registerForActivityResult( - PickVisualMedia(), - ) { uri -> - if (uri != null) { - contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION) - viewModel.updateCover(uri.toString()) - } - } + private val pickCoverFileLauncher = registerForActivityResult(ActivityResultContracts.OpenDocument(), this) + private val pickPageLauncher = registerForActivityResult(PageImagePickContract(), this) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -67,6 +62,15 @@ class OverrideConfigActivity : BaseActivity(), View return insets.consumeAll(typeMask) } + override fun onActivityResult(result: Uri?) { + if (result != null) { + if (result.host?.startsWith(packageName) != true) { + contentResolver.takePersistableUriPermission(result, Intent.FLAG_GRANT_READ_URI_PERMISSION) + } + viewModel.updateCover(result.toString()) + } + } + override fun onClick(v: View) { when (v.id) { R.id.button_done -> viewModel.save( @@ -77,11 +81,7 @@ class OverrideConfigActivity : BaseActivity(), View R.id.button_reset_cover -> viewModel.updateCover(null) R.id.button_pick_file -> { - val request = PickVisualMediaRequest.Builder() - .setMediaType(PickVisualMedia.ImageOnly) - .setAccentColor(getThemeColor(appcompatR.attr.colorAccent).toLong()) - .build() - if (!pickCoverFileLauncher.tryLaunch(request)) { + if (!pickCoverFileLauncher.tryLaunch(arrayOf("image/*"))) { Snackbar.make( viewBinding.imageViewCover, R.string.operation_not_supported, @@ -89,6 +89,11 @@ class OverrideConfigActivity : BaseActivity(), View ).show() } } + + R.id.button_pick_page -> { + val manga = viewModel.data.value?.first + pickPageLauncher.launch(manga) + } } } diff --git a/app/src/main/res/layout/activity_override_edit.xml b/app/src/main/res/layout/activity_override_edit.xml index 0f32fb84d..a5176db77 100644 --- a/app/src/main/res/layout/activity_override_edit.xml +++ b/app/src/main/res/layout/activity_override_edit.xml @@ -83,7 +83,6 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:text="@string/pick_manga_page" - android:visibility="gone" app:drawableStartCompat="@drawable/ic_grid" app:layout_constraintEnd_toEndOf="@id/textView_cover_title" app:layout_constraintStart_toStartOf="@id/textView_cover_title" diff --git a/app/src/main/res/layout/activity_picker.xml b/app/src/main/res/layout/activity_picker.xml new file mode 100644 index 000000000..3f481048e --- /dev/null +++ b/app/src/main/res/layout/activity_picker.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + +