Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ plugins {
alias(libs.plugins.jetbrains.kotlin.kapt)
alias(libs.plugins.google.ksp)
alias(libs.plugins.jetbrains.kotlin.parcelize)
alias(libs.plugins.jetbrains.kotlinx.serialization)
alias(libs.plugins.sonarqube)
checkstyle
}
Expand Down Expand Up @@ -246,6 +247,12 @@ dependencies {
implementation(libs.google.android.material)
implementation(libs.androidx.webkit)

// Coroutines interop
implementation(libs.kotlinx.coroutines.rx3)

// Kotlinx Serialization
implementation(libs.kotlinx.serialization.json)

/** Third-party libraries **/
implementation(libs.livefront.bridge)
implementation(libs.evernote.statesaver.core)
Expand Down
15 changes: 15 additions & 0 deletions app/proguard-rules.pro
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,18 @@
-keepclassmembers class * extends com.google.protobuf.GeneratedMessageLite {
<fields>;
}

## Keep Kotlinx Serialization classes
-keepclassmembers class kotlinx.serialization.json.** {
*** Companion;
}
-keepclasseswithmembers class kotlinx.serialization.json.** {
kotlinx.serialization.KSerializer serializer(...);
}
-keep,includedescriptorclasses class org.schabi.newpipe.**$$serializer { *; }
-keepclassmembers class org.schabi.newpipe.** {
*** Companion;
}
-keepclasseswithmembers class org.schabi.newpipe.** {
kotlinx.serialization.KSerializer serializer(...);
}
8 changes: 0 additions & 8 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -96,14 +96,6 @@
android:exported="false"
android:label="@string/title_activity_about" />

<service
android:name=".local.subscription.services.SubscriptionsImportService"
android:foregroundServiceType="dataSync" />

<service
android:name=".local.subscription.services.SubscriptionsExportService"
android:foregroundServiceType="dataSync" />

<service
android:name=".local.feed.service.FeedLoadService"
android:foregroundServiceType="dataSync" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,41 +1,63 @@
package org.schabi.newpipe.local.subscription;

import android.app.Dialog;
import android.content.Intent;
import android.os.Bundle;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.core.os.BundleCompat;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.Fragment;
import androidx.work.Constraints;
import androidx.work.ExistingWorkPolicy;
import androidx.work.NetworkType;
import androidx.work.OneTimeWorkRequest;
import androidx.work.OutOfQuotaPolicy;
import androidx.work.WorkManager;

import com.livefront.bridge.Bridge;

import org.schabi.newpipe.R;
import org.schabi.newpipe.local.subscription.workers.SubscriptionImportInput;
import org.schabi.newpipe.local.subscription.workers.SubscriptionImportWorker;

public class ImportConfirmationDialog extends DialogFragment {
protected Intent resultServiceIntent;
private static final String EXTRA_RESULT_SERVICE_INTENT = "extra_result_service_intent";

public static void show(@NonNull final Fragment fragment,
@NonNull final Intent resultServiceIntent) {
final ImportConfirmationDialog confirmationDialog = new ImportConfirmationDialog();
final Bundle args = new Bundle();
args.putParcelable(EXTRA_RESULT_SERVICE_INTENT, resultServiceIntent);
confirmationDialog.setArguments(args);
private static final String INPUT = "input";

public static void show(@NonNull final Fragment fragment, final SubscriptionImportInput input) {
final var confirmationDialog = new ImportConfirmationDialog();
final var arguments = new Bundle();
arguments.putParcelable(INPUT, input);
confirmationDialog.setArguments(arguments);
confirmationDialog.show(fragment.getParentFragmentManager(), null);
}

@NonNull
@Override
public Dialog onCreateDialog(@Nullable final Bundle savedInstanceState) {
return new AlertDialog.Builder(requireContext())
final var context = requireContext();
return new AlertDialog.Builder(context)
.setMessage(R.string.import_network_expensive_warning)
.setCancelable(true)
.setNegativeButton(R.string.cancel, null)
.setPositiveButton(R.string.ok, (dialogInterface, i) -> {
requireContext().startService(resultServiceIntent);
final var constraints = new Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build();
final var input = BundleCompat.getParcelable(requireArguments(), INPUT,
SubscriptionImportInput.class);

final var req = new OneTimeWorkRequest.Builder(SubscriptionImportWorker.class)
.setInputData(input.toData())
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
.setConstraints(constraints)
.build();

WorkManager.getInstance(context)
.enqueueUniqueWork(SubscriptionImportWorker.WORK_NAME,
ExistingWorkPolicy.APPEND_OR_REPLACE, req);

dismiss();
})
.create();
Expand All @@ -45,7 +67,7 @@ public Dialog onCreateDialog(@Nullable final Bundle savedInstanceState) {
public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

resultServiceIntent = requireArguments().getParcelable(EXTRA_RESULT_SERVICE_INTENT);
Bridge.restoreInstanceState(this, savedInstanceState);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
package org.schabi.newpipe.local.subscription

import android.app.Activity
import android.content.Context
import android.content.DialogInterface
import android.content.Intent
import android.os.Bundle
import android.os.Parcelable
import android.view.LayoutInflater
Expand All @@ -15,8 +13,6 @@ import android.view.View
import android.view.ViewGroup
import android.webkit.MimeTypeMap
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
Expand All @@ -27,9 +23,6 @@ import com.xwray.groupie.GroupAdapter
import com.xwray.groupie.Section
import com.xwray.groupie.viewbinding.GroupieViewHolder
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.database.feed.model.FeedGroupEntity.Companion.GROUP_ALL_ID
import org.schabi.newpipe.databinding.DialogTitleBinding
Expand All @@ -53,13 +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.Header
import org.schabi.newpipe.local.subscription.item.ImportSubscriptionsHintPlaceholderItem
import org.schabi.newpipe.local.subscription.services.SubscriptionsExportService
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService
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
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
Expand All @@ -72,6 +58,7 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {

private lateinit var viewModel: SubscriptionViewModel
private lateinit var subscriptionManager: SubscriptionManager
private lateinit var importExportHelper: SubscriptionsImportExportHelper
private val disposables: CompositeDisposable = CompositeDisposable()

private val groupAdapter = GroupAdapter<GroupieViewHolder<FeedItemCarouselBinding>>()
Expand All @@ -80,11 +67,6 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
private lateinit var feedGroupsSortMenuItem: GroupsHeader
private val subscriptionsSection = Section()

private val requestExportLauncher =
registerForActivityResult(StartActivityForResult(), this::requestExportResult)
private val requestImportLauncher =
registerForActivityResult(StartActivityForResult(), this::requestImportResult)

@State
@JvmField
var itemsListState: Parcelable? = null
Expand All @@ -104,6 +86,7 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
override fun onAttach(context: Context) {
super.onAttach(context)
subscriptionManager = SubscriptionManager(requireContext())
importExportHelper = SubscriptionsImportExportHelper(this)
}

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
Expand Down Expand Up @@ -143,7 +126,7 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
// -- Import --
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)

for (service in ServiceList.all()) {
Expand All @@ -161,7 +144,7 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
// -- Export --
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)
}

Expand Down Expand Up @@ -197,51 +180,10 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
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() {
FeedGroupReorderDialog().show(parentFragmentManager, null)
}

private fun requestExportResult(result: ActivityResult) {
if (result.data != null && result.resultCode == Activity.RESULT_OK) {
activity.startService(
Intent(activity, SubscriptionsExportService::class.java)
.putExtra(SubscriptionsExportService.KEY_FILE_PATH, result.data?.data)
)
}
}

private fun requestImportResult(result: ActivityResult) {
if (result.data != null && result.resultCode == Activity.RESULT_OK) {
ImportConfirmationDialog.show(
this,
Intent(activity, SubscriptionsImportService::class.java)
.putExtra(KEY_MODE, PREVIOUS_EXPORT_MODE)
.putExtra(KEY_VALUE, result.data?.data)
)
}
}

// ////////////////////////////////////////////////////////////////////////
// Fragment Views
// ////////////////////////////////////////////////////////////////////////
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package org.schabi.newpipe.local.subscription

import android.content.Context
import android.util.Pair
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.core.Completable
import io.reactivex.rxjava3.core.Flowable
Expand Down Expand Up @@ -51,23 +50,16 @@ class SubscriptionManager(context: Context) {
}
}

fun upsertAll(infoList: List<Pair<ChannelInfo, List<ChannelTabInfo>>>): List<SubscriptionEntity> {
val listEntities = subscriptionTable.upsertAll(
infoList.map { SubscriptionEntity.from(it.first) }
)
fun upsertAll(infoList: List<Pair<ChannelInfo, ChannelTabInfo>>) {
val listEntities = infoList.map { SubscriptionEntity.from(it.first) }
subscriptionTable.upsertAll(listEntities)

database.runInTransaction {
infoList.forEachIndexed { index, info ->
info.second.forEach {
feedDatabaseManager.upsertAll(
listEntities[index].uid,
it.relatedItems.filterIsInstance<StreamInfoItem>()
)
}
val streams = info.second.relatedItems.filterIsInstance<StreamInfoItem>()
feedDatabaseManager.upsertAll(listEntities[index].uid, streams)
}
}

return listEntities
}

fun updateChannelInfo(info: ChannelInfo): Completable = subscriptionTable.getSubscription(info.serviceId, info.url)
Expand Down
Loading
Loading