BackupRestoreSettingsFragment: add UI options to import/export subscriptions
* create SubscriptionsImportExportHelper to share common code used in SubscriptionFragment and BackupRestoreSettingsFragment * Add UI options for import/export in BackupRestoreSettingsFragment
This commit is contained in:
parent
6fa97e17f5
commit
bfcc31ec89
6 changed files with 130 additions and 55 deletions
|
|
@ -1,6 +1,5 @@
|
||||||
package org.schabi.newpipe.local.subscription
|
package org.schabi.newpipe.local.subscription
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.DialogInterface
|
import android.content.DialogInterface
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
|
@ -14,8 +13,6 @@ import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.webkit.MimeTypeMap
|
import android.webkit.MimeTypeMap
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.result.ActivityResult
|
|
||||||
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
|
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
|
@ -26,9 +23,6 @@ import com.xwray.groupie.GroupAdapter
|
||||||
import com.xwray.groupie.Section
|
import com.xwray.groupie.Section
|
||||||
import com.xwray.groupie.viewbinding.GroupieViewHolder
|
import com.xwray.groupie.viewbinding.GroupieViewHolder
|
||||||
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||||
import java.text.SimpleDateFormat
|
|
||||||
import java.util.Date
|
|
||||||
import java.util.Locale
|
|
||||||
import org.schabi.newpipe.R
|
import org.schabi.newpipe.R
|
||||||
import org.schabi.newpipe.database.feed.model.FeedGroupEntity.Companion.GROUP_ALL_ID
|
import org.schabi.newpipe.database.feed.model.FeedGroupEntity.Companion.GROUP_ALL_ID
|
||||||
import org.schabi.newpipe.databinding.DialogTitleBinding
|
import org.schabi.newpipe.databinding.DialogTitleBinding
|
||||||
|
|
@ -52,10 +46,6 @@ import org.schabi.newpipe.local.subscription.item.FeedGroupCarouselItem
|
||||||
import org.schabi.newpipe.local.subscription.item.GroupsHeader
|
import org.schabi.newpipe.local.subscription.item.GroupsHeader
|
||||||
import org.schabi.newpipe.local.subscription.item.Header
|
import org.schabi.newpipe.local.subscription.item.Header
|
||||||
import org.schabi.newpipe.local.subscription.item.ImportSubscriptionsHintPlaceholderItem
|
import org.schabi.newpipe.local.subscription.item.ImportSubscriptionsHintPlaceholderItem
|
||||||
import org.schabi.newpipe.local.subscription.workers.SubscriptionExportWorker
|
|
||||||
import org.schabi.newpipe.local.subscription.workers.SubscriptionImportInput
|
|
||||||
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.NavigationHelper
|
||||||
import org.schabi.newpipe.util.OnClickGesture
|
import org.schabi.newpipe.util.OnClickGesture
|
||||||
import org.schabi.newpipe.util.ServiceHelper
|
import org.schabi.newpipe.util.ServiceHelper
|
||||||
|
|
@ -68,6 +58,7 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
|
||||||
|
|
||||||
private lateinit var viewModel: SubscriptionViewModel
|
private lateinit var viewModel: SubscriptionViewModel
|
||||||
private lateinit var subscriptionManager: SubscriptionManager
|
private lateinit var subscriptionManager: SubscriptionManager
|
||||||
|
private lateinit var importExportHelper: SubscriptionsImportExportHelper
|
||||||
private val disposables: CompositeDisposable = CompositeDisposable()
|
private val disposables: CompositeDisposable = CompositeDisposable()
|
||||||
|
|
||||||
private val groupAdapter = GroupAdapter<GroupieViewHolder<FeedItemCarouselBinding>>()
|
private val groupAdapter = GroupAdapter<GroupieViewHolder<FeedItemCarouselBinding>>()
|
||||||
|
|
@ -76,11 +67,6 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
|
||||||
private lateinit var feedGroupsSortMenuItem: GroupsHeader
|
private lateinit var feedGroupsSortMenuItem: GroupsHeader
|
||||||
private val subscriptionsSection = Section()
|
private val subscriptionsSection = Section()
|
||||||
|
|
||||||
private val requestExportLauncher =
|
|
||||||
registerForActivityResult(StartActivityForResult(), this::requestExportResult)
|
|
||||||
private val requestImportLauncher =
|
|
||||||
registerForActivityResult(StartActivityForResult(), this::requestImportResult)
|
|
||||||
|
|
||||||
@State
|
@State
|
||||||
@JvmField
|
@JvmField
|
||||||
var itemsListState: Parcelable? = null
|
var itemsListState: Parcelable? = null
|
||||||
|
|
@ -100,6 +86,7 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
|
||||||
override fun onAttach(context: Context) {
|
override fun onAttach(context: Context) {
|
||||||
super.onAttach(context)
|
super.onAttach(context)
|
||||||
subscriptionManager = SubscriptionManager(requireContext())
|
subscriptionManager = SubscriptionManager(requireContext())
|
||||||
|
importExportHelper = SubscriptionsImportExportHelper(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
|
|
@ -139,7 +126,7 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
|
||||||
// -- Import --
|
// -- Import --
|
||||||
val importSubMenu = menu.addSubMenu(R.string.import_from)
|
val importSubMenu = menu.addSubMenu(R.string.import_from)
|
||||||
|
|
||||||
addMenuItemToSubmenu(importSubMenu, R.string.previous_export) { onImportPreviousSelected() }
|
addMenuItemToSubmenu(importSubMenu, R.string.previous_export) { importExportHelper.onImportPreviousSelected() }
|
||||||
.setIcon(R.drawable.ic_backup)
|
.setIcon(R.drawable.ic_backup)
|
||||||
|
|
||||||
for (service in ServiceList.all()) {
|
for (service in ServiceList.all()) {
|
||||||
|
|
@ -157,7 +144,7 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
|
||||||
// -- Export --
|
// -- Export --
|
||||||
val exportSubMenu = menu.addSubMenu(R.string.export_to)
|
val exportSubMenu = menu.addSubMenu(R.string.export_to)
|
||||||
|
|
||||||
addMenuItemToSubmenu(exportSubMenu, R.string.file) { onExportSelected() }
|
addMenuItemToSubmenu(exportSubMenu, R.string.file) { importExportHelper.onExportSelected() }
|
||||||
.setIcon(R.drawable.ic_save)
|
.setIcon(R.drawable.ic_save)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -193,48 +180,10 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
|
||||||
NavigationHelper.openSubscriptionsImportFragment(fragmentManager, serviceId)
|
NavigationHelper.openSubscriptionsImportFragment(fragmentManager, serviceId)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onImportPreviousSelected() {
|
|
||||||
NoFileManagerSafeGuard.launchSafe(
|
|
||||||
requestImportLauncher,
|
|
||||||
StoredFileHelper.getPicker(activity, JSON_MIME_TYPE),
|
|
||||||
TAG,
|
|
||||||
requireContext()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onExportSelected() {
|
|
||||||
val date = SimpleDateFormat("yyyyMMddHHmm", Locale.ENGLISH).format(Date())
|
|
||||||
val exportName = "newpipe_subscriptions_$date.json"
|
|
||||||
|
|
||||||
NoFileManagerSafeGuard.launchSafe(
|
|
||||||
requestExportLauncher,
|
|
||||||
StoredFileHelper.getNewPicker(activity, exportName, JSON_MIME_TYPE, null),
|
|
||||||
TAG,
|
|
||||||
requireContext()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun openReorderDialog() {
|
private fun openReorderDialog() {
|
||||||
FeedGroupReorderDialog().show(parentFragmentManager, null)
|
FeedGroupReorderDialog().show(parentFragmentManager, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun requestExportResult(result: ActivityResult) {
|
|
||||||
val data = result.data?.data
|
|
||||||
if (data != null && result.resultCode == Activity.RESULT_OK) {
|
|
||||||
SubscriptionExportWorker.schedule(activity, data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun requestImportResult(result: ActivityResult) {
|
|
||||||
val data = result.data?.dataString
|
|
||||||
if (data != null && result.resultCode == Activity.RESULT_OK) {
|
|
||||||
ImportConfirmationDialog.show(
|
|
||||||
this,
|
|
||||||
SubscriptionImportInput.PreviousExportMode(data)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ////////////////////////////////////////////////////////////////////////
|
// ////////////////////////////////////////////////////////////////////////
|
||||||
// Fragment Views
|
// Fragment Views
|
||||||
// ////////////////////////////////////////////////////////////////////////
|
// ////////////////////////////////////////////////////////////////////////
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,82 @@
|
||||||
|
package org.schabi.newpipe.local.subscription
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.activity.result.ActivityResult
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Date
|
||||||
|
import java.util.Locale
|
||||||
|
import org.schabi.newpipe.local.subscription.SubscriptionFragment.Companion.JSON_MIME_TYPE
|
||||||
|
import org.schabi.newpipe.local.subscription.workers.SubscriptionExportWorker
|
||||||
|
import org.schabi.newpipe.local.subscription.workers.SubscriptionImportInput
|
||||||
|
import org.schabi.newpipe.streams.io.NoFileManagerSafeGuard
|
||||||
|
import org.schabi.newpipe.streams.io.StoredFileHelper
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class has to be created in onAttach() or onCreate().
|
||||||
|
*
|
||||||
|
* It contains registerForActivityResult calls and those
|
||||||
|
* calls are only allowed before a fragment/activity is created.
|
||||||
|
*/
|
||||||
|
class SubscriptionsImportExportHelper(
|
||||||
|
val fragment: Fragment
|
||||||
|
) {
|
||||||
|
val context: Context = fragment.requireContext()
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val TAG: String =
|
||||||
|
SubscriptionsImportExportHelper::class.java.simpleName + "@" + Integer.toHexString(
|
||||||
|
hashCode()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val requestExportLauncher =
|
||||||
|
fragment.registerForActivityResult(StartActivityForResult(), this::requestExportResult)
|
||||||
|
private val requestImportLauncher =
|
||||||
|
fragment.registerForActivityResult(StartActivityForResult(), this::requestImportResult)
|
||||||
|
|
||||||
|
private fun requestExportResult(result: ActivityResult) {
|
||||||
|
val data = result.data?.data
|
||||||
|
if (data != null && result.resultCode == Activity.RESULT_OK) {
|
||||||
|
SubscriptionExportWorker.schedule(context, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun requestImportResult(result: ActivityResult) {
|
||||||
|
val data = result.data?.dataString
|
||||||
|
if (data != null && result.resultCode == Activity.RESULT_OK) {
|
||||||
|
ImportConfirmationDialog.show(
|
||||||
|
fragment,
|
||||||
|
SubscriptionImportInput.PreviousExportMode(data)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onExportSelected() {
|
||||||
|
val date = SimpleDateFormat("yyyyMMddHHmm", Locale.ENGLISH).format(Date())
|
||||||
|
val exportName = "newpipe_subscriptions_$date.json"
|
||||||
|
|
||||||
|
NoFileManagerSafeGuard.launchSafe(
|
||||||
|
requestExportLauncher,
|
||||||
|
StoredFileHelper.getNewPicker(
|
||||||
|
context,
|
||||||
|
exportName,
|
||||||
|
JSON_MIME_TYPE,
|
||||||
|
null
|
||||||
|
),
|
||||||
|
TAG,
|
||||||
|
context
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onImportPreviousSelected() {
|
||||||
|
NoFileManagerSafeGuard.launchSafe(
|
||||||
|
requestImportLauncher,
|
||||||
|
StoredFileHelper.getPicker(context, JSON_MIME_TYPE),
|
||||||
|
TAG,
|
||||||
|
context
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -27,6 +27,7 @@ import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.error.ErrorInfo;
|
import org.schabi.newpipe.error.ErrorInfo;
|
||||||
import org.schabi.newpipe.error.ErrorUtil;
|
import org.schabi.newpipe.error.ErrorUtil;
|
||||||
import org.schabi.newpipe.error.UserAction;
|
import org.schabi.newpipe.error.UserAction;
|
||||||
|
import org.schabi.newpipe.local.subscription.SubscriptionsImportExportHelper;
|
||||||
import org.schabi.newpipe.settings.export.BackupFileLocator;
|
import org.schabi.newpipe.settings.export.BackupFileLocator;
|
||||||
import org.schabi.newpipe.settings.export.ImportExportManager;
|
import org.schabi.newpipe.settings.export.ImportExportManager;
|
||||||
import org.schabi.newpipe.streams.io.NoFileManagerSafeGuard;
|
import org.schabi.newpipe.streams.io.NoFileManagerSafeGuard;
|
||||||
|
|
@ -57,8 +58,15 @@ public class BackupRestoreSettingsFragment extends BasePreferenceFragment {
|
||||||
private final ActivityResultLauncher<Intent> requestExportPathLauncher =
|
private final ActivityResultLauncher<Intent> requestExportPathLauncher =
|
||||||
registerForActivityResult(new ActivityResultContracts.StartActivityForResult(),
|
registerForActivityResult(new ActivityResultContracts.StartActivityForResult(),
|
||||||
this::requestExportPathResult);
|
this::requestExportPathResult);
|
||||||
|
private SubscriptionsImportExportHelper importExportHelper;
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAttach(@NonNull final Context context) {
|
||||||
|
super.onAttach(context);
|
||||||
|
importExportHelper = new SubscriptionsImportExportHelper(this);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreatePreferences(@Nullable final Bundle savedInstanceState,
|
public void onCreatePreferences(@Nullable final Bundle savedInstanceState,
|
||||||
@Nullable final String rootKey) {
|
@Nullable final String rootKey) {
|
||||||
|
|
@ -123,6 +131,21 @@ public class BackupRestoreSettingsFragment extends BasePreferenceFragment {
|
||||||
alertDialog.show();
|
alertDialog.show();
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
final Preference exportSubsPreference =
|
||||||
|
requirePreference(R.string.export_subscriptions_key);
|
||||||
|
exportSubsPreference.setOnPreferenceClickListener(reference -> {
|
||||||
|
importExportHelper.onExportSelected();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
final Preference importSubsPreference =
|
||||||
|
requirePreference(R.string.import_subscriptions_key);
|
||||||
|
importSubsPreference.setOnPreferenceClickListener(preference -> {
|
||||||
|
importExportHelper.onImportPreviousSelected();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void requestExportPathResult(final ActivityResult result) {
|
private void requestExportPathResult(final ActivityResult result) {
|
||||||
|
|
|
||||||
|
|
@ -413,6 +413,8 @@
|
||||||
<string name="import_export_data_path">import_export_data_path</string>
|
<string name="import_export_data_path">import_export_data_path</string>
|
||||||
<string name="import_data">import_data</string>
|
<string name="import_data">import_data</string>
|
||||||
<string name="export_data">export_data</string>
|
<string name="export_data">export_data</string>
|
||||||
|
<string name="import_subscriptions_key">import_subscriptions_key</string>
|
||||||
|
<string name="export_subscriptions_key">export_subscriptions_key</string>
|
||||||
|
|
||||||
<string name="clear_cookie_key">clear_cookie</string>
|
<string name="clear_cookie_key">clear_cookie</string>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -520,6 +520,11 @@
|
||||||
<string name="export_ongoing">Exporting…</string>
|
<string name="export_ongoing">Exporting…</string>
|
||||||
<string name="import_file_title">Import file</string>
|
<string name="import_file_title">Import file</string>
|
||||||
<string name="previous_export">Previous export</string>
|
<string name="previous_export">Previous export</string>
|
||||||
|
<string name="import_subscriptions_title">Import subscriptions"</string>
|
||||||
|
<string name="export_subscriptions_title">Export subscriptions</string>
|
||||||
|
<string name="import_subscriptions_summary">Import subscriptions from a previous .json export"</string>
|
||||||
|
<string name="export_subscriptions_summary">Export your subscriptions to a .json file</string>
|
||||||
|
<string name="import_from_previous_export">Import from previous export</string>
|
||||||
<string name="subscriptions_import_unsuccessful">Could not import subscriptions</string>
|
<string name="subscriptions_import_unsuccessful">Could not import subscriptions</string>
|
||||||
<string name="subscriptions_export_unsuccessful">Could not export subscriptions</string>
|
<string name="subscriptions_export_unsuccessful">Could not export subscriptions</string>
|
||||||
<string name="import_youtube_instructions">Import YouTube subscriptions from Google takeout:
|
<string name="import_youtube_instructions">Import YouTube subscriptions from Google takeout:
|
||||||
|
|
|
||||||
|
|
@ -22,4 +22,18 @@
|
||||||
android:summary="@string/reset_settings_summary"
|
android:summary="@string/reset_settings_summary"
|
||||||
app:singleLineTitle="false"
|
app:singleLineTitle="false"
|
||||||
app:iconSpaceReserved="false" />
|
app:iconSpaceReserved="false" />
|
||||||
|
|
||||||
|
<Preference
|
||||||
|
android:key="@string/export_subscriptions_key"
|
||||||
|
android:title="@string/export_subscriptions_title"
|
||||||
|
android:summary="@string/export_subscriptions_summary"
|
||||||
|
app:singleLineTitle="false"
|
||||||
|
app:iconSpaceReserved="false" />
|
||||||
|
|
||||||
|
<Preference
|
||||||
|
android:key="@string/import_subscriptions_key"
|
||||||
|
android:title="@string/import_subscriptions_title"
|
||||||
|
android:summary="@string/import_subscriptions_summary"
|
||||||
|
app:singleLineTitle="false"
|
||||||
|
app:iconSpaceReserved="false" />
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
||||||
Loading…
Add table
Add a link
Reference in a new issue