From d1d5f6821f636d7cbe4271ac0d783392ef4572c4 Mon Sep 17 00:00:00 2001 From: Mauricio Colli Date: Tue, 25 Feb 2020 23:01:23 -0300 Subject: [PATCH] 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. --- .../3.json | 23 +++- .../schabi/newpipe/database/Migrations.java | 3 +- .../newpipe/database/feed/dao/FeedGroupDAO.kt | 24 +++- .../database/feed/model/FeedGroupEntity.kt | 13 ++- .../newpipe/local/feed/FeedDatabaseManager.kt | 9 ++ .../subscription/SubscriptionFragment.kt | 26 ++++- .../subscription/dialog/FeedGroupDialog.kt | 4 +- .../dialog/FeedGroupDialogViewModel.kt | 4 +- .../dialog/FeedGroupReorderDialog.kt | 103 ++++++++++++++++++ .../dialog/FeedGroupReorderDialogViewModel.kt | 41 +++++++ .../subscription/item/FeedGroupReorderItem.kt | 48 ++++++++ .../subscription/item/HeaderWithMenuItem.kt | 48 ++++++++ .../main/res/drawable/ic_sort_black_24dp.xml | 9 ++ .../main/res/drawable/ic_sort_white_24dp.xml | 9 ++ .../res/layout/dialog_feed_group_reorder.xml | 31 ++++++ .../res/layout/feed_group_reorder_item.xml | 62 +++++++++++ .../main/res/layout/header_with_menu_item.xml | 32 ++++++ app/src/main/res/values/attrs.xml | 1 + app/src/main/res/values/styles.xml | 2 + assets/db.dia | Bin 3121 -> 3129 bytes 20 files changed, 473 insertions(+), 19 deletions(-) create mode 100644 app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupReorderDialog.kt create mode 100644 app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupReorderDialogViewModel.kt create mode 100644 app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupReorderItem.kt create mode 100644 app/src/main/java/org/schabi/newpipe/local/subscription/item/HeaderWithMenuItem.kt create mode 100644 app/src/main/res/drawable/ic_sort_black_24dp.xml create mode 100644 app/src/main/res/drawable/ic_sort_white_24dp.xml create mode 100644 app/src/main/res/layout/dialog_feed_group_reorder.xml create mode 100644 app/src/main/res/layout/feed_group_reorder_item.xml create mode 100644 app/src/main/res/layout/header_with_menu_item.xml diff --git a/app/schemas/org.schabi.newpipe.database.AppDatabase/3.json b/app/schemas/org.schabi.newpipe.database.AppDatabase/3.json index dcedb5a7f..313c3e27c 100644 --- a/app/schemas/org.schabi.newpipe.database.AppDatabase/3.json +++ b/app/schemas/org.schabi.newpipe.database.AppDatabase/3.json @@ -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')" ] } } \ No newline at end of file diff --git a/app/src/main/java/org/schabi/newpipe/database/Migrations.java b/app/src/main/java/org/schabi/newpipe/database/Migrations.java index b489900a0..ccb097a7b 100644 --- a/app/src/main/java/org/schabi/newpipe/database/Migrations.java +++ b/app/src/main/java/org/schabi/newpipe/database/Migrations.java @@ -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)"); diff --git a/app/src/main/java/org/schabi/newpipe/database/feed/dao/FeedGroupDAO.kt b/app/src/main/java/org/schabi/newpipe/database/feed/dao/FeedGroupDAO.kt index bf2b12df0..d2616f7d6 100644 --- a/app/src/main/java/org/schabi/newpipe/database/feed/dao/FeedGroupDAO.kt +++ b/app/src/main/java/org/schabi/newpipe/database/feed/dao/FeedGroupDAO.kt @@ -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> @Query("SELECT * FROM feed_group WHERE uid = :groupId") abstract fun getGroup(groupId: Long): Maybe - @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) { + 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 } diff --git a/app/src/main/java/org/schabi/newpipe/database/feed/model/FeedGroupEntity.kt b/app/src/main/java/org/schabi/newpipe/database/feed/model/FeedGroupEntity.kt index edb91406a..a84568dd6 100644 --- a/app/src/main/java/org/schabi/newpipe/database/feed/model/FeedGroupEntity.kt +++ b/app/src/main/java/org/schabi/newpipe/database/feed/model/FeedGroupEntity.kt @@ -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 } diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/FeedDatabaseManager.kt b/app/src/main/java/org/schabi/newpipe/local/feed/FeedDatabaseManager.kt index 08e661580..5231e16c6 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/FeedDatabaseManager.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/FeedDatabaseManager.kt @@ -147,6 +147,15 @@ class FeedDatabaseManager(context: Context) { .observeOn(AndroidSchedulers.mainThread()) } + fun updateGroupsOrder(groupIdList: List): 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> { return when (groupId) { FeedGroupEntity.GROUP_ALL_ID -> feedTable.oldestSubscriptionUpdateFromAll() diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.kt index 58175d5cd..04c797170 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.kt +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.kt @@ -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() { 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() { 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() { } 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() { 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) } + } } /////////////////////////////////////////////////////////////////////////// diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupDialog.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupDialog.kt index c199e72ca..b5f97bf12 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupDialog.kt +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupDialog.kt @@ -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())) diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupDialogViewModel.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupDialogViewModel.kt index 575c71f45..40ed38bd4 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupDialogViewModel.kt +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupDialogViewModel.kt @@ -58,9 +58,9 @@ class FeedGroupDialogViewModel(applicationContext: Context, val groupId: Long = .subscribe { successLiveData.postValue(FeedDialogEvent.SuccessEvent) }) } - fun updateGroup(name: String, selectedIcon: FeedGroupIcon, selectedSubscriptions: Set) { + fun updateGroup(name: String, selectedIcon: FeedGroupIcon, selectedSubscriptions: Set, 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) }) diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupReorderDialog.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupReorderDialog.kt new file mode 100644 index 000000000..6313b3c10 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupReorderDialog.kt @@ -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() + private val groupAdapter = GroupAdapter() + 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) { + val groupList: List + + 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) {} + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupReorderDialogViewModel.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupReorderDialogViewModel.kt new file mode 100644 index 000000000..21828ee04 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupReorderDialogViewModel.kt @@ -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>() + val dialogEventLiveData = MutableLiveData() + + 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) { + disposables.add(feedDatabaseManager.updateGroupsOrder(groupIdList) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { dialogEventLiveData.postValue(DialogEvent.SuccessEvent) }) + } + + sealed class DialogEvent { + object SuccessEvent : DialogEvent() + } +} \ No newline at end of file diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupReorderItem.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupReorderItem.kt new file mode 100644 index 000000000..cf010af7f --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupReorderItem.kt @@ -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 + } +} \ No newline at end of file diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/item/HeaderWithMenuItem.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/item/HeaderWithMenuItem.kt new file mode 100644 index 000000000..5ffdfe7c1 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/item/HeaderWithMenuItem.kt @@ -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) { + 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) + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_sort_black_24dp.xml b/app/src/main/res/drawable/ic_sort_black_24dp.xml new file mode 100644 index 000000000..fd4c56f0e --- /dev/null +++ b/app/src/main/res/drawable/ic_sort_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_sort_white_24dp.xml b/app/src/main/res/drawable/ic_sort_white_24dp.xml new file mode 100644 index 000000000..a0c153ad0 --- /dev/null +++ b/app/src/main/res/drawable/ic_sort_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/dialog_feed_group_reorder.xml b/app/src/main/res/layout/dialog_feed_group_reorder.xml new file mode 100644 index 000000000..82a9b1591 --- /dev/null +++ b/app/src/main/res/layout/dialog_feed_group_reorder.xml @@ -0,0 +1,31 @@ + + + + + + + +