Skip to content

Commit 668af4f

Browse files
authored
Merge pull request #13347 from theimpulson/subscriptions
Port subscriptions related changes from refactor
2 parents 0d65733 + bfcc31e commit 668af4f

24 files changed

+692
-1064
lines changed

app/build.gradle.kts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ plugins {
1111
alias(libs.plugins.jetbrains.kotlin.kapt)
1212
alias(libs.plugins.google.ksp)
1313
alias(libs.plugins.jetbrains.kotlin.parcelize)
14+
alias(libs.plugins.jetbrains.kotlinx.serialization)
1415
alias(libs.plugins.sonarqube)
1516
checkstyle
1617
}
@@ -246,6 +247,12 @@ dependencies {
246247
implementation(libs.google.android.material)
247248
implementation(libs.androidx.webkit)
248249

250+
// Coroutines interop
251+
implementation(libs.kotlinx.coroutines.rx3)
252+
253+
// Kotlinx Serialization
254+
implementation(libs.kotlinx.serialization.json)
255+
249256
/** Third-party libraries **/
250257
implementation(libs.livefront.bridge)
251258
implementation(libs.evernote.statesaver.core)

app/proguard-rules.pro

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,18 @@
4444
-keepclassmembers class * extends com.google.protobuf.GeneratedMessageLite {
4545
<fields>;
4646
}
47+
48+
## Keep Kotlinx Serialization classes
49+
-keepclassmembers class kotlinx.serialization.json.** {
50+
*** Companion;
51+
}
52+
-keepclasseswithmembers class kotlinx.serialization.json.** {
53+
kotlinx.serialization.KSerializer serializer(...);
54+
}
55+
-keep,includedescriptorclasses class org.schabi.newpipe.**$$serializer { *; }
56+
-keepclassmembers class org.schabi.newpipe.** {
57+
*** Companion;
58+
}
59+
-keepclasseswithmembers class org.schabi.newpipe.** {
60+
kotlinx.serialization.KSerializer serializer(...);
61+
}

app/src/main/AndroidManifest.xml

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -96,14 +96,6 @@
9696
android:exported="false"
9797
android:label="@string/title_activity_about" />
9898

99-
<service
100-
android:name=".local.subscription.services.SubscriptionsImportService"
101-
android:foregroundServiceType="dataSync" />
102-
103-
<service
104-
android:name=".local.subscription.services.SubscriptionsExportService"
105-
android:foregroundServiceType="dataSync" />
106-
10799
<service
108100
android:name=".local.feed.service.FeedLoadService"
109101
android:foregroundServiceType="dataSync" />

app/src/main/java/org/schabi/newpipe/local/subscription/ImportConfirmationDialog.java

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,63 @@
11
package org.schabi.newpipe.local.subscription;
22

33
import android.app.Dialog;
4-
import android.content.Intent;
54
import android.os.Bundle;
65

76
import androidx.annotation.NonNull;
87
import androidx.annotation.Nullable;
98
import androidx.appcompat.app.AlertDialog;
9+
import androidx.core.os.BundleCompat;
1010
import androidx.fragment.app.DialogFragment;
1111
import androidx.fragment.app.Fragment;
12+
import androidx.work.Constraints;
13+
import androidx.work.ExistingWorkPolicy;
14+
import androidx.work.NetworkType;
15+
import androidx.work.OneTimeWorkRequest;
16+
import androidx.work.OutOfQuotaPolicy;
17+
import androidx.work.WorkManager;
1218

1319
import com.livefront.bridge.Bridge;
1420

1521
import org.schabi.newpipe.R;
22+
import org.schabi.newpipe.local.subscription.workers.SubscriptionImportInput;
23+
import org.schabi.newpipe.local.subscription.workers.SubscriptionImportWorker;
1624

1725
public class ImportConfirmationDialog extends DialogFragment {
18-
protected Intent resultServiceIntent;
19-
private static final String EXTRA_RESULT_SERVICE_INTENT = "extra_result_service_intent";
20-
21-
public static void show(@NonNull final Fragment fragment,
22-
@NonNull final Intent resultServiceIntent) {
23-
final ImportConfirmationDialog confirmationDialog = new ImportConfirmationDialog();
24-
final Bundle args = new Bundle();
25-
args.putParcelable(EXTRA_RESULT_SERVICE_INTENT, resultServiceIntent);
26-
confirmationDialog.setArguments(args);
26+
private static final String INPUT = "input";
27+
28+
public static void show(@NonNull final Fragment fragment, final SubscriptionImportInput input) {
29+
final var confirmationDialog = new ImportConfirmationDialog();
30+
final var arguments = new Bundle();
31+
arguments.putParcelable(INPUT, input);
32+
confirmationDialog.setArguments(arguments);
2733
confirmationDialog.show(fragment.getParentFragmentManager(), null);
2834
}
2935

3036
@NonNull
3137
@Override
3238
public Dialog onCreateDialog(@Nullable final Bundle savedInstanceState) {
33-
return new AlertDialog.Builder(requireContext())
39+
final var context = requireContext();
40+
return new AlertDialog.Builder(context)
3441
.setMessage(R.string.import_network_expensive_warning)
3542
.setCancelable(true)
3643
.setNegativeButton(R.string.cancel, null)
3744
.setPositiveButton(R.string.ok, (dialogInterface, i) -> {
38-
requireContext().startService(resultServiceIntent);
45+
final var constraints = new Constraints.Builder()
46+
.setRequiredNetworkType(NetworkType.CONNECTED)
47+
.build();
48+
final var input = BundleCompat.getParcelable(requireArguments(), INPUT,
49+
SubscriptionImportInput.class);
50+
51+
final var req = new OneTimeWorkRequest.Builder(SubscriptionImportWorker.class)
52+
.setInputData(input.toData())
53+
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
54+
.setConstraints(constraints)
55+
.build();
56+
57+
WorkManager.getInstance(context)
58+
.enqueueUniqueWork(SubscriptionImportWorker.WORK_NAME,
59+
ExistingWorkPolicy.APPEND_OR_REPLACE, req);
60+
3961
dismiss();
4062
})
4163
.create();
@@ -45,7 +67,7 @@ public Dialog onCreateDialog(@Nullable final Bundle savedInstanceState) {
4567
public void onCreate(@Nullable final Bundle savedInstanceState) {
4668
super.onCreate(savedInstanceState);
4769

48-
resultServiceIntent = requireArguments().getParcelable(EXTRA_RESULT_SERVICE_INTENT);
70+
Bridge.restoreInstanceState(this, savedInstanceState);
4971
}
5072

5173
@Override

app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.kt

Lines changed: 4 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
package org.schabi.newpipe.local.subscription
22

3-
import android.app.Activity
43
import android.content.Context
54
import android.content.DialogInterface
6-
import android.content.Intent
75
import android.os.Bundle
86
import android.os.Parcelable
97
import android.view.LayoutInflater
@@ -15,8 +13,6 @@ import android.view.View
1513
import android.view.ViewGroup
1614
import android.webkit.MimeTypeMap
1715
import android.widget.Toast
18-
import androidx.activity.result.ActivityResult
19-
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
2016
import androidx.annotation.StringRes
2117
import androidx.appcompat.app.AlertDialog
2218
import androidx.lifecycle.ViewModelProvider
@@ -27,9 +23,6 @@ import com.xwray.groupie.GroupAdapter
2723
import com.xwray.groupie.Section
2824
import com.xwray.groupie.viewbinding.GroupieViewHolder
2925
import io.reactivex.rxjava3.disposables.CompositeDisposable
30-
import java.text.SimpleDateFormat
31-
import java.util.Date
32-
import java.util.Locale
3326
import org.schabi.newpipe.R
3427
import org.schabi.newpipe.database.feed.model.FeedGroupEntity.Companion.GROUP_ALL_ID
3528
import org.schabi.newpipe.databinding.DialogTitleBinding
@@ -53,13 +46,6 @@ import org.schabi.newpipe.local.subscription.item.FeedGroupCarouselItem
5346
import org.schabi.newpipe.local.subscription.item.GroupsHeader
5447
import org.schabi.newpipe.local.subscription.item.Header
5548
import org.schabi.newpipe.local.subscription.item.ImportSubscriptionsHintPlaceholderItem
56-
import org.schabi.newpipe.local.subscription.services.SubscriptionsExportService
57-
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService
58-
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.KEY_MODE
59-
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.KEY_VALUE
60-
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.PREVIOUS_EXPORT_MODE
61-
import org.schabi.newpipe.streams.io.NoFileManagerSafeGuard
62-
import org.schabi.newpipe.streams.io.StoredFileHelper
6349
import org.schabi.newpipe.util.NavigationHelper
6450
import org.schabi.newpipe.util.OnClickGesture
6551
import org.schabi.newpipe.util.ServiceHelper
@@ -72,6 +58,7 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
7258

7359
private lateinit var viewModel: SubscriptionViewModel
7460
private lateinit var subscriptionManager: SubscriptionManager
61+
private lateinit var importExportHelper: SubscriptionsImportExportHelper
7562
private val disposables: CompositeDisposable = CompositeDisposable()
7663

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

83-
private val requestExportLauncher =
84-
registerForActivityResult(StartActivityForResult(), this::requestExportResult)
85-
private val requestImportLauncher =
86-
registerForActivityResult(StartActivityForResult(), this::requestImportResult)
87-
8870
@State
8971
@JvmField
9072
var itemsListState: Parcelable? = null
@@ -104,6 +86,7 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
10486
override fun onAttach(context: Context) {
10587
super.onAttach(context)
10688
subscriptionManager = SubscriptionManager(requireContext())
89+
importExportHelper = SubscriptionsImportExportHelper(this)
10790
}
10891

10992
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
@@ -143,7 +126,7 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
143126
// -- Import --
144127
val importSubMenu = menu.addSubMenu(R.string.import_from)
145128

146-
addMenuItemToSubmenu(importSubMenu, R.string.previous_export) { onImportPreviousSelected() }
129+
addMenuItemToSubmenu(importSubMenu, R.string.previous_export) { importExportHelper.onImportPreviousSelected() }
147130
.setIcon(R.drawable.ic_backup)
148131

149132
for (service in ServiceList.all()) {
@@ -161,7 +144,7 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
161144
// -- Export --
162145
val exportSubMenu = menu.addSubMenu(R.string.export_to)
163146

164-
addMenuItemToSubmenu(exportSubMenu, R.string.file) { onExportSelected() }
147+
addMenuItemToSubmenu(exportSubMenu, R.string.file) { importExportHelper.onExportSelected() }
165148
.setIcon(R.drawable.ic_save)
166149
}
167150

@@ -197,51 +180,10 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
197180
NavigationHelper.openSubscriptionsImportFragment(fragmentManager, serviceId)
198181
}
199182

200-
private fun onImportPreviousSelected() {
201-
NoFileManagerSafeGuard.launchSafe(
202-
requestImportLauncher,
203-
StoredFileHelper.getPicker(activity, JSON_MIME_TYPE),
204-
TAG,
205-
requireContext()
206-
)
207-
}
208-
209-
private fun onExportSelected() {
210-
val date = SimpleDateFormat("yyyyMMddHHmm", Locale.ENGLISH).format(Date())
211-
val exportName = "newpipe_subscriptions_$date.json"
212-
213-
NoFileManagerSafeGuard.launchSafe(
214-
requestExportLauncher,
215-
StoredFileHelper.getNewPicker(activity, exportName, JSON_MIME_TYPE, null),
216-
TAG,
217-
requireContext()
218-
)
219-
}
220-
221183
private fun openReorderDialog() {
222184
FeedGroupReorderDialog().show(parentFragmentManager, null)
223185
}
224186

225-
private fun requestExportResult(result: ActivityResult) {
226-
if (result.data != null && result.resultCode == Activity.RESULT_OK) {
227-
activity.startService(
228-
Intent(activity, SubscriptionsExportService::class.java)
229-
.putExtra(SubscriptionsExportService.KEY_FILE_PATH, result.data?.data)
230-
)
231-
}
232-
}
233-
234-
private fun requestImportResult(result: ActivityResult) {
235-
if (result.data != null && result.resultCode == Activity.RESULT_OK) {
236-
ImportConfirmationDialog.show(
237-
this,
238-
Intent(activity, SubscriptionsImportService::class.java)
239-
.putExtra(KEY_MODE, PREVIOUS_EXPORT_MODE)
240-
.putExtra(KEY_VALUE, result.data?.data)
241-
)
242-
}
243-
}
244-
245187
// ////////////////////////////////////////////////////////////////////////
246188
// Fragment Views
247189
// ////////////////////////////////////////////////////////////////////////

app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionManager.kt

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package org.schabi.newpipe.local.subscription
22

33
import android.content.Context
4-
import android.util.Pair
54
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
65
import io.reactivex.rxjava3.core.Completable
76
import io.reactivex.rxjava3.core.Flowable
@@ -51,23 +50,16 @@ class SubscriptionManager(context: Context) {
5150
}
5251
}
5352

54-
fun upsertAll(infoList: List<Pair<ChannelInfo, List<ChannelTabInfo>>>): List<SubscriptionEntity> {
55-
val listEntities = subscriptionTable.upsertAll(
56-
infoList.map { SubscriptionEntity.from(it.first) }
57-
)
53+
fun upsertAll(infoList: List<Pair<ChannelInfo, ChannelTabInfo>>) {
54+
val listEntities = infoList.map { SubscriptionEntity.from(it.first) }
55+
subscriptionTable.upsertAll(listEntities)
5856

5957
database.runInTransaction {
6058
infoList.forEachIndexed { index, info ->
61-
info.second.forEach {
62-
feedDatabaseManager.upsertAll(
63-
listEntities[index].uid,
64-
it.relatedItems.filterIsInstance<StreamInfoItem>()
65-
)
66-
}
59+
val streams = info.second.relatedItems.filterIsInstance<StreamInfoItem>()
60+
feedDatabaseManager.upsertAll(listEntities[index].uid, streams)
6761
}
6862
}
69-
70-
return listEntities
7163
}
7264

7365
fun updateChannelInfo(info: ChannelInfo): Completable = subscriptionTable.getSubscription(info.serviceId, info.url)

0 commit comments

Comments
 (0)