Implement feed groups manual sorting
Now, the user can sort its groups to his liking even after he created them. Also updated the database diagram to reflect the table's new column.
This commit is contained in:
parent
50714c3006
commit
d1d5f6821f
20 changed files with 473 additions and 19 deletions
|
@ -2,7 +2,7 @@
|
|||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 3,
|
||||
"identityHash": "83d5d68663102d5fa28d63caaffb396d",
|
||||
"identityHash": "9f825b1ee281480bedd38b971feac327",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "subscriptions",
|
||||
|
@ -555,7 +555,7 @@
|
|||
},
|
||||
{
|
||||
"tableName": "feed_group",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `icon_id` INTEGER NOT NULL)",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `icon_id` INTEGER NOT NULL, `sort_order` INTEGER NOT NULL)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "uid",
|
||||
|
@ -574,6 +574,12 @@
|
|||
"columnName": "icon_id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "sortOrder",
|
||||
"columnName": "sort_order",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
|
@ -582,7 +588,16 @@
|
|||
],
|
||||
"autoGenerate": true
|
||||
},
|
||||
"indices": [],
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_feed_group_sort_order",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"sort_order"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_feed_group_sort_order` ON `${TABLE_NAME}` (`sort_order`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
|
@ -686,7 +701,7 @@
|
|||
"views": [],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '83d5d68663102d5fa28d63caaffb396d')"
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '9f825b1ee281480bedd38b971feac327')"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -91,7 +91,8 @@ public class Migrations {
|
|||
// Tables for feed feature
|
||||
database.execSQL("CREATE TABLE IF NOT EXISTS feed (stream_id INTEGER NOT NULL, subscription_id INTEGER NOT NULL, PRIMARY KEY(stream_id, subscription_id), FOREIGN KEY(stream_id) REFERENCES streams(uid) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY(subscription_id) REFERENCES subscriptions(uid) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)");
|
||||
database.execSQL("CREATE INDEX index_feed_subscription_id ON feed (subscription_id)");
|
||||
database.execSQL("CREATE TABLE IF NOT EXISTS feed_group (uid INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name TEXT NOT NULL, icon_id INTEGER NOT NULL)");
|
||||
database.execSQL("CREATE TABLE IF NOT EXISTS feed_group (uid INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name TEXT NOT NULL, icon_id INTEGER NOT NULL, sort_order INTEGER NOT NULL)");
|
||||
database.execSQL("CREATE INDEX index_feed_group_sort_order ON feed_group (sort_order)");
|
||||
database.execSQL("CREATE TABLE IF NOT EXISTS feed_group_subscription_join (group_id INTEGER NOT NULL, subscription_id INTEGER NOT NULL, PRIMARY KEY(group_id, subscription_id), FOREIGN KEY(group_id) REFERENCES feed_group(uid) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY(subscription_id) REFERENCES subscriptions(uid) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)");
|
||||
database.execSQL("CREATE INDEX index_feed_group_subscription_join_subscription_id ON feed_group_subscription_join (subscription_id)");
|
||||
database.execSQL("CREATE TABLE IF NOT EXISTS feed_last_updated (subscription_id INTEGER NOT NULL, last_updated INTEGER, PRIMARY KEY(subscription_id), FOREIGN KEY(subscription_id) REFERENCES subscriptions(uid) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)");
|
||||
|
|
|
@ -9,14 +9,18 @@ import org.schabi.newpipe.database.feed.model.FeedGroupSubscriptionEntity
|
|||
@Dao
|
||||
abstract class FeedGroupDAO {
|
||||
|
||||
@Query("SELECT * FROM feed_group")
|
||||
@Query("SELECT * FROM feed_group ORDER BY sort_order ASC")
|
||||
abstract fun getAll(): Flowable<List<FeedGroupEntity>>
|
||||
|
||||
@Query("SELECT * FROM feed_group WHERE uid = :groupId")
|
||||
abstract fun getGroup(groupId: Long): Maybe<FeedGroupEntity>
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.ABORT)
|
||||
abstract fun insert(feedEntity: FeedGroupEntity): Long
|
||||
@Transaction
|
||||
open fun insert(feedGroupEntity: FeedGroupEntity): Long {
|
||||
val nextSortOrder = nextSortOrder()
|
||||
feedGroupEntity.sortOrder = nextSortOrder
|
||||
return insertInternal(feedGroupEntity)
|
||||
}
|
||||
|
||||
@Update(onConflict = OnConflictStrategy.IGNORE)
|
||||
abstract fun update(feedGroupEntity: FeedGroupEntity): Int
|
||||
|
@ -41,4 +45,18 @@ abstract class FeedGroupDAO {
|
|||
deleteSubscriptionsFromGroup(groupId)
|
||||
insertSubscriptionsToGroup(subscriptionIds.map { FeedGroupSubscriptionEntity(groupId, it) })
|
||||
}
|
||||
|
||||
@Transaction
|
||||
open fun updateOrder(orderMap: Map<Long, Long>) {
|
||||
orderMap.forEach { (groupId, sortOrder) -> updateOrder(groupId, sortOrder) }
|
||||
}
|
||||
|
||||
@Query("UPDATE feed_group SET sort_order = :sortOrder WHERE uid = :groupId")
|
||||
abstract fun updateOrder(groupId: Long, sortOrder: Long): Int
|
||||
|
||||
@Query("SELECT IFNULL(MAX(sort_order) + 1, 0) FROM feed_group")
|
||||
protected abstract fun nextSortOrder(): Long
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.ABORT)
|
||||
protected abstract fun insertInternal(feedGroupEntity: FeedGroupEntity): Long
|
||||
}
|
||||
|
|
|
@ -2,11 +2,16 @@ package org.schabi.newpipe.database.feed.model
|
|||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.Index
|
||||
import androidx.room.PrimaryKey
|
||||
import org.schabi.newpipe.database.feed.model.FeedGroupEntity.Companion.FEED_GROUP_TABLE
|
||||
import org.schabi.newpipe.database.feed.model.FeedGroupEntity.Companion.SORT_ORDER
|
||||
import org.schabi.newpipe.local.subscription.FeedGroupIcon
|
||||
|
||||
@Entity(tableName = FEED_GROUP_TABLE)
|
||||
@Entity(
|
||||
tableName = FEED_GROUP_TABLE,
|
||||
indices = [Index(SORT_ORDER)]
|
||||
)
|
||||
data class FeedGroupEntity(
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
@ColumnInfo(name = ID)
|
||||
|
@ -16,7 +21,10 @@ data class FeedGroupEntity(
|
|||
var name: String,
|
||||
|
||||
@ColumnInfo(name = ICON)
|
||||
var icon: FeedGroupIcon
|
||||
var icon: FeedGroupIcon,
|
||||
|
||||
@ColumnInfo(name = SORT_ORDER)
|
||||
var sortOrder: Long = -1
|
||||
) {
|
||||
companion object {
|
||||
const val FEED_GROUP_TABLE = "feed_group"
|
||||
|
@ -24,6 +32,7 @@ data class FeedGroupEntity(
|
|||
const val ID = "uid"
|
||||
const val NAME = "name"
|
||||
const val ICON = "icon_id"
|
||||
const val SORT_ORDER = "sort_order"
|
||||
|
||||
const val GROUP_ALL_ID = -1L
|
||||
}
|
||||
|
|
|
@ -147,6 +147,15 @@ class FeedDatabaseManager(context: Context) {
|
|||
.observeOn(AndroidSchedulers.mainThread())
|
||||
}
|
||||
|
||||
fun updateGroupsOrder(groupIdList: List<Long>): Completable {
|
||||
var index = 0L
|
||||
val orderMap = groupIdList.associateBy({ it }, { index++ })
|
||||
|
||||
return Completable.fromCallable { feedGroupTable.updateOrder(orderMap) }
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
}
|
||||
|
||||
fun oldestSubscriptionUpdate(groupId: Long): Flowable<List<Date>> {
|
||||
return when (groupId) {
|
||||
FeedGroupEntity.GROUP_ALL_ID -> feedTable.oldestSubscriptionUpdateFromAll()
|
||||
|
|
|
@ -25,8 +25,9 @@ import org.schabi.newpipe.R
|
|||
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
|
||||
import org.schabi.newpipe.extractor.channel.ChannelInfoItem
|
||||
import org.schabi.newpipe.fragments.BaseStateFragment
|
||||
import org.schabi.newpipe.local.subscription.SubscriptionViewModel.*
|
||||
import org.schabi.newpipe.local.subscription.SubscriptionViewModel.SubscriptionState
|
||||
import org.schabi.newpipe.local.subscription.dialog.FeedGroupDialog
|
||||
import org.schabi.newpipe.local.subscription.dialog.FeedGroupReorderDialog
|
||||
import org.schabi.newpipe.local.subscription.item.*
|
||||
import org.schabi.newpipe.local.subscription.services.SubscriptionsExportService
|
||||
import org.schabi.newpipe.local.subscription.services.SubscriptionsExportService.EXPORT_COMPLETE_ACTION
|
||||
|
@ -34,11 +35,8 @@ import org.schabi.newpipe.local.subscription.services.SubscriptionsExportService
|
|||
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService
|
||||
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.*
|
||||
import org.schabi.newpipe.report.UserAction
|
||||
import org.schabi.newpipe.util.*
|
||||
import org.schabi.newpipe.util.AnimationUtils.animateView
|
||||
import org.schabi.newpipe.util.FilePickerActivityHelper
|
||||
import org.schabi.newpipe.util.NavigationHelper
|
||||
import org.schabi.newpipe.util.OnClickGesture
|
||||
import org.schabi.newpipe.util.ShareUtils
|
||||
import java.io.File
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
@ -54,6 +52,7 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
|
|||
private val feedGroupsSection = Section()
|
||||
private var feedGroupsCarousel: FeedGroupCarouselItem? = null
|
||||
private lateinit var importExportItem: FeedImportExportItem
|
||||
private lateinit var feedGroupsSortMenuItem: HeaderWithMenuItem
|
||||
private val subscriptionsSection = Section()
|
||||
|
||||
@State @JvmField var itemsListState: Parcelable? = null
|
||||
|
@ -164,6 +163,10 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
|
|||
startActivityForResult(FilePickerActivityHelper.chooseFileToSave(activity, exportFile.absolutePath), REQUEST_EXPORT_CODE)
|
||||
}
|
||||
|
||||
private fun openReorderDialog() {
|
||||
FeedGroupReorderDialog().show(requireFragmentManager(), null)
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
if (data != null && data.data != null && resultCode == Activity.RESULT_OK) {
|
||||
|
@ -210,7 +213,12 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
|
|||
}
|
||||
|
||||
feedGroupsCarousel = FeedGroupCarouselItem(requireContext(), carouselAdapter)
|
||||
add(Section(HeaderItem(getString(R.string.feed_groups_header_title)), listOf(feedGroupsCarousel)))
|
||||
feedGroupsSortMenuItem = HeaderWithMenuItem(
|
||||
getString(R.string.feed_groups_header_title),
|
||||
ThemeHelper.resolveResourceIdFromAttr(requireContext(), R.attr.ic_sort),
|
||||
menuItemOnClickListener = ::openReorderDialog
|
||||
)
|
||||
add(Section(feedGroupsSortMenuItem, listOf(feedGroupsCarousel)))
|
||||
|
||||
groupAdapter.add(this)
|
||||
}
|
||||
|
@ -333,6 +341,12 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
|
|||
feedGroupsCarousel?.onRestoreInstanceState(feedGroupsListState)
|
||||
feedGroupsListState = null
|
||||
}
|
||||
|
||||
if (groups.size < 2) {
|
||||
items_list.post { feedGroupsSortMenuItem.notifyChanged(HeaderWithMenuItem.PAYLOAD_HIDE_MENU_ITEM) }
|
||||
} else {
|
||||
items_list.post { feedGroupsSortMenuItem.notifyChanged(HeaderWithMenuItem.PAYLOAD_SHOW_MENU_ITEM) }
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -39,6 +39,7 @@ class FeedGroupDialog : DialogFragment() {
|
|||
private lateinit var viewModel: FeedGroupDialogViewModel
|
||||
private var groupId: Long = NO_GROUP_SELECTED
|
||||
private var groupIcon: FeedGroupIcon? = null
|
||||
private var groupSortOrder: Long = -1
|
||||
|
||||
sealed class ScreenState : Serializable {
|
||||
object InitialScreen : ScreenState()
|
||||
|
@ -145,7 +146,7 @@ class FeedGroupDialog : DialogFragment() {
|
|||
|
||||
when (groupId) {
|
||||
NO_GROUP_SELECTED -> viewModel.createGroup(name, icon, selectedSubscriptions)
|
||||
else -> viewModel.updateGroup(name, icon, selectedSubscriptions)
|
||||
else -> viewModel.updateGroup(name, icon, selectedSubscriptions, groupSortOrder)
|
||||
}
|
||||
} else {
|
||||
showInitialScreen()
|
||||
|
@ -167,6 +168,7 @@ class FeedGroupDialog : DialogFragment() {
|
|||
val icon = feedGroupEntity?.icon ?: FeedGroupIcon.ALL
|
||||
val name = feedGroupEntity?.name ?: ""
|
||||
groupIcon = feedGroupEntity?.icon
|
||||
groupSortOrder = feedGroupEntity?.sortOrder ?: -1
|
||||
|
||||
icon_preview.setImageResource((if (selectedIcon == null) icon else selectedIcon!!).getDrawableRes(requireContext()))
|
||||
|
||||
|
|
|
@ -58,9 +58,9 @@ class FeedGroupDialogViewModel(applicationContext: Context, val groupId: Long =
|
|||
.subscribe { successLiveData.postValue(FeedDialogEvent.SuccessEvent) })
|
||||
}
|
||||
|
||||
fun updateGroup(name: String, selectedIcon: FeedGroupIcon, selectedSubscriptions: Set<Long>) {
|
||||
fun updateGroup(name: String, selectedIcon: FeedGroupIcon, selectedSubscriptions: Set<Long>, sortOrder: Long) {
|
||||
disposables.add(feedDatabaseManager.updateSubscriptionsForGroup(groupId, selectedSubscriptions.toList())
|
||||
.andThen(feedDatabaseManager.updateGroup(FeedGroupEntity(groupId, name, selectedIcon)))
|
||||
.andThen(feedDatabaseManager.updateGroup(FeedGroupEntity(groupId, name, selectedIcon, sortOrder)))
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe { successLiveData.postValue(FeedDialogEvent.SuccessEvent) })
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
package org.schabi.newpipe.local.subscription.dialog
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.ItemTouchHelper.SimpleCallback
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.xwray.groupie.GroupAdapter
|
||||
import com.xwray.groupie.TouchCallback
|
||||
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
|
||||
import icepick.Icepick
|
||||
import icepick.State
|
||||
import kotlinx.android.synthetic.main.dialog_feed_group_reorder.*
|
||||
import org.schabi.newpipe.R
|
||||
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
|
||||
import org.schabi.newpipe.local.subscription.dialog.FeedGroupReorderDialogViewModel.DialogEvent.SuccessEvent
|
||||
import org.schabi.newpipe.local.subscription.item.FeedGroupReorderItem
|
||||
import org.schabi.newpipe.util.ThemeHelper
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
class FeedGroupReorderDialog : DialogFragment() {
|
||||
private lateinit var viewModel: FeedGroupReorderDialogViewModel
|
||||
|
||||
@State @JvmField var groupOrderedIdList = ArrayList<Long>()
|
||||
private val groupAdapter = GroupAdapter<GroupieViewHolder>()
|
||||
private val itemTouchHelper = ItemTouchHelper(getItemTouchCallback())
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
Icepick.restoreInstanceState(this, savedInstanceState)
|
||||
|
||||
setStyle(STYLE_NO_TITLE, ThemeHelper.getMinWidthDialogTheme(requireContext()))
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
return inflater.inflate(R.layout.dialog_feed_group_reorder, container)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
viewModel = ViewModelProviders.of(this).get(FeedGroupReorderDialogViewModel::class.java)
|
||||
viewModel.groupsLiveData.observe(viewLifecycleOwner, Observer(::handleGroups))
|
||||
viewModel.dialogEventLiveData.observe(viewLifecycleOwner, Observer {
|
||||
when (it) {
|
||||
is SuccessEvent -> dismiss()
|
||||
}
|
||||
})
|
||||
|
||||
feed_groups_list.layoutManager = LinearLayoutManager(requireContext())
|
||||
feed_groups_list.adapter = groupAdapter
|
||||
itemTouchHelper.attachToRecyclerView(feed_groups_list)
|
||||
|
||||
confirm_button.setOnClickListener {
|
||||
viewModel.updateOrder(groupOrderedIdList)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
super.onSaveInstanceState(outState)
|
||||
Icepick.saveInstanceState(this, outState)
|
||||
}
|
||||
|
||||
private fun handleGroups(list: List<FeedGroupEntity>) {
|
||||
val groupList: List<FeedGroupEntity>
|
||||
|
||||
if (groupOrderedIdList.isEmpty()) {
|
||||
groupList = list
|
||||
groupOrderedIdList.addAll(groupList.map { it.uid })
|
||||
} else {
|
||||
groupList = list.sortedBy { groupOrderedIdList.indexOf(it.uid) }
|
||||
}
|
||||
|
||||
groupAdapter.update(groupList.map { FeedGroupReorderItem(it, itemTouchHelper) })
|
||||
}
|
||||
|
||||
private fun getItemTouchCallback(): SimpleCallback {
|
||||
return object : TouchCallback() {
|
||||
|
||||
override fun onMove(recyclerView: RecyclerView, source: RecyclerView.ViewHolder,
|
||||
target: RecyclerView.ViewHolder): Boolean {
|
||||
val sourceIndex = source.adapterPosition
|
||||
val targetIndex = target.adapterPosition
|
||||
|
||||
groupAdapter.notifyItemMoved(sourceIndex, targetIndex)
|
||||
Collections.swap(groupOrderedIdList, sourceIndex, targetIndex)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun isLongPressDragEnabled(): Boolean = false
|
||||
override fun isItemViewSwipeEnabled(): Boolean = false
|
||||
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, swipeDir: Int) {}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package org.schabi.newpipe.local.subscription.dialog
|
||||
|
||||
import android.app.Application
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.disposables.CompositeDisposable
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
|
||||
import org.schabi.newpipe.local.feed.FeedDatabaseManager
|
||||
|
||||
class FeedGroupReorderDialogViewModel(application: Application) : AndroidViewModel(application) {
|
||||
private var feedDatabaseManager: FeedDatabaseManager = FeedDatabaseManager(application)
|
||||
|
||||
val groupsLiveData = MutableLiveData<List<FeedGroupEntity>>()
|
||||
val dialogEventLiveData = MutableLiveData<DialogEvent>()
|
||||
|
||||
private val disposables = CompositeDisposable()
|
||||
|
||||
private var groupsDisposable = feedDatabaseManager.groups()
|
||||
.limit(1)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe(groupsLiveData::postValue)
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
groupsDisposable.dispose()
|
||||
disposables.dispose()
|
||||
}
|
||||
|
||||
fun updateOrder(groupIdList: List<Long>) {
|
||||
disposables.add(feedDatabaseManager.updateGroupsOrder(groupIdList)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe { dialogEventLiveData.postValue(DialogEvent.SuccessEvent) })
|
||||
}
|
||||
|
||||
sealed class DialogEvent {
|
||||
object SuccessEvent : DialogEvent()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
package org.schabi.newpipe.local.subscription.item
|
||||
|
||||
import android.view.MotionEvent
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.ItemTouchHelper.DOWN
|
||||
import androidx.recyclerview.widget.ItemTouchHelper.UP
|
||||
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
|
||||
import com.xwray.groupie.kotlinandroidextensions.Item
|
||||
import kotlinx.android.synthetic.main.feed_group_reorder_item.*
|
||||
import org.schabi.newpipe.R
|
||||
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
|
||||
import org.schabi.newpipe.local.subscription.FeedGroupIcon
|
||||
|
||||
data class FeedGroupReorderItem(
|
||||
val groupId: Long = FeedGroupEntity.GROUP_ALL_ID,
|
||||
val name: String,
|
||||
val icon: FeedGroupIcon,
|
||||
val dragCallback: ItemTouchHelper
|
||||
) : Item() {
|
||||
constructor (feedGroupEntity: FeedGroupEntity, dragCallback: ItemTouchHelper)
|
||||
: this(feedGroupEntity.uid, feedGroupEntity.name, feedGroupEntity.icon, dragCallback)
|
||||
|
||||
override fun getId(): Long {
|
||||
return when (groupId) {
|
||||
FeedGroupEntity.GROUP_ALL_ID -> super.getId()
|
||||
else -> groupId
|
||||
}
|
||||
}
|
||||
|
||||
override fun getLayout(): Int = R.layout.feed_group_reorder_item
|
||||
|
||||
override fun bind(viewHolder: GroupieViewHolder, position: Int) {
|
||||
viewHolder.group_name.text = name
|
||||
viewHolder.group_icon.setImageResource(icon.getDrawableRes(viewHolder.containerView.context))
|
||||
viewHolder.handle.setOnTouchListener { _, event ->
|
||||
if (event.actionMasked == MotionEvent.ACTION_DOWN) {
|
||||
dragCallback.startDrag(viewHolder)
|
||||
return@setOnTouchListener true
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
override fun getDragDirs(): Int {
|
||||
return UP or DOWN
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
package org.schabi.newpipe.local.subscription.item
|
||||
|
||||
import android.view.View.*
|
||||
import androidx.annotation.DrawableRes
|
||||
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
|
||||
import com.xwray.groupie.kotlinandroidextensions.Item
|
||||
import kotlinx.android.synthetic.main.header_with_menu_item.*
|
||||
import org.schabi.newpipe.R
|
||||
|
||||
class HeaderWithMenuItem(
|
||||
val title: String,
|
||||
@DrawableRes val itemIcon: Int = 0,
|
||||
private val onClickListener: (() -> Unit)? = null,
|
||||
private val menuItemOnClickListener: (() -> Unit)? = null
|
||||
) : Item() {
|
||||
companion object {
|
||||
const val PAYLOAD_SHOW_MENU_ITEM = 1
|
||||
const val PAYLOAD_HIDE_MENU_ITEM = 2
|
||||
}
|
||||
|
||||
override fun getLayout(): Int = R.layout.header_with_menu_item
|
||||
|
||||
|
||||
override fun bind(viewHolder: GroupieViewHolder, position: Int, payloads: MutableList<Any>) {
|
||||
if (payloads.contains(PAYLOAD_SHOW_MENU_ITEM)) {
|
||||
viewHolder.header_menu_item.visibility = VISIBLE
|
||||
return
|
||||
} else if (payloads.contains(PAYLOAD_HIDE_MENU_ITEM)) {
|
||||
viewHolder.header_menu_item.visibility = GONE
|
||||
return
|
||||
}
|
||||
|
||||
super.bind(viewHolder, position, payloads)
|
||||
}
|
||||
|
||||
override fun bind(viewHolder: GroupieViewHolder, position: Int) {
|
||||
viewHolder.header_title.text = title
|
||||
viewHolder.header_menu_item.setImageResource(itemIcon)
|
||||
|
||||
val listener: OnClickListener? =
|
||||
onClickListener?.let { OnClickListener { onClickListener.invoke() } }
|
||||
viewHolder.root.setOnClickListener(listener)
|
||||
|
||||
val menuItemListener: OnClickListener? =
|
||||
menuItemOnClickListener?.let { OnClickListener { menuItemOnClickListener.invoke() } }
|
||||
viewHolder.header_menu_item.setOnClickListener(menuItemListener)
|
||||
}
|
||||
}
|
9
app/src/main/res/drawable/ic_sort_black_24dp.xml
Normal file
9
app/src/main/res/drawable/ic_sort_black_24dp.xml
Normal file
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M3,18h6v-2L3,16v2zM3,6v2h18L21,6L3,6zM3,13h12v-2L3,11v2z"/>
|
||||
</vector>
|
9
app/src/main/res/drawable/ic_sort_white_24dp.xml
Normal file
9
app/src/main/res/drawable/ic_sort_white_24dp.xml
Normal file
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FFFFFFFF"
|
||||
android:pathData="M3,18h6v-2L3,16v2zM3,6v2h18L21,6L3,6zM3,13h12v-2L3,11v2z"/>
|
||||
</vector>
|
31
app/src/main/res/layout/dialog_feed_group_reorder.xml
Normal file
31
app/src/main/res/layout/dialog_feed_group_reorder.xml
Normal file
|
@ -0,0 +1,31 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/feed_groups_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:clipToPadding="false"
|
||||
android:padding="6dp"
|
||||
android:scrollbars="vertical"
|
||||
tools:itemCount="100"
|
||||
tools:listitem="@layout/feed_group_reorder_item" />
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="?attr/separator_color" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/confirm_button"
|
||||
style="?buttonBarButtonStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end"
|
||||
android:text="@string/finish" />
|
||||
</LinearLayout>
|
62
app/src/main/res/layout/feed_group_reorder_item.xml
Normal file
62
app/src/main/res/layout/feed_group_reorder_item.xml
Normal file
|
@ -0,0 +1,62 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/layoutCard"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginTop="3dp"
|
||||
android:layout_marginRight="8dp"
|
||||
android:layout_marginBottom="3dp"
|
||||
android:minHeight="?listPreferredItemHeightSmall"
|
||||
android:orientation="horizontal"
|
||||
app:cardBackgroundColor="?attr/card_item_background_color"
|
||||
app:cardCornerRadius="5dp"
|
||||
app:cardElevation="2dp">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center_vertical">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/group_icon"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginLeft="16dp"
|
||||
tools:ignore="ContentDescription,RtlHardcoded"
|
||||
tools:src="?attr/ic_hot" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/group_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginTop="6dp"
|
||||
android:layout_marginBottom="6dp"
|
||||
android:layout_toLeftOf="@+id/handle"
|
||||
android:layout_toRightOf="@+id/group_icon"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="2"
|
||||
android:textAppearance="?textAppearanceListItem"
|
||||
tools:ignore="RtlHardcoded"
|
||||
tools:text="Lorem ipsum dolor sit amet" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/handle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingTop="12dp"
|
||||
android:paddingRight="16dp"
|
||||
android:paddingBottom="12dp"
|
||||
android:src="?attr/drag_handle"
|
||||
tools:ignore="ContentDescription,RtlHardcoded" />
|
||||
</RelativeLayout>
|
||||
</androidx.cardview.widget.CardView>
|
32
app/src/main/res/layout/header_with_menu_item.xml
Normal file
32
app/src/main/res/layout/header_with_menu_item.xml
Normal file
|
@ -0,0 +1,32 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingTop="12dp"
|
||||
android:paddingRight="16dp"
|
||||
android:paddingBottom="12dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/header_title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:ellipsize="end"
|
||||
android:gravity="start|center_vertical"
|
||||
android:maxLines="2"
|
||||
android:minHeight="24dp"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
tools:text="Header" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/header_menu_item"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
tools:src="?attr/ic_bookmark" />
|
||||
</LinearLayout>
|
|
@ -79,6 +79,7 @@
|
|||
<attr name="ic_sunny" format="reference"/>
|
||||
<attr name="ic_telescope" format="reference"/>
|
||||
<attr name="ic_megaphone" format="reference"/>
|
||||
<attr name="ic_sort" format="reference"/>
|
||||
|
||||
<attr name="progress_horizontal_drawable" format="reference"/>
|
||||
<!-- Can't refer to colors directly in drawable's xml-->
|
||||
|
|
|
@ -94,6 +94,7 @@
|
|||
<item name="ic_sunny">@drawable/ic_sunny_black_24dp</item>
|
||||
<item name="ic_telescope">@drawable/ic_telescope_black_24dp</item>
|
||||
<item name="ic_megaphone">@drawable/ic_megaphone_black_24dp</item>
|
||||
<item name="ic_sort">@drawable/ic_sort_black_24dp</item>
|
||||
|
||||
<item name="separator_color">@color/light_separator_color</item>
|
||||
<item name="contrast_background_color">@color/light_contrast_background_color</item>
|
||||
|
@ -198,6 +199,7 @@
|
|||
<item name="ic_sunny">@drawable/ic_sunny_white_24dp</item>
|
||||
<item name="ic_telescope">@drawable/ic_telescope_white_24dp</item>
|
||||
<item name="ic_megaphone">@drawable/ic_megaphone_white_24dp</item>
|
||||
<item name="ic_sort">@drawable/ic_sort_white_24dp</item>
|
||||
|
||||
<item name="separator_color">@color/dark_separator_color</item>
|
||||
<item name="contrast_background_color">@color/dark_contrast_background_color</item>
|
||||
|
|
BIN
assets/db.dia
BIN
assets/db.dia
Binary file not shown.
Loading…
Reference in a new issue