Moved import/export options to menu

This commit is contained in:
litetex 2021-11-28 16:13:14 +01:00
parent 5b435c586e
commit 1d69bd48be
4 changed files with 73 additions and 316 deletions

View File

@ -1,24 +1,24 @@
package org.schabi.newpipe.local.subscription
import android.app.Activity
import android.content.BroadcastReceiver
import android.content.Context
import android.content.DialogInterface
import android.content.Intent
import android.content.IntentFilter
import android.os.Bundle
import android.os.Parcelable
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.SubMenu
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.activity.result.ActivityResult
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
import androidx.annotation.StringRes
import androidx.appcompat.app.AlertDialog
import androidx.lifecycle.ViewModelProvider
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import androidx.recyclerview.widget.GridLayoutManager
import com.xwray.groupie.Group
import com.xwray.groupie.GroupAdapter
@ -34,7 +34,9 @@ import org.schabi.newpipe.databinding.FeedItemCarouselBinding
import org.schabi.newpipe.databinding.FragmentSubscriptionBinding
import org.schabi.newpipe.error.ErrorInfo
import org.schabi.newpipe.error.UserAction
import org.schabi.newpipe.extractor.NewPipe
import org.schabi.newpipe.extractor.channel.ChannelInfoItem
import org.schabi.newpipe.extractor.exceptions.ExtractionException
import org.schabi.newpipe.fragments.BaseStateFragment
import org.schabi.newpipe.ktx.animate
import org.schabi.newpipe.local.subscription.SubscriptionViewModel.SubscriptionState
@ -45,13 +47,10 @@ import org.schabi.newpipe.local.subscription.item.EmptyPlaceholderItem
import org.schabi.newpipe.local.subscription.item.FeedGroupAddItem
import org.schabi.newpipe.local.subscription.item.FeedGroupCardItem
import org.schabi.newpipe.local.subscription.item.FeedGroupCarouselItem
import org.schabi.newpipe.local.subscription.item.FeedImportExportItem
import org.schabi.newpipe.local.subscription.item.HeaderWithMenuItem
import org.schabi.newpipe.local.subscription.item.HeaderWithMenuItem.Companion.PAYLOAD_UPDATE_VISIBILITY_MENU_ITEM
import org.schabi.newpipe.local.subscription.services.SubscriptionsExportService
import org.schabi.newpipe.local.subscription.services.SubscriptionsExportService.EXPORT_COMPLETE_ACTION
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.IMPORT_COMPLETE_ACTION
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.KEY_MODE
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.KEY_VALUE
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.PREVIOUS_EXPORT_MODE
@ -65,6 +64,7 @@ import org.schabi.newpipe.util.external_communication.ShareUtils
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import java.util.function.Supplier
class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
private var _binding: FragmentSubscriptionBinding? = null
@ -74,12 +74,9 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
private lateinit var subscriptionManager: SubscriptionManager
private val disposables: CompositeDisposable = CompositeDisposable()
private var subscriptionBroadcastReceiver: BroadcastReceiver? = null
private val groupAdapter = GroupAdapter<GroupieViewHolder<FeedItemCarouselBinding>>()
private val feedGroupsSection = Section()
private var feedGroupsCarousel: FeedGroupCarouselItem? = null
private lateinit var importExportItem: FeedImportExportItem
private lateinit var feedGroupsSortMenuItem: HeaderWithMenuItem
private val subscriptionsSection = Section()
@ -94,9 +91,6 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
@State
@JvmField
var feedGroupsListState: Parcelable? = null
@State
@JvmField
var importExportItemExpandedState: Boolean? = null
init {
setHasOptionsMenu(true)
@ -120,20 +114,10 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
return inflater.inflate(R.layout.fragment_subscription, container, false)
}
override fun onResume() {
super.onResume()
setupBroadcastReceiver()
}
override fun onPause() {
super.onPause()
itemsListState = binding.itemsList.layoutManager?.onSaveInstanceState()
feedGroupsListState = feedGroupsCarousel?.onSaveInstanceState()
importExportItemExpandedState = importExportItem.isExpanded
if (subscriptionBroadcastReceiver != null && activity != null) {
LocalBroadcastManager.getInstance(activity).unregisterReceiver(subscriptionBroadcastReceiver!!)
}
}
override fun onDestroy() {
@ -150,28 +134,75 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
activity.supportActionBar?.setDisplayShowTitleEnabled(true)
activity.supportActionBar?.setTitle(R.string.tab_subscriptions)
buildImportExportMenu(menu)
}
private fun setupBroadcastReceiver() {
if (activity == null) return
private fun buildImportExportMenu(menu: Menu) {
// -- Import --
if (subscriptionBroadcastReceiver != null) {
LocalBroadcastManager.getInstance(activity).unregisterReceiver(subscriptionBroadcastReceiver!!)
val importSubMenu = menu.addSubMenu(R.string.import_from)
addMenuItem(importSubMenu, R.string.previous_export) {
onImportPreviousSelected()
}
val filters = IntentFilter()
filters.addAction(EXPORT_COMPLETE_ACTION)
filters.addAction(IMPORT_COMPLETE_ACTION)
subscriptionBroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
_binding?.itemsList?.post {
importExportItem.isExpanded = false
importExportItem.notifyChanged(FeedImportExportItem.REFRESH_EXPANDED_STATUS)
val services = requireContext().resources.getStringArray(R.array.service_list)
for (serviceName in services) {
try {
val service = NewPipe.getService(serviceName)
val subscriptionExtractor = service.subscriptionExtractor ?: continue
val supportedSources = subscriptionExtractor.supportedSources
if (supportedSources.isEmpty()) continue
addMenuItem(importSubMenu, serviceName) {
onImportFromServiceSelected(service.serviceId)
}
} catch (e: ExtractionException) {
throw RuntimeException(
"Services array contains an entry that it's not a valid service name ($serviceName)",
e
)
}
}
LocalBroadcastManager.getInstance(activity).registerReceiver(subscriptionBroadcastReceiver!!, filters)
// -- Export --
val exportSubMenu = menu.addSubMenu(R.string.export_to)
addMenuItem(exportSubMenu, R.string.file) { onExportSelected() }
}
private fun addMenuItem(
subMenu: SubMenu,
@StringRes title: Int,
onClick: Runnable
): MenuItem {
return addMenuItem({ subMenu.add(title) }, onClick)
}
private fun addMenuItem(
subMenu: SubMenu,
title: String,
onClick: Runnable
): MenuItem {
return addMenuItem({ subMenu.add(title) }, onClick)
}
private fun addMenuItem(
menuItemSupplier: Supplier<MenuItem>,
onClick: Runnable
): MenuItem {
val item = menuItemSupplier.get()
item.setOnMenuItemClickListener { _ ->
onClick.run()
true
}
return item
}
private fun onImportFromServiceSelected(serviceId: Int) {
@ -263,13 +294,14 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
subscriptionsSection.setPlaceholder(EmptyPlaceholderItem())
subscriptionsSection.setHideWhenEmpty(true)
importExportItem = FeedImportExportItem(
{ onImportPreviousSelected() },
{ onImportFromServiceSelected(it) },
{ onExportSelected() },
importExportItemExpandedState ?: false
groupAdapter.add(
Section(
HeaderWithMenuItem(
getString(R.string.tab_subscriptions)
),
listOf(subscriptionsSection)
)
)
groupAdapter.add(Section(importExportItem, listOf(subscriptionsSection)))
}
override fun initViews(rootView: View, savedInstanceState: Bundle?) {
@ -371,13 +403,6 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
subscriptionsSection.update(result.subscriptions)
subscriptionsSection.setHideWhenEmpty(false)
if (result.subscriptions.isEmpty() && importExportItemExpandedState == null) {
binding.itemsList.post {
importExportItem.isExpanded = true
importExportItem.notifyChanged(FeedImportExportItem.REFRESH_EXPANDED_STATUS)
}
}
if (itemsListState != null) {
binding.itemsList.layoutManager?.onRestoreInstanceState(itemsListState)
itemsListState = null

View File

@ -1,122 +0,0 @@
package org.schabi.newpipe.local.subscription.item
import android.graphics.Color
import android.graphics.PorterDuff
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.annotation.DrawableRes
import com.xwray.groupie.viewbinding.BindableItem
import com.xwray.groupie.viewbinding.GroupieViewHolder
import org.schabi.newpipe.R
import org.schabi.newpipe.databinding.FeedImportExportGroupBinding
import org.schabi.newpipe.extractor.NewPipe
import org.schabi.newpipe.extractor.exceptions.ExtractionException
import org.schabi.newpipe.ktx.animateRotation
import org.schabi.newpipe.util.ServiceHelper
import org.schabi.newpipe.util.ThemeHelper
import org.schabi.newpipe.views.CollapsibleView
class FeedImportExportItem(
val onImportPreviousSelected: () -> Unit,
val onImportFromServiceSelected: (Int) -> Unit,
val onExportSelected: () -> Unit,
var isExpanded: Boolean = false
) : BindableItem<FeedImportExportGroupBinding>() {
companion object {
const val REFRESH_EXPANDED_STATUS = 123
}
override fun bind(viewBinding: FeedImportExportGroupBinding, position: Int, payloads: MutableList<Any>) {
if (payloads.contains(REFRESH_EXPANDED_STATUS)) {
viewBinding.importExportOptions.apply { if (isExpanded) expand() else collapse() }
return
}
super.bind(viewBinding, position, payloads)
}
override fun getLayout(): Int = R.layout.feed_import_export_group
override fun bind(viewBinding: FeedImportExportGroupBinding, position: Int) {
if (viewBinding.importFromOptions.childCount == 0) setupImportFromItems(viewBinding.importFromOptions)
if (viewBinding.exportToOptions.childCount == 0) setupExportToItems(viewBinding.exportToOptions)
expandIconListener?.let { viewBinding.importExportOptions.removeListener(it) }
expandIconListener = CollapsibleView.StateListener { newState ->
viewBinding.importExportExpandIcon.animateRotation(
250, if (newState == CollapsibleView.COLLAPSED) 0 else 180
)
}
viewBinding.importExportOptions.currentState = if (isExpanded) CollapsibleView.EXPANDED else CollapsibleView.COLLAPSED
viewBinding.importExportExpandIcon.rotation = if (isExpanded) 180F else 0F
viewBinding.importExportOptions.ready()
viewBinding.importExportOptions.addListener(expandIconListener)
viewBinding.importExport.setOnClickListener {
viewBinding.importExportOptions.switchState()
isExpanded = viewBinding.importExportOptions.currentState == CollapsibleView.EXPANDED
}
}
override fun unbind(viewHolder: GroupieViewHolder<FeedImportExportGroupBinding>) {
super.unbind(viewHolder)
expandIconListener?.let { viewHolder.binding.importExportOptions.removeListener(it) }
expandIconListener = null
}
override fun initializeViewBinding(view: View) = FeedImportExportGroupBinding.bind(view)
private var expandIconListener: CollapsibleView.StateListener? = null
private fun addItemView(title: String, @DrawableRes icon: Int, container: ViewGroup): View {
val itemRoot = View.inflate(container.context, R.layout.subscription_import_export_item, null)
val titleView = itemRoot.findViewById<TextView>(android.R.id.text1)
val iconView = itemRoot.findViewById<ImageView>(android.R.id.icon1)
titleView.text = title
iconView.setImageResource(icon)
container.addView(itemRoot)
return itemRoot
}
private fun setupImportFromItems(listHolder: ViewGroup) {
val previousBackupItem = addItemView(
listHolder.context.getString(R.string.previous_export),
R.drawable.ic_backup, listHolder
)
previousBackupItem.setOnClickListener { onImportPreviousSelected() }
val iconColor = if (ThemeHelper.isLightThemeSelected(listHolder.context)) Color.BLACK else Color.WHITE
val services = listHolder.context.resources.getStringArray(R.array.service_list)
for (serviceName in services) {
try {
val service = NewPipe.getService(serviceName)
val subscriptionExtractor = service.subscriptionExtractor ?: continue
val supportedSources = subscriptionExtractor.supportedSources
if (supportedSources.isEmpty()) continue
val itemView = addItemView(serviceName, ServiceHelper.getIcon(service.serviceId), listHolder)
val iconView = itemView.findViewById<ImageView>(android.R.id.icon1)
iconView.setColorFilter(iconColor, PorterDuff.Mode.SRC_IN)
itemView.setOnClickListener { onImportFromServiceSelected(service.serviceId) }
} catch (e: ExtractionException) {
throw RuntimeException("Services array contains an entry that it's not a valid service name ($serviceName)", e)
}
}
}
private fun setupExportToItems(listHolder: ViewGroup) {
val previousBackupItem = addItemView(
listHolder.context.getString(R.string.file),
R.drawable.ic_save, listHolder
)
previousBackupItem.setOnClickListener { onExportSelected() }
}
}

View File

@ -1,114 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:orientation="vertical">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="14dp"
android:background="?attr/contrast_background_color">
<View
android:layout_width="match_parent"
android:layout_height="5dp"
android:background="?attr/toolbar_shadow" />
</FrameLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?attr/separator_color" />
<LinearLayout
android:id="@+id/import_export"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:gravity="center_vertical"
android:orientation="horizontal">
<org.schabi.newpipe.views.NewPipeTextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:paddingLeft="16dp"
android:paddingTop="8dp"
android:paddingRight="16dp"
android:paddingBottom="8dp"
android:text="@string/tab_subscriptions"
android:textColor="?android:attr/textColorPrimary"
android:textSize="16sp"
android:textStyle="bold" />
<ImageView
android:id="@+id/import_export_expand_icon"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginRight="16dp"
app:srcCompat="@drawable/ic_expand_more"
tools:ignore="ContentDescription,RtlHardcoded" />
</LinearLayout>
<org.schabi.newpipe.views.CollapsibleView
android:id="@+id/import_export_options"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingBottom="6dp"
tools:ignore="RtlSymmetry">
<org.schabi.newpipe.views.NewPipeTextView
android:layout_width="match_parent"
android:layout_height="@dimen/subscription_import_export_title_height"
android:gravity="left|center"
android:maxLines="1"
android:paddingLeft="36dp"
android:text="@string/import_from"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textSize="13sp"
tools:ignore="RtlHardcoded" />
<LinearLayout
android:id="@+id/import_from_options"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="36dp"
android:layout_marginLeft="36dp"
android:orientation="vertical" />
<org.schabi.newpipe.views.NewPipeTextView
android:layout_width="match_parent"
android:layout_height="@dimen/subscription_import_export_title_height"
android:background="?attr/selectableItemBackground"
android:gravity="left|center"
android:maxLines="1"
android:paddingLeft="36dp"
android:text="@string/export_to"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textSize="13sp"
tools:ignore="RtlHardcoded" />
<LinearLayout
android:id="@+id/export_to_options"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="36dp"
android:orientation="vertical" />
<View
android:layout_width="match_parent"
android:layout_height="2dp"
android:layout_gravity="center_vertical"
android:layout_marginStart="8dp"
android:layout_marginTop="6dp"
android:layout_marginEnd="8dp"
android:background="?attr/separator_color" />
</org.schabi.newpipe.views.CollapsibleView>
</LinearLayout>

View File

@ -1,32 +0,0 @@
<?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:background="?attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:orientation="horizontal">
<ImageView
android:id="@android:id/icon1"
android:layout_width="48dp"
android:layout_height="@dimen/subscription_import_export_item_icon_size"
android:layout_marginTop="@dimen/subscription_import_export_item_icon_margin"
android:layout_marginBottom="@dimen/subscription_import_export_item_icon_margin"
android:scaleType="fitCenter"
tools:ignore="ContentDescription,RtlHardcoded"
tools:src="@drawable/place_holder_youtube" />
<org.schabi.newpipe.views.NewPipeTextView
android:id="@android:id/text1"
android:layout_width="match_parent"
android:layout_height="@dimen/subscription_import_export_item_height"
android:gravity="center_vertical"
android:maxLines="1"
android:paddingLeft="6dp"
android:paddingRight="6dp"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textSize="13sp"
tools:text="@string/youtube" />
</LinearLayout>