Migrate to MVVM

This commit is contained in:
Koitharu
2020-11-17 21:47:22 +02:00
parent eaac271143
commit 7d24286c55
273 changed files with 1926 additions and 2115 deletions

View File

@@ -0,0 +1,31 @@
package org.koitharu.kotatsu.widget
import android.appwidget.AppWidgetManager
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import org.koitharu.kotatsu.favourites.domain.OnFavouritesChangeListener
import org.koitharu.kotatsu.history.domain.OnHistoryChangeListener
import org.koitharu.kotatsu.widget.recent.RecentWidgetProvider
import org.koitharu.kotatsu.widget.shelf.ShelfWidgetProvider
class WidgetUpdater(private val context: Context) : OnFavouritesChangeListener,
OnHistoryChangeListener {
override fun onFavouritesChanged(mangaId: Long) {
updateWidget(ShelfWidgetProvider::class.java)
}
override fun onHistoryChanged() {
updateWidget(RecentWidgetProvider::class.java)
}
private fun updateWidget(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,66 @@
package org.koitharu.kotatsu.widget.recent
import android.content.Context
import android.content.Intent
import android.widget.RemoteViews
import android.widget.RemoteViewsService
import coil.ImageLoader
import coil.executeBlocking
import coil.request.ImageRequest
import kotlinx.coroutines.runBlocking
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.details.ui.DetailsActivity
import org.koitharu.kotatsu.history.domain.HistoryRepository
import org.koitharu.kotatsu.utils.ext.requireBitmap
import java.io.IOException
class RecentListFactory(
private val context: Context,
private val historyRepository: HistoryRepository,
private val coil: ImageLoader
) : RemoteViewsService.RemoteViewsFactory {
private val dataSet = ArrayList<Manga>()
override fun onCreate() {
}
override fun getLoadingView() = null
override fun getItemId(position: Int) = dataSet[position].id
override fun onDataSetChanged() {
dataSet.clear()
val data = runBlocking { historyRepository.getList(0, 10) }
dataSet.addAll(data)
}
override fun hasStableIds() = true
override fun getViewAt(position: Int): RemoteViews {
val views = RemoteViews(context.packageName, R.layout.item_recent)
val item = dataSet[position]
try {
val cover = coil.executeBlocking(
ImageRequest.Builder(context)
.data(item.coverUrl)
.build()
).requireBitmap()
views.setImageViewBitmap(R.id.imageView_cover, cover)
} catch (e: IOException) {
views.setImageViewResource(R.id.imageView_cover, R.drawable.ic_placeholder)
}
val intent = Intent()
intent.putExtra(DetailsActivity.EXTRA_MANGA_ID, item.id)
views.setOnClickFillInIntent(R.id.imageView_cover, intent)
return views
}
override fun getCount() = dataSet.size
override fun getViewTypeCount() = 1
override fun onDestroy() {
}
}

View File

@@ -0,0 +1,41 @@
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 org.koitharu.kotatsu.R
import org.koitharu.kotatsu.details.ui.DetailsActivity
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, DetailsActivity::class.java)
intent.action = DetailsActivity.ACTION_MANGA_VIEW
views.setPendingIntentTemplate(
R.id.stackView, PendingIntent.getActivity(
context,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT
)
)
views.setEmptyView(R.id.stackView, R.id.textView_holder)
appWidgetManager.updateAppWidget(id, views)
}
appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetIds, R.id.stackView)
}
}

View File

@@ -0,0 +1,12 @@
package org.koitharu.kotatsu.widget.recent
import android.content.Intent
import android.widget.RemoteViewsService
import org.koin.android.ext.android.get
class RecentWidgetService : RemoteViewsService() {
override fun onGetViewFactory(intent: Intent): RemoteViewsFactory {
return RecentListFactory(this, get(), get())
}
}

View File

@@ -0,0 +1,35 @@
package org.koitharu.kotatsu.widget.shelf
import android.view.ViewGroup
import org.koitharu.kotatsu.base.ui.list.BaseRecyclerAdapter
import org.koitharu.kotatsu.base.ui.list.OnRecyclerItemClickListener
import org.koitharu.kotatsu.core.model.FavouriteCategory
class CategorySelectAdapter(onItemClickListener: OnRecyclerItemClickListener<FavouriteCategory>? = null) :
BaseRecyclerAdapter<FavouriteCategory, Boolean>(onItemClickListener) {
var checkedItemId = 0L
private set
fun setCheckedId(id: Long) {
val oldId = checkedItemId
checkedItemId = id
val oldPos = findItemPositionById(oldId)
val newPos = findItemPositionById(id)
if (newPos != -1) {
notifyItemChanged(newPos)
}
if (oldPos != -1) {
notifyItemChanged(oldPos)
}
}
override fun getExtra(item: FavouriteCategory, position: Int) =
checkedItemId == item.id
override fun onCreateViewHolder(parent: ViewGroup) = CategorySelectHolder(
parent
)
override fun onGetItemId(item: FavouriteCategory) = item.id
}

View File

@@ -0,0 +1,16 @@
package org.koitharu.kotatsu.widget.shelf
import android.view.ViewGroup
import kotlinx.android.synthetic.main.item_category_checkable.*
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.list.BaseViewHolder
import org.koitharu.kotatsu.core.model.FavouriteCategory
class CategorySelectHolder(parent: ViewGroup) :
BaseViewHolder<FavouriteCategory, Boolean>(parent, R.layout.item_category_checkable_single) {
override fun onBind(data: FavouriteCategory, extra: Boolean) {
checkedTextView.text = data.title
checkedTextView.isChecked = extra
}
}

View File

@@ -0,0 +1,101 @@
package org.koitharu.kotatsu.widget.shelf
import android.app.Activity
import android.appwidget.AppWidgetManager
import android.content.Intent
import android.content.res.ColorStateList
import android.graphics.Color
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import android.view.View
import androidx.core.view.isVisible
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.activity_categories.*
import org.koin.android.viewmodel.ext.android.viewModel
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseActivity
import org.koitharu.kotatsu.base.ui.list.OnRecyclerItemClickListener
import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.core.prefs.AppWidgetConfig
import org.koitharu.kotatsu.favourites.ui.categories.FavouritesCategoriesViewModel
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
import java.util.*
import kotlin.collections.ArrayList
class ShelfConfigActivity : BaseActivity(), OnRecyclerItemClickListener<FavouriteCategory> {
private val viewModel by viewModel<FavouritesCategoriesViewModel>()
private lateinit var adapter: CategorySelectAdapter
private lateinit var config: AppWidgetConfig
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_categories)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
fab_add.imageTintList = ColorStateList.valueOf(Color.WHITE)
adapter = CategorySelectAdapter(this)
recyclerView.addItemDecoration(DividerItemDecoration(this, RecyclerView.VERTICAL))
recyclerView.adapter = adapter
fab_add.isVisible = false
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.getInstance(this, appWidgetId)
adapter.setCheckedId(config.categoryId)
viewModel.categories.observe(this, ::onCategoriesChanged)
viewModel.onError.observe(this, ::onError)
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.opt_config, menu)
return super.onCreateOptionsMenu(menu)
}
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
R.id.action_done -> {
config.categoryId = adapter.checkedItemId
updateWidget()
setResult(
Activity.RESULT_OK,
Intent().putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, config.widgetId)
)
finish()
true
}
else -> super.onOptionsItemSelected(item)
}
override fun onItemClick(item: FavouriteCategory, position: Int, view: View) {
adapter.setCheckedId(item.id)
}
private fun onCategoriesChanged(categories: List<FavouriteCategory>) {
val data = ArrayList<FavouriteCategory>(categories.size + 1)
data += FavouriteCategory(0L, getString(R.string.all_favourites), -1, Date())
data += categories
adapter.replaceData(data)
}
private fun onError(e: Throwable) {
Snackbar.make(recyclerView, e.getDisplayMessage(resources), Snackbar.LENGTH_LONG)
.show()
}
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,76 @@
package org.koitharu.kotatsu.widget.shelf
import android.content.Context
import android.content.Intent
import android.widget.RemoteViews
import android.widget.RemoteViewsService
import coil.ImageLoader
import coil.executeBlocking
import coil.request.ImageRequest
import kotlinx.coroutines.runBlocking
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.core.prefs.AppWidgetConfig
import org.koitharu.kotatsu.details.ui.DetailsActivity
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
import org.koitharu.kotatsu.utils.ext.requireBitmap
import java.io.IOException
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.getInstance(context, widgetId)
override fun onCreate() {
}
override fun getLoadingView() = null
override fun getItemId(position: Int) = dataSet[position].id
override fun onDataSetChanged() {
dataSet.clear()
val data = runBlocking {
val category = config.categoryId
if (category == 0L) {
favouritesRepository.getAllManga()
} else {
favouritesRepository.getManga(category)
}
}
dataSet.addAll(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)
try {
val cover = coil.executeBlocking(
ImageRequest.Builder(context)
.data(item.coverUrl)
.build()
).requireBitmap()
views.setImageViewBitmap(R.id.imageView_cover, cover)
} catch (e: IOException) {
views.setImageViewResource(R.id.imageView_cover, R.drawable.ic_placeholder)
}
val intent = Intent()
intent.putExtra(DetailsActivity.EXTRA_MANGA_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,41 @@
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 org.koitharu.kotatsu.R
import org.koitharu.kotatsu.details.ui.DetailsActivity
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, DetailsActivity::class.java)
intent.action = DetailsActivity.ACTION_MANGA_VIEW
views.setPendingIntentTemplate(
R.id.gridView, PendingIntent.getActivity(
context,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT
)
)
views.setEmptyView(R.id.gridView, R.id.textView_holder)
appWidgetManager.updateAppWidget(id, views)
}
appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetIds, R.id.gridView)
}
}

View File

@@ -0,0 +1,17 @@
package org.koitharu.kotatsu.widget.shelf
import android.appwidget.AppWidgetManager
import android.content.Intent
import android.widget.RemoteViewsService
import org.koin.android.ext.android.get
class ShelfWidgetService : RemoteViewsService() {
override fun onGetViewFactory(intent: Intent): RemoteViewsFactory {
val widgetId = intent.getIntExtra(
AppWidgetManager.EXTRA_APPWIDGET_ID,
AppWidgetManager.INVALID_APPWIDGET_ID
)
return ShelfListFactory(this, get(), get(), widgetId)
}
}