Configure shelf appwidget
This commit is contained in:
@@ -60,8 +60,15 @@
|
||||
android:windowSoftInputMode="stateAlwaysHidden" />
|
||||
<activity
|
||||
android:name=".ui.main.list.favourites.categories.CategoriesActivity"
|
||||
android:windowSoftInputMode="stateAlwaysHidden"
|
||||
android:label="@string/favourites_categories" />
|
||||
android:label="@string/favourites_categories"
|
||||
android:windowSoftInputMode="stateAlwaysHidden" />
|
||||
<activity
|
||||
android:name=".ui.widget.shelf.ShelfConfigActivity"
|
||||
android:label="@string/manga_shelf">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<service
|
||||
android:name=".ui.download.DownloadService"
|
||||
@@ -87,21 +94,25 @@
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/filepaths" />
|
||||
</provider>
|
||||
|
||||
<receiver android:name=".ui.widget.shelf.ShelfWidgetProvider"
|
||||
|
||||
<receiver
|
||||
android:name=".ui.widget.shelf.ShelfWidgetProvider"
|
||||
android:label="@string/manga_shelf">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
</intent-filter>
|
||||
<meta-data android:name="android.appwidget.provider"
|
||||
<meta-data
|
||||
android:name="android.appwidget.provider"
|
||||
android:resource="@xml/widget_shelf" />
|
||||
</receiver>
|
||||
<receiver android:name=".ui.widget.recent.RecentWidgetProvider"
|
||||
<receiver
|
||||
android:name=".ui.widget.recent.RecentWidgetProvider"
|
||||
android:label="@string/recent_manga">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
</intent-filter>
|
||||
<meta-data android:name="android.appwidget.provider"
|
||||
<meta-data
|
||||
android:name="android.appwidget.provider"
|
||||
android:resource="@xml/widget_recent" />
|
||||
</receiver>
|
||||
|
||||
|
||||
@@ -9,8 +9,12 @@ import org.koitharu.kotatsu.core.db.entity.MangaEntity
|
||||
abstract class FavouritesDao {
|
||||
|
||||
@Transaction
|
||||
@Query("SELECT * FROM favourites GROUP BY manga_id ORDER BY :orderBy LIMIT :limit OFFSET :offset")
|
||||
abstract suspend fun findAll(offset: Int, limit: Int, orderBy: String): List<FavouriteManga>
|
||||
@Query("SELECT * FROM favourites GROUP BY manga_id ORDER BY created_at LIMIT :limit OFFSET :offset")
|
||||
abstract suspend fun findAll(offset: Int, limit: Int): List<FavouriteManga>
|
||||
|
||||
@Transaction
|
||||
@Query("SELECT * FROM favourites WHERE category_id = :categoryId GROUP BY manga_id ORDER BY created_at LIMIT :limit OFFSET :offset")
|
||||
abstract suspend fun findAll(categoryId: Long, offset: Int, limit: Int): List<FavouriteManga>
|
||||
|
||||
@Query("SELECT * FROM manga WHERE manga_id IN (SELECT manga_id FROM favourites)")
|
||||
abstract suspend fun findAllManga(): List<MangaEntity>
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package org.koitharu.kotatsu.core.prefs
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import org.koitharu.kotatsu.utils.delegates.prefs.LongPreferenceDelegate
|
||||
|
||||
class AppWidgetConfig private constructor(
|
||||
private val prefs: SharedPreferences,
|
||||
val widgetId: Int
|
||||
) : SharedPreferences by prefs {
|
||||
|
||||
var categoryId by LongPreferenceDelegate(CATEGORY_ID, 0L)
|
||||
|
||||
companion object {
|
||||
|
||||
private const val CATEGORY_ID = "cat_id"
|
||||
|
||||
fun getInstance(context: Context, widgetId: Int) = AppWidgetConfig(
|
||||
context.getSharedPreferences(
|
||||
"appwidget_$widgetId",
|
||||
Context.MODE_PRIVATE
|
||||
), widgetId
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -19,7 +19,12 @@ class FavouritesRepository : KoinComponent {
|
||||
private val db: MangaDatabase by inject()
|
||||
|
||||
suspend fun getAllManga(offset: Int): List<Manga> {
|
||||
val entities = db.favouritesDao.findAll(offset, 20, "created_at")
|
||||
val entities = db.favouritesDao.findAll(offset, 20)
|
||||
return entities.map { it.manga.toManga(it.tags.map(TagEntity::toMangaTag).toSet()) }
|
||||
}
|
||||
|
||||
suspend fun getManga(categoryId: Long, offset: Int): List<Manga> {
|
||||
val entities = db.favouritesDao.findAll(categoryId, offset, 20)
|
||||
return entities.map { it.manga.toManga(it.tags.map(TagEntity::toMangaTag).toSet()) }
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package org.koitharu.kotatsu.ui.widget.shelf
|
||||
|
||||
import android.view.ViewGroup
|
||||
import org.koitharu.kotatsu.core.model.FavouriteCategory
|
||||
import org.koitharu.kotatsu.ui.common.list.BaseRecyclerAdapter
|
||||
import org.koitharu.kotatsu.ui.common.list.OnRecyclerItemClickListener
|
||||
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.koitharu.kotatsu.ui.widget.shelf
|
||||
|
||||
import android.view.ViewGroup
|
||||
import kotlinx.android.synthetic.main.item_category_checkable.*
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.model.FavouriteCategory
|
||||
import org.koitharu.kotatsu.ui.common.list.BaseViewHolder
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package org.koitharu.kotatsu.ui.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 moxy.ktx.moxyPresenter
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.model.FavouriteCategory
|
||||
import org.koitharu.kotatsu.core.prefs.AppWidgetConfig
|
||||
import org.koitharu.kotatsu.ui.common.BaseActivity
|
||||
import org.koitharu.kotatsu.ui.common.list.OnRecyclerItemClickListener
|
||||
import org.koitharu.kotatsu.ui.main.list.favourites.categories.FavouriteCategoriesPresenter
|
||||
import org.koitharu.kotatsu.ui.main.list.favourites.categories.FavouriteCategoriesView
|
||||
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
class ShelfConfigActivity : BaseActivity(), FavouriteCategoriesView,
|
||||
OnRecyclerItemClickListener<FavouriteCategory> {
|
||||
|
||||
private val presenter by moxyPresenter(factory = ::FavouriteCategoriesPresenter)
|
||||
|
||||
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) {
|
||||
finish()
|
||||
return
|
||||
}
|
||||
config = AppWidgetConfig.getInstance(this, appWidgetId)
|
||||
adapter.setCheckedId(config.categoryId)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
override fun onCategoriesChanged(categories: List<FavouriteCategory>) {
|
||||
val data = ArrayList<FavouriteCategory>(categories.size + 1)
|
||||
data += FavouriteCategory(0L, getString(R.string.favourites), Date())
|
||||
data += categories
|
||||
adapter.replaceData(data)
|
||||
}
|
||||
|
||||
override fun onCheckedCategoriesChanged(checkedIds: Set<Int>) = Unit
|
||||
|
||||
override 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)
|
||||
}
|
||||
}
|
||||
@@ -9,14 +9,16 @@ import coil.request.GetRequestBuilder
|
||||
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.domain.favourites.FavouritesRepository
|
||||
import org.koitharu.kotatsu.ui.details.MangaDetailsActivity
|
||||
import org.koitharu.kotatsu.utils.ext.requireBitmap
|
||||
import java.io.IOException
|
||||
|
||||
class ShelfListFactory(private val context: Context) : RemoteViewsService.RemoteViewsFactory {
|
||||
class ShelfListFactory(private val context: Context, widgetId: Int) : RemoteViewsService.RemoteViewsFactory {
|
||||
|
||||
private val dataSet = ArrayList<Manga>()
|
||||
private val config = AppWidgetConfig.getInstance(context, widgetId)
|
||||
|
||||
override fun onCreate() {
|
||||
}
|
||||
@@ -27,7 +29,9 @@ class ShelfListFactory(private val context: Context) : RemoteViewsService.Remote
|
||||
|
||||
override fun onDataSetChanged() {
|
||||
dataSet.clear()
|
||||
val data = runBlocking { FavouritesRepository().getAllManga(0) }
|
||||
val data = runBlocking {
|
||||
FavouritesRepository().getManga(config.categoryId, 0)
|
||||
}
|
||||
dataSet.addAll(data)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
package org.koitharu.kotatsu.ui.widget.shelf
|
||||
|
||||
import android.appwidget.AppWidgetManager
|
||||
import android.content.Intent
|
||||
import android.widget.RemoteViewsService
|
||||
|
||||
class ShelfWidgetService : RemoteViewsService() {
|
||||
|
||||
override fun onGetViewFactory(intent: Intent): RemoteViewsFactory {
|
||||
return ShelfListFactory(this)
|
||||
val widgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
|
||||
AppWidgetManager.INVALID_APPWIDGET_ID)
|
||||
return ShelfListFactory(this, widgetId)
|
||||
}
|
||||
}
|
||||
11
app/src/main/res/drawable/ic_done.xml
Normal file
11
app/src/main/res/drawable/ic_done.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<vector
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="?colorControlNormal"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M9,16.2L4.8,12l-1.4,1.4L9,19 21,7l-1.4,-1.4L9,16.2z" />
|
||||
</vector>
|
||||
16
app/src/main/res/layout/item_category_checkable_single.xml
Normal file
16
app/src/main/res/layout/item_category_checkable_single.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<CheckedTextView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/checkedTextView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?android:listPreferredItemHeightSmall"
|
||||
android:background="?android:selectableItemBackground"
|
||||
android:checkMark="?android:attr/listChoiceIndicatorSingle"
|
||||
android:gravity="start|center_vertical"
|
||||
android:paddingStart="?android:listPreferredItemPaddingStart"
|
||||
android:paddingEnd="?android:listPreferredItemPaddingEnd"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
|
||||
android:textColor="?android:textColorPrimary"
|
||||
tools:checked="true"
|
||||
tools:text="@tools:sample/lorem[4]" />
|
||||
@@ -28,7 +28,9 @@
|
||||
android:ellipsize="end"
|
||||
android:gravity="center"
|
||||
android:lines="2"
|
||||
android:textColor="?android:textColorPrimary" />
|
||||
android:shadowColor="@android:color/black"
|
||||
android:shadowRadius="1"
|
||||
android:textColor="@android:color/white" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
@@ -21,7 +21,9 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:shadowColor="@android:color/black"
|
||||
android:shadowRadius="1"
|
||||
android:text="@string/you_have_not_favourites_yet"
|
||||
android:textColor="?android:textColorPrimary" />
|
||||
android:textColor="@android:color/white" />
|
||||
|
||||
</FrameLayout>
|
||||
12
app/src/main/res/menu/opt_config.xml
Normal file
12
app/src/main/res/menu/opt_config.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
android:id="@+id/action_done"
|
||||
android:icon="@drawable/ic_done"
|
||||
android:orderInCategory="0"
|
||||
android:title="@string/done"
|
||||
app:showAsAction="ifRoom|withText" />
|
||||
</menu>
|
||||
@@ -131,4 +131,5 @@
|
||||
<string name="cannot_find_available_storage">Не удалось найти ни одного доступного хранилища</string>
|
||||
<string name="other_storage">Другое хранилище</string>
|
||||
<string name="use_ssl">Защищённое соединение (HTTPS)</string>
|
||||
<string name="done">Готово</string>
|
||||
</resources>
|
||||
@@ -132,4 +132,5 @@
|
||||
<string name="cannot_find_available_storage">Cannot find any available storage</string>
|
||||
<string name="other_storage">Other storage</string>
|
||||
<string name="use_ssl">Use secure connection (HTTPS)</string>
|
||||
<string name="done">Done</string>
|
||||
</resources>
|
||||
@@ -1,6 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<appwidget-provider
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:configure="org.koitharu.kotatsu.ui.widget.shelf.ShelfConfigActivity"
|
||||
android:initialLayout="@layout/widget_shelf"
|
||||
android:minWidth="110dp"
|
||||
android:minHeight="110dp"
|
||||
|
||||
Reference in New Issue
Block a user