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 008228083..4295424e6 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 @@ -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,6 +34,7 @@ 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.ServiceList import org.schabi.newpipe.extractor.channel.ChannelInfoItem import org.schabi.newpipe.fragments.BaseStateFragment import org.schabi.newpipe.ktx.animate @@ -45,13 +46,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 @@ -59,6 +57,7 @@ import org.schabi.newpipe.streams.io.NoFileManagerSafeGuard import org.schabi.newpipe.streams.io.StoredFileHelper import org.schabi.newpipe.util.NavigationHelper import org.schabi.newpipe.util.OnClickGesture +import org.schabi.newpipe.util.ServiceHelper import org.schabi.newpipe.util.ThemeHelper.getGridSpanCountChannels import org.schabi.newpipe.util.ThemeHelper.shouldUseGridLayout import org.schabi.newpipe.util.external_communication.ShareUtils @@ -74,12 +73,9 @@ class SubscriptionFragment : BaseStateFragment() { private lateinit var subscriptionManager: SubscriptionManager private val disposables: CompositeDisposable = CompositeDisposable() - private var subscriptionBroadcastReceiver: BroadcastReceiver? = null - private val groupAdapter = GroupAdapter>() private val feedGroupsSection = Section() private var feedGroupsCarousel: FeedGroupCarouselItem? = null - private lateinit var importExportItem: FeedImportExportItem private lateinit var feedGroupsSortMenuItem: HeaderWithMenuItem private val subscriptionsSection = Section() @@ -91,12 +87,10 @@ class SubscriptionFragment : BaseStateFragment() { @State @JvmField var itemsListState: Parcelable? = null + @State @JvmField var feedGroupsListState: Parcelable? = null - @State - @JvmField - var importExportItemExpandedState: Boolean? = null init { setHasOptionsMenu(true) @@ -120,20 +114,10 @@ class SubscriptionFragment : BaseStateFragment() { 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,61 @@ class SubscriptionFragment : BaseStateFragment() { 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 -- + val importSubMenu = menu.addSubMenu(R.string.import_from) - if (subscriptionBroadcastReceiver != null) { - LocalBroadcastManager.getInstance(activity).unregisterReceiver(subscriptionBroadcastReceiver!!) - } + addMenuItemToSubmenu(importSubMenu, R.string.previous_export) { onImportPreviousSelected() } + .setIcon(R.drawable.ic_backup) - 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) - } + for (service in ServiceList.all()) { + val subscriptionExtractor = service.subscriptionExtractor ?: continue + + val supportedSources = subscriptionExtractor.supportedSources + if (supportedSources.isEmpty()) continue + + addMenuItemToSubmenu(importSubMenu, service.serviceInfo.name) { + onImportFromServiceSelected(service.serviceId) } + .setIcon(ServiceHelper.getIcon(service.serviceId)) } - LocalBroadcastManager.getInstance(activity).registerReceiver(subscriptionBroadcastReceiver!!, filters) + // -- Export -- + val exportSubMenu = menu.addSubMenu(R.string.export_to) + + addMenuItemToSubmenu(exportSubMenu, R.string.file) { onExportSelected() } + .setIcon(R.drawable.ic_save) + } + + private fun addMenuItemToSubmenu( + subMenu: SubMenu, + @StringRes title: Int, + onClick: Runnable + ): MenuItem { + return setClickListenerToMenuItem(subMenu.add(title), onClick) + } + + private fun addMenuItemToSubmenu( + subMenu: SubMenu, + title: String, + onClick: Runnable + ): MenuItem { + return setClickListenerToMenuItem(subMenu.add(title), onClick) + } + + private fun setClickListenerToMenuItem( + menuItem: MenuItem, + onClick: Runnable + ): MenuItem { + menuItem.setOnMenuItemClickListener { _ -> + onClick.run() + true + } + return menuItem } private fun onImportFromServiceSelected(serviceId: Int) { @@ -263,13 +280,14 @@ class SubscriptionFragment : BaseStateFragment() { 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 +389,6 @@ class SubscriptionFragment : BaseStateFragment() { 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 diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedImportExportItem.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedImportExportItem.kt deleted file mode 100644 index aacfc77ad..000000000 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedImportExportItem.kt +++ /dev/null @@ -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() { - companion object { - const val REFRESH_EXPANDED_STATUS = 123 - } - - override fun bind(viewBinding: FeedImportExportGroupBinding, position: Int, payloads: MutableList) { - 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) { - 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(android.R.id.text1) - val iconView = itemRoot.findViewById(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(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() } - } -} diff --git a/app/src/main/java/org/schabi/newpipe/settings/PeertubeInstanceListFragment.java b/app/src/main/java/org/schabi/newpipe/settings/PeertubeInstanceListFragment.java index c7eb0be40..1ff7947fd 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/PeertubeInstanceListFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/PeertubeInstanceListFragment.java @@ -207,7 +207,7 @@ public class PeertubeInstanceListFragment extends Fragment { new AlertDialog.Builder(c) .setTitle(R.string.peertube_instance_add_title) - .setIcon(R.drawable.place_holder_peertube) + .setIcon(R.drawable.ic_placeholder_peertube) .setView(dialogBinding.getRoot()) .setNegativeButton(R.string.cancel, null) .setPositiveButton(R.string.ok, (dialog1, which) -> { @@ -411,7 +411,7 @@ public class PeertubeInstanceListFragment extends Fragment { lastChecked = instanceRB; } }); - instanceIconView.setImageResource(R.drawable.place_holder_peertube); + instanceIconView.setImageResource(R.drawable.ic_placeholder_peertube); } @SuppressLint("ClickableViewAccessibility") diff --git a/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java b/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java index d41493a7f..bf173e08d 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java @@ -31,17 +31,17 @@ public final class ServiceHelper { public static int getIcon(final int serviceId) { switch (serviceId) { case 0: - return R.drawable.place_holder_youtube; + return R.drawable.ic_smart_display; case 1: - return R.drawable.place_holder_cloud; + return R.drawable.ic_cloud; case 2: - return R.drawable.place_holder_gadse; + return R.drawable.ic_placeholder_media_ccc; case 3: - return R.drawable.place_holder_peertube; + return R.drawable.ic_placeholder_peertube; case 4: - return R.drawable.place_holder_bandcamp; + return R.drawable.ic_placeholder_bandcamp; default: - return R.drawable.place_holder_circle; + return R.drawable.ic_circle; } } diff --git a/app/src/main/res/drawable-nodpi/place_holder_bandcamp.png b/app/src/main/res/drawable-nodpi/place_holder_bandcamp.png deleted file mode 100644 index 13c44b649..000000000 Binary files a/app/src/main/res/drawable-nodpi/place_holder_bandcamp.png and /dev/null differ diff --git a/app/src/main/res/drawable-nodpi/place_holder_circle.png b/app/src/main/res/drawable-nodpi/place_holder_circle.png deleted file mode 100644 index 630d0454e..000000000 Binary files a/app/src/main/res/drawable-nodpi/place_holder_circle.png and /dev/null differ diff --git a/app/src/main/res/drawable-nodpi/place_holder_cloud.png b/app/src/main/res/drawable-nodpi/place_holder_cloud.png deleted file mode 100644 index c4ba2a6f4..000000000 Binary files a/app/src/main/res/drawable-nodpi/place_holder_cloud.png and /dev/null differ diff --git a/app/src/main/res/drawable-nodpi/place_holder_gadse.png b/app/src/main/res/drawable-nodpi/place_holder_gadse.png deleted file mode 100644 index 9b479ed4f..000000000 Binary files a/app/src/main/res/drawable-nodpi/place_holder_gadse.png and /dev/null differ diff --git a/app/src/main/res/drawable-nodpi/place_holder_peertube.png b/app/src/main/res/drawable-nodpi/place_holder_peertube.png deleted file mode 100644 index 81dfdb8cc..000000000 Binary files a/app/src/main/res/drawable-nodpi/place_holder_peertube.png and /dev/null differ diff --git a/app/src/main/res/drawable-nodpi/place_holder_youtube.png b/app/src/main/res/drawable-nodpi/place_holder_youtube.png deleted file mode 100644 index d147c6643..000000000 Binary files a/app/src/main/res/drawable-nodpi/place_holder_youtube.png and /dev/null differ diff --git a/app/src/main/res/drawable/ic_circle.xml b/app/src/main/res/drawable/ic_circle.xml new file mode 100644 index 000000000..dc0a218b8 --- /dev/null +++ b/app/src/main/res/drawable/ic_circle.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_cloud.xml b/app/src/main/res/drawable/ic_cloud.xml new file mode 100644 index 000000000..15a682b76 --- /dev/null +++ b/app/src/main/res/drawable/ic_cloud.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_placeholder_bandcamp.xml b/app/src/main/res/drawable/ic_placeholder_bandcamp.xml new file mode 100644 index 000000000..411e69854 --- /dev/null +++ b/app/src/main/res/drawable/ic_placeholder_bandcamp.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_placeholder_media_ccc.xml b/app/src/main/res/drawable/ic_placeholder_media_ccc.xml new file mode 100644 index 000000000..cdc743cb2 --- /dev/null +++ b/app/src/main/res/drawable/ic_placeholder_media_ccc.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_placeholder_peertube.xml b/app/src/main/res/drawable/ic_placeholder_peertube.xml new file mode 100644 index 000000000..263d92d70 --- /dev/null +++ b/app/src/main/res/drawable/ic_placeholder_peertube.xml @@ -0,0 +1,9 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_smart_display.xml b/app/src/main/res/drawable/ic_smart_display.xml new file mode 100644 index 000000000..d666a3b37 --- /dev/null +++ b/app/src/main/res/drawable/ic_smart_display.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/layout/drawer_header.xml b/app/src/main/res/layout/drawer_header.xml index d2e936870..94e045863 100644 --- a/app/src/main/res/layout/drawer_header.xml +++ b/app/src/main/res/layout/drawer_header.xml @@ -86,7 +86,7 @@ android:scaleType="fitCenter" app:tint="@color/drawer_header_font_color" tools:ignore="ContentDescription" - tools:srcCompat="@drawable/place_holder_youtube" /> + tools:srcCompat="@drawable/ic_smart_display" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/item_instance.xml b/app/src/main/res/layout/item_instance.xml index 1a96c5bb2..dd5b4156f 100644 --- a/app/src/main/res/layout/item_instance.xml +++ b/app/src/main/res/layout/item_instance.xml @@ -26,7 +26,7 @@ android:layout_centerVertical="true" android:layout_marginLeft="10dp" tools:ignore="ContentDescription,RtlHardcoded" - tools:src="@drawable/place_holder_peertube" /> + tools:src="@drawable/ic_placeholder_peertube" /> - - - - - - diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 022dc5179..419b3ca43 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -58,11 +58,6 @@ 150dp 9dp - 32dp - 42dp - 24dp - - 9dp 15sp diff --git a/app/src/main/res/values/settings_keys.xml b/app/src/main/res/values/settings_keys.xml index bf42aaf0e..c13caf610 100644 --- a/app/src/main/res/values/settings_keys.xml +++ b/app/src/main/res/values/settings_keys.xml @@ -4,10 +4,6 @@ last_used_preferences_version - - @string/youtube - @string/soundcloud - service @string/youtube diff --git a/app/src/main/res/values/styles_misc.xml b/app/src/main/res/values/styles_misc.xml index f539eb5a1..ca285ae15 100644 --- a/app/src/main/res/values/styles_misc.xml +++ b/app/src/main/res/values/styles_misc.xml @@ -69,14 +69,17 @@ +