Move sources from java to kotlin dir

This commit is contained in:
Koitharu
2023-05-22 18:16:50 +03:00
parent a8f5714b35
commit c3216871ed
711 changed files with 1 additions and 0 deletions

View File

@@ -0,0 +1,38 @@
package org.koitharu.kotatsu.widget
import android.appwidget.AppWidgetManager
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import androidx.room.InvalidationTracker
import dagger.hilt.android.qualifiers.ApplicationContext
import org.koitharu.kotatsu.core.db.TABLE_FAVOURITES
import org.koitharu.kotatsu.core.db.TABLE_HISTORY
import org.koitharu.kotatsu.widget.recent.RecentWidgetProvider
import org.koitharu.kotatsu.widget.shelf.ShelfWidgetProvider
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class WidgetUpdater @Inject constructor(
@ApplicationContext private val context: Context,
) : InvalidationTracker.Observer(TABLE_HISTORY, TABLE_FAVOURITES) {
override fun onInvalidated(tables: Set<String>) {
if (TABLE_HISTORY in tables) {
updateWidgets(RecentWidgetProvider::class.java)
}
if (TABLE_FAVOURITES in tables) {
updateWidgets(ShelfWidgetProvider::class.java)
}
}
private fun updateWidgets(cls: Class<*>) {
val intent = Intent(context, cls)
intent.action = AppWidgetManager.ACTION_APPWIDGET_UPDATE
val ids = AppWidgetManager.getInstance(context)
.getAppWidgetIds(ComponentName(context, cls))
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, ids)
context.sendBroadcast(intent)
}
}

View File

@@ -0,0 +1,77 @@
package org.koitharu.kotatsu.widget.recent
import android.content.Context
import android.content.Intent
import android.widget.RemoteViews
import android.widget.RemoteViewsService
import androidx.core.graphics.drawable.toBitmap
import coil.ImageLoader
import coil.executeBlocking
import coil.request.ImageRequest
import coil.size.Size
import coil.transform.RoundedCornersTransformation
import kotlinx.coroutines.runBlocking
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.parser.MangaIntent
import org.koitharu.kotatsu.core.util.ext.getDrawableOrThrow
import org.koitharu.kotatsu.history.domain.HistoryRepository
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.util.replaceWith
class RecentListFactory(
private val context: Context,
private val historyRepository: HistoryRepository,
private val coil: ImageLoader,
) : RemoteViewsService.RemoteViewsFactory {
private val dataSet = ArrayList<Manga>()
private val transformation = RoundedCornersTransformation(
context.resources.getDimension(R.dimen.appwidget_corner_radius_inner),
)
private val coverSize = Size(
context.resources.getDimensionPixelSize(R.dimen.widget_cover_width),
context.resources.getDimensionPixelSize(R.dimen.widget_cover_height),
)
override fun onCreate() = Unit
override fun getLoadingView() = null
override fun getItemId(position: Int) = dataSet[position].id
override fun onDataSetChanged() {
val data = runBlocking { historyRepository.getList(0, 10) }
dataSet.replaceWith(data)
}
override fun hasStableIds() = true
override fun getViewAt(position: Int): RemoteViews {
val views = RemoteViews(context.packageName, R.layout.item_recent)
val item = dataSet[position]
runCatching {
coil.executeBlocking(
ImageRequest.Builder(context)
.data(item.coverUrl)
.size(coverSize)
.tag(item.source)
.transformations(transformation)
.build(),
).getDrawableOrThrow().toBitmap()
}.onSuccess { cover ->
views.setImageViewBitmap(R.id.imageView_cover, cover)
}.onFailure {
views.setImageViewResource(R.id.imageView_cover, R.drawable.ic_placeholder)
}
val intent = Intent()
intent.putExtra(MangaIntent.KEY_ID, item.id)
views.setOnClickFillInIntent(R.id.imageView_cover, intent)
return views
}
override fun getCount() = dataSet.size
override fun getViewTypeCount() = 1
override fun onDestroy() = Unit
}

View File

@@ -0,0 +1,44 @@
package org.koitharu.kotatsu.widget.recent
import android.app.PendingIntent
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.widget.RemoteViews
import androidx.core.app.PendingIntentCompat
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.reader.ui.ReaderActivity
class RecentWidgetProvider : AppWidgetProvider() {
override fun onUpdate(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetIds: IntArray
) {
appWidgetIds.forEach { id ->
val views = RemoteViews(context.packageName, R.layout.widget_recent)
val adapter = Intent(context, RecentWidgetService::class.java)
adapter.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, id)
adapter.data = Uri.parse(adapter.toUri(Intent.URI_INTENT_SCHEME))
views.setRemoteAdapter(R.id.stackView, adapter)
val intent = Intent(context, ReaderActivity::class.java)
intent.action = ReaderActivity.ACTION_MANGA_READ
views.setPendingIntentTemplate(
R.id.stackView,
PendingIntentCompat.getActivity(
context,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT,
true,
),
)
views.setEmptyView(R.id.stackView, R.id.textView_holder)
appWidgetManager.updateAppWidget(id, views)
}
appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetIds, R.id.stackView)
}
}

View File

@@ -0,0 +1,22 @@
package org.koitharu.kotatsu.widget.recent
import android.content.Intent
import android.widget.RemoteViewsService
import coil.ImageLoader
import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.history.domain.HistoryRepository
import javax.inject.Inject
@AndroidEntryPoint
class RecentWidgetService : RemoteViewsService() {
@Inject
lateinit var historyRepository: HistoryRepository
@Inject
lateinit var coil: ImageLoader
override fun onGetViewFactory(intent: Intent): RemoteViewsFactory {
return RecentListFactory(applicationContext, historyRepository, coil)
}
}

View File

@@ -0,0 +1,114 @@
package org.koitharu.kotatsu.widget.shelf
import android.app.Activity
import android.appwidget.AppWidgetManager
import android.content.Intent
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import androidx.activity.viewModels
import androidx.core.graphics.Insets
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
import androidx.core.view.updatePadding
import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver
import org.koitharu.kotatsu.core.prefs.AppWidgetConfig
import org.koitharu.kotatsu.core.ui.BaseActivity
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.databinding.ActivityCategoriesBinding
import org.koitharu.kotatsu.widget.shelf.adapter.CategorySelectAdapter
import org.koitharu.kotatsu.widget.shelf.model.CategoryItem
import com.google.android.material.R as materialR
@AndroidEntryPoint
class ShelfConfigActivity :
BaseActivity<ActivityCategoriesBinding>(),
OnListItemClickListener<CategoryItem>,
View.OnClickListener {
private val viewModel by viewModels<ShelfConfigViewModel>()
private lateinit var adapter: CategorySelectAdapter
private lateinit var config: AppWidgetConfig
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(ActivityCategoriesBinding.inflate(layoutInflater))
supportActionBar?.run {
setDisplayHomeAsUpEnabled(true)
setHomeAsUpIndicator(materialR.drawable.abc_ic_clear_material)
}
adapter = CategorySelectAdapter(this)
viewBinding.recyclerView.adapter = adapter
viewBinding.buttonDone.isVisible = true
viewBinding.buttonDone.setOnClickListener(this)
viewBinding.fabAdd.hide()
val appWidgetId = intent?.getIntExtra(
AppWidgetManager.EXTRA_APPWIDGET_ID,
AppWidgetManager.INVALID_APPWIDGET_ID,
) ?: AppWidgetManager.INVALID_APPWIDGET_ID
if (appWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) {
finishAfterTransition()
return
}
config = AppWidgetConfig(this, appWidgetId)
viewModel.checkedId = config.categoryId
viewModel.content.observe(this, this::onContentChanged)
viewModel.onError.observe(this, SnackbarErrorObserver(viewBinding.recyclerView, null))
}
override fun onClick(v: View) {
when (v.id) {
R.id.button_done -> {
config.categoryId = viewModel.checkedId
updateWidget()
setResult(
Activity.RESULT_OK,
Intent().putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, config.widgetId),
)
finish()
}
}
}
override fun onItemClick(item: CategoryItem, view: View) {
viewModel.checkedId = item.id
}
override fun onWindowInsetsChanged(insets: Insets) {
viewBinding.fabAdd.updateLayoutParams<ViewGroup.MarginLayoutParams> {
rightMargin = topMargin + insets.right
leftMargin = topMargin + insets.left
bottomMargin = topMargin + insets.bottom
}
viewBinding.recyclerView.updatePadding(
left = insets.left,
right = insets.right,
bottom = insets.bottom,
)
with(viewBinding.toolbar) {
updatePadding(
left = insets.left,
right = insets.right,
)
updateLayoutParams<ViewGroup.MarginLayoutParams> {
topMargin = insets.top
}
}
}
private fun onContentChanged(categories: List<CategoryItem>) {
adapter.items = categories
}
private fun updateWidget() {
val intent = Intent(this, ShelfWidgetProvider::class.java)
intent.action = AppWidgetManager.ACTION_APPWIDGET_UPDATE
val ids = intArrayOf(config.widgetId)
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, ids)
sendBroadcast(intent)
}
}

View File

@@ -0,0 +1,35 @@
package org.koitharu.kotatsu.widget.shelf
import androidx.lifecycle.LiveData
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
import org.koitharu.kotatsu.core.ui.BaseViewModel
import org.koitharu.kotatsu.core.util.asFlowLiveData
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
import org.koitharu.kotatsu.widget.shelf.model.CategoryItem
import javax.inject.Inject
@HiltViewModel
class ShelfConfigViewModel @Inject constructor(
favouritesRepository: FavouritesRepository,
) : BaseViewModel() {
private val selectedCategoryId = MutableStateFlow(0L)
val content: LiveData<List<CategoryItem>> = combine(
favouritesRepository.observeCategories(),
selectedCategoryId,
) { categories, selectedId ->
val list = ArrayList<CategoryItem>(categories.size + 1)
list += CategoryItem(0L, null, selectedId == 0L)
categories.mapTo(list) {
CategoryItem(it.id, it.title, selectedId == it.id)
}
list
}.asFlowLiveData(viewModelScope.coroutineContext + Dispatchers.Default, emptyList())
var checkedId: Long by selectedCategoryId::value
}

View File

@@ -0,0 +1,88 @@
package org.koitharu.kotatsu.widget.shelf
import android.content.Context
import android.content.Intent
import android.widget.RemoteViews
import android.widget.RemoteViewsService
import androidx.core.graphics.drawable.toBitmap
import coil.ImageLoader
import coil.executeBlocking
import coil.request.ImageRequest
import coil.size.Size
import coil.transform.RoundedCornersTransformation
import kotlinx.coroutines.runBlocking
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.parser.MangaIntent
import org.koitharu.kotatsu.core.prefs.AppWidgetConfig
import org.koitharu.kotatsu.core.util.ext.getDrawableOrThrow
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.util.replaceWith
class ShelfListFactory(
private val context: Context,
private val favouritesRepository: FavouritesRepository,
private val coil: ImageLoader,
widgetId: Int,
) : RemoteViewsService.RemoteViewsFactory {
private val dataSet = ArrayList<Manga>()
private val config = AppWidgetConfig(context, widgetId)
private val transformation = RoundedCornersTransformation(
context.resources.getDimension(R.dimen.appwidget_corner_radius_inner),
)
private val coverSize = Size(
context.resources.getDimensionPixelSize(R.dimen.widget_cover_width),
context.resources.getDimensionPixelSize(R.dimen.widget_cover_height),
)
override fun onCreate() = Unit
override fun getLoadingView() = null
override fun getItemId(position: Int) = dataSet[position].id
override fun onDataSetChanged() {
val data = runBlocking {
val category = config.categoryId
if (category == 0L) {
favouritesRepository.getAllManga()
} else {
favouritesRepository.getManga(category)
}
}
dataSet.replaceWith(data)
}
override fun hasStableIds() = true
override fun getViewAt(position: Int): RemoteViews {
val views = RemoteViews(context.packageName, R.layout.item_shelf)
val item = dataSet[position]
views.setTextViewText(R.id.textView_title, item.title)
runCatching {
coil.executeBlocking(
ImageRequest.Builder(context)
.data(item.coverUrl)
.size(coverSize)
.tag(item.source)
.transformations(transformation)
.build(),
).getDrawableOrThrow().toBitmap()
}.onSuccess { cover ->
views.setImageViewBitmap(R.id.imageView_cover, cover)
}.onFailure {
views.setImageViewResource(R.id.imageView_cover, R.drawable.ic_placeholder)
}
val intent = Intent()
intent.putExtra(MangaIntent.KEY_ID, item.id)
views.setOnClickFillInIntent(R.id.rootLayout, intent)
return views
}
override fun getCount() = dataSet.size
override fun getViewTypeCount() = 1
override fun onDestroy() = Unit
}

View File

@@ -0,0 +1,44 @@
package org.koitharu.kotatsu.widget.shelf
import android.app.PendingIntent
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.widget.RemoteViews
import androidx.core.app.PendingIntentCompat
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.reader.ui.ReaderActivity
class ShelfWidgetProvider : AppWidgetProvider() {
override fun onUpdate(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetIds: IntArray
) {
appWidgetIds.forEach { id ->
val views = RemoteViews(context.packageName, R.layout.widget_shelf)
val adapter = Intent(context, ShelfWidgetService::class.java)
adapter.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, id)
adapter.data = Uri.parse(adapter.toUri(Intent.URI_INTENT_SCHEME))
views.setRemoteAdapter(R.id.gridView, adapter)
val intent = Intent(context, ReaderActivity::class.java)
intent.action = ReaderActivity.ACTION_MANGA_READ
views.setPendingIntentTemplate(
R.id.gridView,
PendingIntentCompat.getActivity(
context,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT,
true,
),
)
views.setEmptyView(R.id.gridView, R.id.textView_holder)
appWidgetManager.updateAppWidget(id, views)
}
appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetIds, R.id.gridView)
}
}

View File

@@ -0,0 +1,27 @@
package org.koitharu.kotatsu.widget.shelf
import android.appwidget.AppWidgetManager
import android.content.Intent
import android.widget.RemoteViewsService
import coil.ImageLoader
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
@AndroidEntryPoint
class ShelfWidgetService : RemoteViewsService() {
@Inject
lateinit var favouritesRepository: FavouritesRepository
@Inject
lateinit var coil: ImageLoader
override fun onGetViewFactory(intent: Intent): RemoteViewsFactory {
val widgetId = intent.getIntExtra(
AppWidgetManager.EXTRA_APPWIDGET_ID,
AppWidgetManager.INVALID_APPWIDGET_ID,
)
return ShelfListFactory(applicationContext, favouritesRepository, coil, widgetId)
}
}

View File

@@ -0,0 +1,33 @@
package org.koitharu.kotatsu.widget.shelf.adapter
import androidx.recyclerview.widget.DiffUtil
import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.widget.shelf.model.CategoryItem
class CategorySelectAdapter(
clickListener: OnListItemClickListener<CategoryItem>
) : AsyncListDifferDelegationAdapter<CategoryItem>(DiffCallback()) {
init {
delegatesManager.addDelegate(categorySelectItemAD(clickListener))
}
private class DiffCallback : DiffUtil.ItemCallback<CategoryItem>() {
override fun areItemsTheSame(oldItem: CategoryItem, newItem: CategoryItem): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: CategoryItem, newItem: CategoryItem): Boolean {
return oldItem == newItem
}
override fun getChangePayload(oldItem: CategoryItem, newItem: CategoryItem): Any? {
if (oldItem.isSelected != newItem.isSelected) {
return newItem.isSelected
}
return super.getChangePayload(oldItem, newItem)
}
}
}

View File

@@ -0,0 +1,25 @@
package org.koitharu.kotatsu.widget.shelf.adapter
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.databinding.ItemCategoryCheckableSingleBinding
import org.koitharu.kotatsu.widget.shelf.model.CategoryItem
fun categorySelectItemAD(
clickListener: OnListItemClickListener<CategoryItem>
) = adapterDelegateViewBinding<CategoryItem, CategoryItem, ItemCategoryCheckableSingleBinding>(
{ inflater, parent -> ItemCategoryCheckableSingleBinding.inflate(inflater, parent, false) },
) {
itemView.setOnClickListener {
clickListener.onItemClick(item, it)
}
bind {
with(binding.checkedTextView) {
text = item.name ?: getString(R.string.all_favourites)
isChecked = item.isSelected
}
}
}

View File

@@ -0,0 +1,7 @@
package org.koitharu.kotatsu.widget.shelf.model
data class CategoryItem(
val id: Long,
val name: String?,
val isSelected: Boolean
)