Merge branch 'refactor' into pr11060

This commit is contained in:
Stypox 2024-11-10 16:50:46 +01:00
commit bac9f7eebf
No known key found for this signature in database
GPG Key ID: 4BDF1B40A49FDD23
57 changed files with 639 additions and 181 deletions

View File

@ -47,10 +47,10 @@ jobs:
BRANCH: ${{ github.head_ref }}
run: git checkout -B "$BRANCH"
- name: set up JDK 17
- name: set up JDK
uses: actions/setup-java@v4
with:
java-version: 17
java-version: 21
distribution: "temurin"
cache: 'gradle'
@ -88,10 +88,10 @@ jobs:
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
- name: set up JDK 17
- name: set up JDK
uses: actions/setup-java@v4
with:
java-version: 17
java-version: 21
distribution: "temurin"
cache: 'gradle'
@ -121,10 +121,10 @@ jobs:
with:
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
- name: Set up JDK 17
- name: Set up JDK
uses: actions/setup-java@v4
with:
java-version: 17
java-version: 21
distribution: "temurin"
cache: 'gradle'

View File

@ -10,6 +10,7 @@ plugins {
id "checkstyle"
id "org.sonarqube" version "4.0.0.2929"
id "org.jetbrains.kotlin.plugin.compose" version "${kotlin_version}"
id 'com.google.dagger.hilt.android'
}
android {
@ -94,6 +95,7 @@ android {
buildFeatures {
viewBinding true
compose true
buildConfig true
}
packagingOptions {
@ -114,7 +116,7 @@ ext {
androidxRoomVersion = '2.6.1'
androidxWorkVersion = '2.8.1'
icepickVersion = '3.2.0'
stateSaverVersion = '1.4.1'
exoPlayerVersion = '2.18.7'
googleAutoServiceVersion = '1.1.1'
groupieVersion = '2.10.1'
@ -190,6 +192,10 @@ sonar {
}
}
kapt {
correctErrorTypes true
}
dependencies {
/** Desugaring **/
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs_nio:2.0.4'
@ -200,7 +206,8 @@ dependencies {
// name and the commit hash with the commit hash of the (pushed) commit you want to test
// This works thanks to JitPack: https://jitpack.io/
implementation 'com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751'
implementation 'com.github.TeamNewPipe:NewPipeExtractor:v0.24.2'
// WORKAROUND: v0.24.2 can't be resolved by jitpack -> use git commit hash instead
implementation 'com.github.TeamNewPipe:NewPipeExtractor:176da72cb4c3ec4679211339b0e59f6b01bf2f52'
implementation 'com.github.TeamNewPipe:NoNonsense-FilePicker:5.0.0'
/** Checkstyle **/
@ -236,8 +243,9 @@ dependencies {
/** Third-party libraries **/
// Instance state boilerplate elimination
implementation "frankiesardo:icepick:${icepickVersion}"
kapt "frankiesardo:icepick-processor:${icepickVersion}"
implementation 'com.github.livefront:bridge:v2.0.2'
implementation "com.evernote:android-state:$stateSaverVersion"
kapt "com.evernote:android-state-processor:$stateSaverVersion"
// HTML parser
implementation "org.jsoup:jsoup:1.17.2"
@ -286,18 +294,28 @@ dependencies {
implementation "org.ocpsoft.prettytime:prettytime:5.0.8.Final"
// Jetpack Compose
implementation(platform('androidx.compose:compose-bom:2024.09.00'))
implementation(platform('androidx.compose:compose-bom:2024.09.03'))
implementation 'androidx.compose.material3:material3'
implementation 'androidx.compose.material3.adaptive:adaptive'
implementation 'androidx.activity:activity-compose'
implementation 'androidx.compose.ui:ui-tooling-preview'
implementation 'androidx.lifecycle:lifecycle-viewmodel-compose'
implementation 'androidx.compose.ui:ui-text' // Needed for parsing HTML to AnnotatedString
// Jetpack Compose related dependencies
implementation 'androidx.paging:paging-compose:3.3.2'
implementation 'com.github.nanihadesuka:LazyColumnScrollbar:2.2.0'
implementation "androidx.navigation:navigation-compose"
// Coroutines interop
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-rx3:1.8.1'
// Hilt
implementation("com.google.dagger:hilt-android:2.51.1")
kapt("com.google.dagger:hilt-compiler:2.51.1")
// Scroll
implementation 'com.github.nanihadesuka:LazyColumnScrollbar:2.2.0'
/** Debugging **/
// Memory leak detection
debugImplementation "com.squareup.leakcanary:leakcanary-object-watcher-android:${leakCanaryVersion}"

View File

@ -13,15 +13,6 @@
## Rules for ExoPlayer
-keep class com.google.android.exoplayer2.** { *; }
## Rules for Icepick. Copy pasted from https://github.com/frankiesardo/icepick
-dontwarn icepick.**
-keep class icepick.** { *; }
-keep class **$$Icepick { *; }
-keepclasseswithmembernames class * {
@icepick.* <fields>;
}
-keepnames class * { @icepick.State *;}
## Rules for OkHttp. Copy pasted from https://github.com/square/okhttp
-dontwarn okhttp3.**
-dontwarn okio.**

View File

@ -77,6 +77,11 @@
android:exported="false"
android:label="@string/settings" />
<activity
android:name=".settings.SettingsV2Activity"
android:exported="true"
android:label="@string/settings" />
<activity
android:name=".about.AboutActivity"
android:exported="false"

View File

@ -21,6 +21,7 @@ import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.downloader.Downloader;
import org.schabi.newpipe.ktx.ExceptionUtils;
import org.schabi.newpipe.settings.NewPipeSettings;
import org.schabi.newpipe.util.BridgeStateSaverInitializer;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.ServiceHelper;
import org.schabi.newpipe.util.StateSaver;
@ -36,6 +37,7 @@ import java.util.Objects;
import coil.ImageLoader;
import coil.ImageLoaderFactory;
import coil.util.DebugLogger;
import dagger.hilt.android.HiltAndroidApp;
import io.reactivex.rxjava3.exceptions.CompositeException;
import io.reactivex.rxjava3.exceptions.MissingBackpressureException;
import io.reactivex.rxjava3.exceptions.OnErrorNotImplementedException;
@ -61,6 +63,7 @@ import io.reactivex.rxjava3.plugins.RxJavaPlugins;
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
*/
@HiltAndroidApp
public class App extends Application implements ImageLoaderFactory {
public static final String PACKAGE_NAME = BuildConfig.APPLICATION_ID;
private static final String TAG = App.class.toString();
@ -105,6 +108,7 @@ public class App extends Application implements ImageLoaderFactory {
Localization.getPreferredContentCountry(this));
Localization.initPrettyTime(Localization.resolvePrettyTime(getApplicationContext()));
BridgeStateSaverInitializer.init(this);
StateSaver.init(this);
initNotificationChannels();

View File

@ -0,0 +1,22 @@
package org.schabi.newpipe
import android.content.Context
import android.content.SharedPreferences
import androidx.preference.PreferenceManager
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
class AppModule {
@Provides
@Singleton
fun providesSharedPreference(@ApplicationContext context: Context): SharedPreferences {
return PreferenceManager.getDefaultSharedPreferences(context)
}
}

View File

@ -10,8 +10,9 @@ import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import icepick.Icepick;
import icepick.State;
import com.evernote.android.state.State;
import com.livefront.bridge.Bridge;
public abstract class BaseFragment extends Fragment {
protected final String TAG = getClass().getSimpleName() + "@" + Integer.toHexString(hashCode());
@ -48,7 +49,7 @@ public abstract class BaseFragment extends Fragment {
+ "savedInstanceState = [" + savedInstanceState + "]");
}
super.onCreate(savedInstanceState);
Icepick.restoreInstanceState(this, savedInstanceState);
Bridge.restoreInstanceState(this, savedInstanceState);
if (savedInstanceState != null) {
onRestoreInstanceState(savedInstanceState);
}
@ -70,7 +71,7 @@ public abstract class BaseFragment extends Fragment {
@Override
public void onSaveInstanceState(@NonNull final Bundle outState) {
super.onSaveInstanceState(outState);
Icepick.saveInstanceState(this, outState);
Bridge.saveInstanceState(this, outState);
}
protected void onRestoreInstanceState(@NonNull final Bundle savedInstanceState) {

View File

@ -41,6 +41,9 @@ import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleOwner;
import androidx.preference.PreferenceManager;
import com.evernote.android.state.State;
import com.livefront.bridge.Bridge;
import org.schabi.newpipe.database.stream.model.StreamEntity;
import org.schabi.newpipe.databinding.ListRadioIconItemBinding;
import org.schabi.newpipe.databinding.SingleChoiceDialogViewBinding;
@ -98,8 +101,6 @@ import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
import icepick.Icepick;
import icepick.State;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.core.Single;
@ -152,7 +153,7 @@ public class RouterActivity extends AppCompatActivity {
getWindow().setAttributes(params);
super.onCreate(savedInstanceState);
Icepick.restoreInstanceState(this, savedInstanceState);
Bridge.restoreInstanceState(this, savedInstanceState);
// FragmentManager will take care to recreate (Playlist|Download)Dialog when screen rotates
// We used to .setOnDismissListener(dialog -> finish()); when creating these DialogFragments
@ -197,7 +198,7 @@ public class RouterActivity extends AppCompatActivity {
@Override
protected void onSaveInstanceState(@NonNull final Bundle outState) {
super.onSaveInstanceState(outState);
Icepick.saveInstanceState(this, outState);
Bridge.saveInstanceState(this, outState);
}
@Override

View File

@ -138,8 +138,12 @@ class AboutActivity : AppCompatActivity() {
"https://github.com/lisawray/groupie", StandardLicenses.MIT
),
SoftwareComponent(
"Icepick", "2015", "Frankie Sardo",
"https://github.com/frankiesardo/icepick", StandardLicenses.EPL1
"Android-State", "2018", "Evernote",
"https://github.com/Evernote/android-state", StandardLicenses.EPL1
),
SoftwareComponent(
"Bridge", "2021", "Livefront",
"https://github.com/livefront/bridge", StandardLicenses.APACHE2
),
SoftwareComponent(
"Jsoup", "2009 - 2020", "Jonathan Hedley",

View File

@ -39,6 +39,8 @@ import androidx.documentfile.provider.DocumentFile;
import androidx.fragment.app.DialogFragment;
import androidx.preference.PreferenceManager;
import com.evernote.android.state.State;
import com.livefront.bridge.Bridge;
import com.nononsenseapps.filepicker.Utils;
import org.schabi.newpipe.MainActivity;
@ -59,6 +61,8 @@ import org.schabi.newpipe.settings.NewPipeSettings;
import org.schabi.newpipe.streams.io.NoFileManagerSafeGuard;
import org.schabi.newpipe.streams.io.StoredDirectoryHelper;
import org.schabi.newpipe.streams.io.StoredFileHelper;
import org.schabi.newpipe.util.AudioTrackAdapter;
import org.schabi.newpipe.util.AudioTrackAdapter.AudioTracksWrapper;
import org.schabi.newpipe.util.FilePickerActivityHelper;
import org.schabi.newpipe.util.FilenameUtils;
import org.schabi.newpipe.util.ListHelper;
@ -67,8 +71,6 @@ import org.schabi.newpipe.util.SecondaryStreamHelper;
import org.schabi.newpipe.util.SimpleOnSeekBarChangeListener;
import org.schabi.newpipe.util.StreamItemAdapter;
import org.schabi.newpipe.util.StreamItemAdapter.StreamInfoWrapper;
import org.schabi.newpipe.util.AudioTrackAdapter;
import org.schabi.newpipe.util.AudioTrackAdapter.AudioTracksWrapper;
import org.schabi.newpipe.util.ThemeHelper;
import java.io.File;
@ -79,8 +81,6 @@ import java.util.Locale;
import java.util.Objects;
import java.util.Optional;
import icepick.Icepick;
import icepick.State;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
import us.shandian.giga.get.MissionRecoveryInfo;
import us.shandian.giga.postprocessing.Postprocessing;
@ -214,7 +214,7 @@ public class DownloadDialog extends DialogFragment
context = getContext();
setStyle(STYLE_NO_TITLE, ThemeHelper.getDialogTheme(context));
Icepick.restoreInstanceState(this, savedInstanceState);
Bridge.restoreInstanceState(this, savedInstanceState);
this.audioTrackAdapter = new AudioTrackAdapter(wrappedAudioTracks);
this.subtitleStreamsAdapter = new StreamItemAdapter<>(wrappedSubtitleStreams);
@ -372,7 +372,7 @@ public class DownloadDialog extends DialogFragment
@Override
public void onSaveInstanceState(@NonNull final Bundle outState) {
super.onSaveInstanceState(outState);
Icepick.saveInstanceState(this, outState);
Bridge.saveInstanceState(this, outState);
}

View File

@ -2,7 +2,6 @@ package org.schabi.newpipe.error;
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
@ -13,7 +12,6 @@ import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
@ -22,7 +20,6 @@ import androidx.core.content.IntentCompat;
import com.grack.nanojson.JsonWriter;
import org.schabi.newpipe.BuildConfig;
import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.R;
import org.schabi.newpipe.databinding.ActivityErrorBinding;
import org.schabi.newpipe.util.Localization;
@ -187,25 +184,6 @@ public class ErrorActivity extends AppCompatActivity {
.collect(Collectors.joining(separator + "\n", separator + "\n", separator));
}
/**
* Get the checked activity.
*
* @param returnActivity the activity to return to
* @return the casted return activity or null
*/
@Nullable
static Class<? extends Activity> getReturnActivity(final Class<?> returnActivity) {
Class<? extends Activity> checkedReturnActivity = null;
if (returnActivity != null) {
if (Activity.class.isAssignableFrom(returnActivity)) {
checkedReturnActivity = returnActivity.asSubclass(Activity.class);
} else {
checkedReturnActivity = MainActivity.class;
}
}
return checkedReturnActivity;
}
private void buildInfo(final ErrorInfo info) {
String text = "";

View File

@ -13,6 +13,8 @@ import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.fragment.app.Fragment;
import com.evernote.android.state.State;
import org.schabi.newpipe.BaseFragment;
import org.schabi.newpipe.R;
import org.schabi.newpipe.error.ErrorInfo;
@ -22,8 +24,6 @@ import org.schabi.newpipe.util.InfoCache;
import java.util.concurrent.atomic.AtomicBoolean;
import icepick.State;
public abstract class BaseStateFragment<I> extends BaseFragment implements ViewContract<I> {
@State
protected AtomicBoolean wasLoading = new AtomicBoolean();
@ -134,6 +134,7 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
hideErrorPanel();
}
@Override
public void showEmptyState() {
isLoading.set(false);
if (emptyStateView != null) {

View File

@ -11,6 +11,8 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import com.evernote.android.state.State;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.stream.Description;
@ -19,8 +21,6 @@ import org.schabi.newpipe.util.Localization;
import java.util.List;
import icepick.State;
public class DescriptionFragment extends BaseDescriptionFragment {
@State
@ -31,7 +31,7 @@ public class DescriptionFragment extends BaseDescriptionFragment {
}
public DescriptionFragment() {
// keep empty constructor for IcePick when resuming fragment from memory
// keep empty constructor for State when resuming fragment from memory
}

View File

@ -56,6 +56,7 @@ import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import androidx.preference.PreferenceManager;
import com.evernote.android.state.State;
import com.google.android.exoplayer2.PlaybackException;
import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.material.appbar.AppBarLayout;
@ -127,7 +128,6 @@ import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import coil.util.CoilUtils;
import icepick.State;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.rxjava3.disposables.Disposable;

View File

@ -9,6 +9,8 @@ import android.view.View;
import androidx.annotation.NonNull;
import com.evernote.android.state.State;
import org.schabi.newpipe.R;
import org.schabi.newpipe.error.ErrorInfo;
import org.schabi.newpipe.error.UserAction;
@ -24,7 +26,6 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import icepick.State;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.disposables.Disposable;
@ -143,7 +144,7 @@ public abstract class BaseListInfoFragment<I extends InfoItem, L extends ListInf
currentWorker = loadResult(forceLoad)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe((@NonNull L result) -> {
.subscribe((@NonNull final L result) -> {
isLoading.set(false);
currentInfo = result;
currentNextPage = result.getNextPage();

View File

@ -10,6 +10,8 @@ import android.widget.LinearLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.evernote.android.state.State;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.channel.ChannelInfo;
@ -20,8 +22,6 @@ import org.schabi.newpipe.util.Localization;
import java.util.List;
import icepick.State;
public class ChannelAboutFragment extends BaseDescriptionFragment {
@State
protected ChannelInfo channelInfo;
@ -31,7 +31,7 @@ public class ChannelAboutFragment extends BaseDescriptionFragment {
}
public ChannelAboutFragment() {
// keep empty constructor for IcePick when resuming fragment from memory
// keep empty constructor for State when resuming fragment from memory
}
@Override

View File

@ -25,6 +25,7 @@ import androidx.core.graphics.ColorUtils;
import androidx.core.view.MenuProvider;
import androidx.preference.PreferenceManager;
import com.evernote.android.state.State;
import com.google.android.material.snackbar.Snackbar;
import com.google.android.material.tabs.TabLayout;
import com.jakewharton.rxbinding4.view.RxView;
@ -60,7 +61,6 @@ import java.util.Queue;
import java.util.concurrent.TimeUnit;
import coil.util.CoilUtils;
import icepick.State;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
@ -249,7 +249,7 @@ public class ChannelFragment extends BaseStateFragment<ChannelInfo>
//////////////////////////////////////////////////////////////////////////*/
private void monitorSubscription(final ChannelInfo info) {
final Consumer<Throwable> onError = (Throwable throwable) -> {
final Consumer<Throwable> onError = (final Throwable throwable) -> {
animate(binding.channelSubscribeButton, false, 100);
showSnackBarError(new ErrorInfo(throwable, UserAction.SUBSCRIPTION_GET,
"Get subscription status", currentInfo));
@ -284,14 +284,14 @@ public class ChannelFragment extends BaseStateFragment<ChannelInfo>
}
private Function<Object, Object> mapOnSubscribe(final SubscriptionEntity subscription) {
return (@NonNull Object o) -> {
return (@NonNull final Object o) -> {
subscriptionManager.insertSubscription(subscription);
return o;
};
}
private Function<Object, Object> mapOnUnsubscribe(final SubscriptionEntity subscription) {
return (@NonNull Object o) -> {
return (@NonNull final Object o) -> {
subscriptionManager.deleteSubscription(subscription);
return o;
};
@ -318,7 +318,7 @@ public class ChannelFragment extends BaseStateFragment<ChannelInfo>
}
private Disposable monitorSubscribeButton(final Function<Object, Object> action) {
final Consumer<Object> onNext = (@NonNull Object o) -> {
final Consumer<Object> onNext = (@NonNull final Object o) -> {
if (DEBUG) {
Log.d(TAG, "Changed subscription status to this channel!");
}
@ -338,7 +338,7 @@ public class ChannelFragment extends BaseStateFragment<ChannelInfo>
}
private Consumer<List<SubscriptionEntity>> getSubscribeUpdateMonitor(final ChannelInfo info) {
return (List<SubscriptionEntity> subscriptionEntities) -> {
return (final List<SubscriptionEntity> subscriptionEntities) -> {
if (DEBUG) {
Log.d(TAG, "subscriptionManager.subscriptionTable.doOnNext() called with: "
+ "subscriptionEntities = [" + subscriptionEntities + "]");

View File

@ -9,6 +9,8 @@ import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.evernote.android.state.State;
import org.schabi.newpipe.R;
import org.schabi.newpipe.databinding.PlaylistControlBinding;
import org.schabi.newpipe.error.UserAction;
@ -32,13 +34,12 @@ import java.util.List;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import icepick.State;
import io.reactivex.rxjava3.core.Single;
public class ChannelTabFragment extends BaseListInfoFragment<InfoItem, ChannelTabInfo>
implements PlaylistControlViewHolder {
// states must be protected and not private for IcePick being able to access them
// states must be protected and not private for State being able to access them
@State
protected ListLinkHandler tabHandler;
@State
@ -156,6 +157,7 @@ public class ChannelTabFragment extends BaseListInfoFragment<InfoItem, ChannelTa
}
}
@Override
public PlayQueue getPlayQueue() {
final List<StreamInfoItem> streamItems = infoListAdapter.getItemsList().stream()
.filter(StreamInfoItem.class::isInstance)

View File

@ -11,6 +11,8 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import com.evernote.android.state.State;
import org.schabi.newpipe.R;
import org.schabi.newpipe.error.ErrorInfo;
import org.schabi.newpipe.error.UserAction;
@ -29,7 +31,6 @@ import org.schabi.newpipe.util.ExtractorHelper;
import org.schabi.newpipe.util.KioskTranslator;
import org.schabi.newpipe.util.Localization;
import icepick.State;
import io.reactivex.rxjava3.core.Single;
/**

View File

@ -40,6 +40,8 @@ import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.RecyclerView;
import com.evernote.android.state.State;
import org.schabi.newpipe.R;
import org.schabi.newpipe.databinding.FragmentSearchBinding;
import org.schabi.newpipe.error.ErrorInfo;
@ -77,7 +79,6 @@ import java.util.Queue;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import icepick.State;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.core.Single;
@ -550,7 +551,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
}
});
searchEditText.setOnFocusChangeListener((View v, boolean hasFocus) -> {
searchEditText.setOnFocusChangeListener((final View v, final boolean hasFocus) -> {
if (DEBUG) {
Log.d(TAG, "onFocusChange() called with: "
+ "v = [" + v + "], hasFocus = [" + hasFocus + "]");
@ -611,7 +612,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
};
searchEditText.addTextChangedListener(textWatcher);
searchEditText.setOnEditorActionListener(
(TextView v, int actionId, KeyEvent event) -> {
(final TextView v, final int actionId, final KeyEvent event) -> {
if (DEBUG) {
Log.d(TAG, "onEditorAction() called with: v = [" + v + "], "
+ "actionId = [" + actionId + "], event = [" + event + "]");

View File

@ -19,6 +19,8 @@ import androidx.fragment.app.FragmentManager;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.RecyclerView;
import com.evernote.android.state.State;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
import org.schabi.newpipe.NewPipeDatabase;
@ -36,16 +38,15 @@ import org.schabi.newpipe.local.holder.LocalBookmarkPlaylistItemHolder;
import org.schabi.newpipe.local.holder.RemoteBookmarkPlaylistItemHolder;
import org.schabi.newpipe.local.playlist.LocalPlaylistManager;
import org.schabi.newpipe.local.playlist.RemotePlaylistManager;
import org.schabi.newpipe.util.debounce.DebounceSavable;
import org.schabi.newpipe.util.debounce.DebounceSaver;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.OnClickGesture;
import org.schabi.newpipe.util.debounce.DebounceSavable;
import org.schabi.newpipe.util.debounce.DebounceSaver;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import icepick.State;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.rxjava3.disposables.Disposable;

View File

@ -44,11 +44,11 @@ import androidx.lifecycle.ViewModelProvider
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.evernote.android.state.State
import com.xwray.groupie.GroupieAdapter
import com.xwray.groupie.Item
import com.xwray.groupie.OnItemClickListener
import com.xwray.groupie.OnItemLongClickListener
import icepick.State
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.core.Single
import io.reactivex.rxjava3.disposables.CompositeDisposable

View File

@ -15,6 +15,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.viewbinding.ViewBinding;
import com.evernote.android.state.State;
import com.google.android.material.snackbar.Snackbar;
import org.reactivestreams.Subscriber;
@ -45,7 +46,6 @@ import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import icepick.State;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.rxjava3.disposables.Disposable;
@ -368,6 +368,7 @@ public class StatisticsPlaylistFragment
}
}
@Override
public PlayQueue getPlayQueue() {
return getPlayQueue(0);
}

View File

@ -26,6 +26,8 @@ import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.RecyclerView;
import androidx.viewbinding.ViewBinding;
import com.evernote.android.state.State;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
import org.schabi.newpipe.NewPipeDatabase;
@ -49,12 +51,12 @@ import org.schabi.newpipe.local.BaseLocalListFragment;
import org.schabi.newpipe.local.history.HistoryRecordManager;
import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
import org.schabi.newpipe.util.debounce.DebounceSavable;
import org.schabi.newpipe.util.debounce.DebounceSaver;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.OnClickGesture;
import org.schabi.newpipe.util.PlayButtonHelper;
import org.schabi.newpipe.util.debounce.DebounceSavable;
import org.schabi.newpipe.util.debounce.DebounceSaver;
import org.schabi.newpipe.util.external_communication.ShareUtils;
import java.util.ArrayList;
@ -63,7 +65,6 @@ import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
import icepick.State;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
@ -843,6 +844,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
}
}
@Override
public PlayQueue getPlayQueue() {
return getPlayQueue(0);
}

View File

@ -1,5 +1,7 @@
package org.schabi.newpipe.local.subscription;
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
import android.app.Dialog;
import android.content.Intent;
import android.os.Bundle;
@ -10,13 +12,11 @@ import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.Fragment;
import com.evernote.android.state.State;
import com.livefront.bridge.Bridge;
import org.schabi.newpipe.R;
import icepick.Icepick;
import icepick.State;
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
public class ImportConfirmationDialog extends DialogFragment {
@State
protected Intent resultServiceIntent;
@ -57,12 +57,12 @@ public class ImportConfirmationDialog extends DialogFragment {
throw new IllegalStateException("Result intent is null");
}
Icepick.restoreInstanceState(this, savedInstanceState);
Bridge.restoreInstanceState(this, savedInstanceState);
}
@Override
public void onSaveInstanceState(@NonNull final Bundle outState) {
super.onSaveInstanceState(outState);
Icepick.saveInstanceState(this, outState);
Bridge.saveInstanceState(this, outState);
}
}

View File

@ -20,11 +20,11 @@ import androidx.annotation.StringRes
import androidx.appcompat.app.AlertDialog
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.GridLayoutManager
import com.evernote.android.state.State
import com.xwray.groupie.Group
import com.xwray.groupie.GroupAdapter
import com.xwray.groupie.Section
import com.xwray.groupie.viewbinding.GroupieViewHolder
import icepick.State
import io.reactivex.rxjava3.disposables.CompositeDisposable
import org.schabi.newpipe.R
import org.schabi.newpipe.database.feed.model.FeedGroupEntity.Companion.GROUP_ALL_ID

View File

@ -27,6 +27,8 @@ import androidx.annotation.StringRes;
import androidx.appcompat.app.ActionBar;
import androidx.core.text.util.LinkifyCompat;
import com.evernote.android.state.State;
import org.schabi.newpipe.BaseFragment;
import org.schabi.newpipe.R;
import org.schabi.newpipe.error.ErrorInfo;
@ -44,8 +46,6 @@ import org.schabi.newpipe.util.ServiceHelper;
import java.util.Collections;
import java.util.List;
import icepick.State;
public class SubscriptionsImportFragment extends BaseFragment {
@State
int currentServiceId = Constants.NO_SERVICE_ID;

View File

@ -18,11 +18,11 @@ import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.evernote.android.state.State
import com.livefront.bridge.Bridge
import com.xwray.groupie.GroupieAdapter
import com.xwray.groupie.OnItemClickListener
import com.xwray.groupie.Section
import icepick.Icepick
import icepick.State
import org.schabi.newpipe.R
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
import org.schabi.newpipe.databinding.DialogFeedGroupCreateBinding
@ -78,7 +78,7 @@ class FeedGroupDialog : DialogFragment(), BackPressable {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Icepick.restoreInstanceState(this, savedInstanceState)
Bridge.restoreInstanceState(this, savedInstanceState)
setStyle(STYLE_NO_TITLE, ThemeHelper.getMinWidthDialogTheme(requireContext()))
groupId = arguments?.getLong(KEY_GROUP_ID, NO_GROUP_SELECTED) ?: NO_GROUP_SELECTED
@ -115,7 +115,7 @@ class FeedGroupDialog : DialogFragment(), BackPressable {
iconsListState = feedGroupCreateBinding.iconSelector.layoutManager?.onSaveInstanceState()
subscriptionsListState = feedGroupCreateBinding.subscriptionsSelectorList.layoutManager?.onSaveInstanceState()
Icepick.saveInstanceState(this, outState)
Bridge.saveInstanceState(this, outState)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {

View File

@ -11,10 +11,10 @@ import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.ItemTouchHelper.SimpleCallback
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.evernote.android.state.State
import com.livefront.bridge.Bridge
import com.xwray.groupie.GroupieAdapter
import com.xwray.groupie.TouchCallback
import icepick.Icepick
import icepick.State
import org.schabi.newpipe.R
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
import org.schabi.newpipe.databinding.DialogFeedGroupReorderBinding
@ -23,10 +23,6 @@ import org.schabi.newpipe.local.subscription.dialog.FeedGroupReorderDialogViewMo
import org.schabi.newpipe.local.subscription.item.FeedGroupReorderItem
import org.schabi.newpipe.util.ThemeHelper
import java.util.Collections
import kotlin.collections.ArrayList
import kotlin.collections.List
import kotlin.collections.map
import kotlin.collections.sortedBy
class FeedGroupReorderDialog : DialogFragment() {
private var _binding: DialogFeedGroupReorderBinding? = null
@ -42,7 +38,7 @@ class FeedGroupReorderDialog : DialogFragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Icepick.restoreInstanceState(this, savedInstanceState)
Bridge.restoreInstanceState(this, savedInstanceState)
setStyle(STYLE_NO_TITLE, ThemeHelper.getMinWidthDialogTheme(requireContext()))
}
@ -80,7 +76,7 @@ class FeedGroupReorderDialog : DialogFragment() {
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
Icepick.saveInstanceState(this, outState)
Bridge.saveInstanceState(this, outState)
}
private fun handleGroups(list: List<FeedGroupEntity>) {

View File

@ -24,6 +24,9 @@ import androidx.core.math.MathUtils;
import androidx.fragment.app.DialogFragment;
import androidx.preference.PreferenceManager;
import com.evernote.android.state.State;
import com.livefront.bridge.Bridge;
import org.schabi.newpipe.R;
import org.schabi.newpipe.databinding.DialogPlaybackParameterBinding;
import org.schabi.newpipe.player.ui.VideoPlayerUi;
@ -37,9 +40,6 @@ import java.util.function.DoubleConsumer;
import java.util.function.DoubleFunction;
import java.util.function.DoubleSupplier;
import icepick.Icepick;
import icepick.State;
public class PlaybackParameterDialog extends DialogFragment {
private static final String TAG = "PlaybackParameterDialog";
@ -135,7 +135,7 @@ public class PlaybackParameterDialog extends DialogFragment {
@Override
public void onSaveInstanceState(@NonNull final Bundle outState) {
super.onSaveInstanceState(outState);
Icepick.saveInstanceState(this, outState);
Bridge.saveInstanceState(this, outState);
}
/*//////////////////////////////////////////////////////////////////////////
@ -146,7 +146,7 @@ public class PlaybackParameterDialog extends DialogFragment {
@Override
public Dialog onCreateDialog(@Nullable final Bundle savedInstanceState) {
assureCorrectAppLanguage(getContext());
Icepick.restoreInstanceState(this, savedInstanceState);
Bridge.restoreInstanceState(this, savedInstanceState);
binding = DialogPlaybackParameterBinding.inflate(getLayoutInflater());
initUI();

View File

@ -0,0 +1,27 @@
package org.schabi.newpipe.settings
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import org.schabi.newpipe.R
import org.schabi.newpipe.settings.viewmodel.SettingsViewModel
import org.schabi.newpipe.ui.SwitchPreference
import org.schabi.newpipe.ui.theme.SizeTokens
@Composable
fun DebugScreen(viewModel: SettingsViewModel, modifier: Modifier = Modifier) {
val settingsLayoutRedesign by viewModel.settingsLayoutRedesign.collectAsState()
Column(modifier = modifier) {
SwitchPreference(
modifier = Modifier.padding(SizeTokens.SpacingExtraSmall),
R.string.settings_layout_redesign,
settingsLayoutRedesign,
viewModel::toggleSettingsLayoutRedesign
)
}
}

View File

@ -21,7 +21,9 @@ import androidx.fragment.app.FragmentManager;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
import com.evernote.android.state.State;
import com.jakewharton.rxbinding4.widget.RxTextView;
import com.livefront.bridge.Bridge;
import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.R;
@ -41,9 +43,6 @@ import org.schabi.newpipe.views.FocusOverlayView;
import java.util.concurrent.TimeUnit;
import icepick.Icepick;
import icepick.State;
/*
* Created by Christian Schabesberger on 31.08.15.
*
@ -93,7 +92,7 @@ public class SettingsActivity extends AppCompatActivity implements
assureCorrectAppLanguage(this);
super.onCreate(savedInstanceBundle);
Icepick.restoreInstanceState(this, savedInstanceBundle);
Bridge.restoreInstanceState(this, savedInstanceBundle);
final boolean restored = savedInstanceBundle != null;
final SettingsLayoutBinding settingsLayoutBinding =
@ -125,7 +124,7 @@ public class SettingsActivity extends AppCompatActivity implements
@Override
protected void onSaveInstanceState(@NonNull final Bundle outState) {
super.onSaveInstanceState(outState);
Icepick.saveInstanceState(this, outState);
Bridge.saveInstanceState(this, outState);
}
@Override

View File

@ -0,0 +1,23 @@
package org.schabi.newpipe.settings
import androidx.compose.foundation.layout.Column
import androidx.compose.material3.HorizontalDivider
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import org.schabi.newpipe.R
import org.schabi.newpipe.ui.TextPreference
@Composable
fun SettingsScreen(
onSelectSettingOption: (SettingsScreenKey) -> Unit,
modifier: Modifier = Modifier
) {
Column(modifier = modifier) {
TextPreference(
title = R.string.settings_category_debug_title,
onClick = { onSelectSettingOption(SettingsScreenKey.DEBUG) }
)
HorizontalDivider(color = Color.Black)
}
}

View File

@ -0,0 +1,85 @@
package org.schabi.newpipe.settings
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.annotation.StringRes
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
import dagger.hilt.android.AndroidEntryPoint
import org.schabi.newpipe.R
import org.schabi.newpipe.settings.viewmodel.SettingsViewModel
import org.schabi.newpipe.ui.Toolbar
import org.schabi.newpipe.ui.theme.AppTheme
const val SCREEN_TITLE_KEY = "SCREEN_TITLE_KEY"
@AndroidEntryPoint
class SettingsV2Activity : ComponentActivity() {
private val settingsViewModel: SettingsViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val navController = rememberNavController()
var screenTitle by remember { mutableIntStateOf(SettingsScreenKey.ROOT.screenTitle) }
navController.addOnDestinationChangedListener { _, _, arguments ->
screenTitle =
arguments?.getInt(SCREEN_TITLE_KEY) ?: SettingsScreenKey.ROOT.screenTitle
}
AppTheme {
Scaffold(topBar = {
Toolbar(
title = stringResource(id = screenTitle),
hasSearch = true,
onSearchQueryChange = null // TODO: Add suggestions logic
)
}) { padding ->
NavHost(
navController = navController,
startDestination = SettingsScreenKey.ROOT.name,
modifier = Modifier.padding(padding)
) {
composable(
SettingsScreenKey.ROOT.name,
listOf(createScreenTitleArg(SettingsScreenKey.ROOT.screenTitle))
) {
SettingsScreen(onSelectSettingOption = { screen ->
navController.navigate(screen.name)
})
}
composable(
SettingsScreenKey.DEBUG.name,
listOf(createScreenTitleArg(SettingsScreenKey.DEBUG.screenTitle))
) {
DebugScreen(settingsViewModel)
}
}
}
}
}
}
}
fun createScreenTitleArg(@StringRes screenTitle: Int) = navArgument(SCREEN_TITLE_KEY) {
defaultValue = screenTitle
}
enum class SettingsScreenKey(@StringRes val screenTitle: Int) {
ROOT(R.string.settings),
DEBUG(R.string.settings_category_debug_title)
}

View File

@ -0,0 +1,39 @@
package org.schabi.newpipe.settings.viewmodel
import android.app.Application
import android.content.Context
import android.content.SharedPreferences
import androidx.core.content.ContextCompat
import androidx.lifecycle.AndroidViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import org.schabi.newpipe.R
import javax.inject.Inject
@HiltViewModel
class SettingsViewModel @Inject constructor(
@ApplicationContext context: Context,
private val preferenceManager: SharedPreferences
) : AndroidViewModel(context.applicationContext as Application) {
private var _settingsLayoutRedesignPref: Boolean
get() = preferenceManager.getBoolean(
ContextCompat.getString(getApplication(), R.string.settings_layout_redesign_key), false
)
set(value) {
preferenceManager.edit().putBoolean(
ContextCompat.getString(getApplication(), R.string.settings_layout_redesign_key),
value
).apply()
}
private val _settingsLayoutRedesign: MutableStateFlow<Boolean> =
MutableStateFlow(_settingsLayoutRedesignPref)
val settingsLayoutRedesign = _settingsLayoutRedesign.asStateFlow()
fun toggleSettingsLayoutRedesign(newState: Boolean) {
_settingsLayoutRedesign.value = newState
_settingsLayoutRedesignPref = newState
}
}

View File

@ -0,0 +1,53 @@
package org.schabi.newpipe.ui
import androidx.annotation.StringRes
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import org.schabi.newpipe.ui.theme.SizeTokens
@Composable
fun SwitchPreference(
modifier: Modifier = Modifier,
@StringRes title: Int,
isChecked: Boolean,
onCheckedChange: (Boolean) -> Unit,
@StringRes summary: Int? = null
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
modifier = modifier.fillMaxWidth()
) {
Column {
Text(
text = stringResource(id = title),
modifier = Modifier.padding(SizeTokens.SpacingExtraSmall),
style = MaterialTheme.typography.titleSmall,
textAlign = TextAlign.Start,
)
summary?.let {
Text(
text = stringResource(id = summary),
modifier = Modifier.padding(SizeTokens.SpacingExtraSmall),
style = MaterialTheme.typography.bodySmall,
textAlign = TextAlign.Start,
)
}
}
Spacer(modifier = Modifier.width(SizeTokens.SpacingSmall))
Switch(checked = isChecked, onCheckedChange = onCheckedChange)
}
}

View File

@ -0,0 +1,66 @@
package org.schabi.newpipe.ui
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import org.schabi.newpipe.ui.theme.SizeTokens
@Composable
fun TextPreference(
modifier: Modifier = Modifier,
@StringRes title: Int,
@DrawableRes icon: Int? = null,
@StringRes summary: Int? = null,
onClick: () -> Unit,
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
modifier = modifier
.fillMaxWidth()
.padding(SizeTokens.SpacingSmall)
.defaultMinSize(minHeight = SizeTokens.SpaceMinSize)
.clickable { onClick() }
) {
icon?.let {
Icon(
painter = painterResource(id = icon),
contentDescription = "icon for $title preference"
)
Spacer(modifier = Modifier.width(SizeTokens.SpacingSmall))
}
Column {
Text(
text = stringResource(id = title),
modifier = Modifier.padding(SizeTokens.SpacingExtraSmall),
style = MaterialTheme.typography.titleSmall,
textAlign = TextAlign.Start,
)
summary?.let {
Text(
text = stringResource(id = summary),
modifier = Modifier.padding(SizeTokens.SpacingExtraSmall),
style = MaterialTheme.typography.bodySmall,
textAlign = TextAlign.Start,
)
}
}
}
}

View File

@ -0,0 +1,61 @@
package org.schabi.newpipe.util;
import android.content.Context;
import android.os.Bundle;
import android.os.Parcelable;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.evernote.android.state.StateSaver;
import com.livefront.bridge.Bridge;
import com.livefront.bridge.SavedStateHandler;
import com.livefront.bridge.ViewSavedStateHandler;
/**
* Configures Bridge's state saver.
*/
public final class BridgeStateSaverInitializer {
public static void init(final Context context) {
Bridge.initialize(
context,
new SavedStateHandler() {
@Override
public void saveInstanceState(
@NonNull final Object target,
@NonNull final Bundle state) {
StateSaver.saveInstanceState(target, state);
}
@Override
public void restoreInstanceState(
@NonNull final Object target,
@Nullable final Bundle state) {
StateSaver.restoreInstanceState(target, state);
}
},
new ViewSavedStateHandler() {
@NonNull
@Override
public <T extends View> Parcelable saveInstanceState(
@NonNull final T target,
@Nullable final Parcelable parentState) {
return StateSaver.saveInstanceState(target, parentState);
}
@Nullable
@Override
public <T extends View> Parcelable restoreInstanceState(
@NonNull final T target,
@Nullable final Parcelable state) {
return StateSaver.restoreInstanceState(target, state);
}
}
);
}
private BridgeStateSaverInitializer() {
}
}

View File

@ -21,6 +21,7 @@ import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.preference.PreferenceManager;
import com.jakewharton.processphoenix.ProcessPhoenix;
@ -64,6 +65,7 @@ import org.schabi.newpipe.player.helper.PlayerHolder;
import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
import org.schabi.newpipe.settings.SettingsActivity;
import org.schabi.newpipe.settings.SettingsV2Activity;
import org.schabi.newpipe.util.external_communication.ShareUtils;
import java.util.List;
@ -640,7 +642,13 @@ public final class NavigationHelper {
}
public static void openSettings(final Context context) {
final Intent intent = new Intent(context, SettingsActivity.class);
final Class<?> settingsClass = PreferenceManager.getDefaultSharedPreferences(context)
.getBoolean(
ContextCompat.getString(context, R.string.settings_layout_redesign_key),
false
) ? SettingsV2Activity.class : SettingsActivity.class;
final Intent intent = new Intent(context, settingsClass);
context.startActivity(intent);
}

View File

@ -19,6 +19,9 @@
package org.schabi.newpipe.views;
import static org.schabi.newpipe.MainActivity.DEBUG;
import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.animation.ValueAnimator;
import android.content.Context;
import android.os.Parcelable;
@ -29,18 +32,15 @@ import android.widget.LinearLayout;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import com.evernote.android.state.State;
import com.livefront.bridge.Bridge;
import org.schabi.newpipe.ktx.ViewUtils;
import java.lang.annotation.Retention;
import java.util.ArrayList;
import java.util.List;
import icepick.Icepick;
import icepick.State;
import static java.lang.annotation.RetentionPolicy.SOURCE;
import static org.schabi.newpipe.MainActivity.DEBUG;
/**
* A view that can be fully collapsed and expanded.
*/
@ -207,12 +207,12 @@ public class CollapsibleView extends LinearLayout {
@Nullable
@Override
public Parcelable onSaveInstanceState() {
return Icepick.saveInstanceState(this, super.onSaveInstanceState());
return Bridge.saveInstanceState(this, super.onSaveInstanceState());
}
@Override
public void onRestoreInstanceState(final Parcelable state) {
super.onRestoreInstanceState(Icepick.restoreInstanceState(this, state));
super.onRestoreInstanceState(Bridge.restoreInstanceState(this, state));
ready();
}

View File

@ -1 +0,0 @@
../layout/list_stream_item.xml

View File

@ -0,0 +1,94 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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:id="@+id/itemRoot"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:padding="@dimen/video_item_search_padding">
<ImageView
android:id="@+id/itemThumbnailView"
android:layout_width="@dimen/video_item_search_thumbnail_image_width"
android:layout_height="@dimen/video_item_search_thumbnail_image_height"
android:scaleType="fitCenter"
android:src="@drawable/placeholder_thumbnail_video"
app:layout_constraintBottom_toTopOf="@+id/itemProgressView"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<org.schabi.newpipe.views.NewPipeTextView
android:id="@+id/itemDurationView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/video_item_search_duration_margin"
android:layout_marginBottom="@dimen/video_item_search_duration_margin"
android:background="@color/duration_background_color"
android:paddingHorizontal="@dimen/video_item_search_duration_horizontal_padding"
android:paddingVertical="@dimen/video_item_search_duration_vertical_padding"
android:textAllCaps="true"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="@color/duration_text_color"
android:textSize="@dimen/video_item_search_duration_text_size"
app:layout_constraintBottom_toBottomOf="@id/itemThumbnailView"
app:layout_constraintRight_toRightOf="@id/itemThumbnailView"
tools:text="1:09:10" />
<org.schabi.newpipe.views.NewPipeTextView
android:id="@+id/itemVideoTitleView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/video_item_search_image_right_margin"
android:ellipsize="end"
android:maxLines="2"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textSize="@dimen/video_item_search_title_text_size"
app:layout_constraintBottom_toTopOf="@+id/itemUploaderView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/itemThumbnailView"
app:layout_constraintTop_toTopOf="parent"
tools:text="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blanditLorem ipsumLorem ipsumLorem ipsumLorem ipsumLorem ipsumLorem ipsumLorem ipsum" />
<org.schabi.newpipe.views.NewPipeTextView
android:id="@+id/itemUploaderView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ellipsize="end"
android:lines="1"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textSize="@dimen/video_item_search_uploader_text_size"
app:layout_constraintBottom_toTopOf="@+id/itemAdditionalDetails"
app:layout_constraintEnd_toEndOf="@+id/itemVideoTitleView"
app:layout_constraintStart_toStartOf="@+id/itemVideoTitleView"
app:layout_constraintTop_toBottomOf="@+id/itemVideoTitleView"
tools:text="Uploader" />
<org.schabi.newpipe.views.NewPipeTextView
android:id="@+id/itemAdditionalDetails"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ellipsize="end"
android:lines="1"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textSize="@dimen/video_item_search_upload_date_text_size"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@+id/itemVideoTitleView"
app:layout_constraintStart_toStartOf="@+id/itemVideoTitleView"
app:layout_constraintTop_toBottomOf="@+id/itemUploaderView"
tools:text="2 years ago • 10M views" />
<org.schabi.newpipe.views.AnimatedProgressBar
android:id="@+id/itemProgressView"
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
android:layout_width="0dp"
android:layout_height="4dp"
android:progressDrawable="?progress_horizontal_drawable"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@+id/itemThumbnailView"
app:layout_constraintStart_toStartOf="@+id/itemThumbnailView"
app:layout_constraintTop_toBottomOf="@+id/itemThumbnailView" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -246,6 +246,7 @@
<string name="crash_the_app_key">crash_the_app_key</string>
<string name="show_error_snackbar_key">show_error_snackbar_key</string>
<string name="create_error_notification_key">create_error_notification_key</string>
<string name="settings_layout_redesign_key">settings_layout_redesign_key</string>
<!-- THEMES -->
<string name="theme_key">theme</string>

View File

@ -492,6 +492,7 @@
<string name="crash_the_app">Crash the app</string>
<string name="show_error_snackbar">Show an error snackbar</string>
<string name="create_error_notification">Create an error notification</string>
<string name="settings_layout_redesign">Enable the Redesigned Settings page</string>
<!-- Subscriptions import/export -->
<string name="import_title">Import</string>
<string name="import_from">Import from</string>

View File

@ -64,4 +64,11 @@
android:title="@string/create_error_notification"
app:singleLineTitle="false"
app:iconSpaceReserved="false" />
<SwitchPreferenceCompat
android:defaultValue="false"
android:key="@string/settings_layout_redesign_key"
android:title="@string/settings_layout_redesign"
app:iconSpaceReserved="false"
app:singleLineTitle="false" />
</PreferenceScreen>

View File

@ -1,35 +0,0 @@
package org.schabi.newpipe.error;
import android.app.Activity;
import org.junit.Test;
import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.RouterActivity;
import org.schabi.newpipe.fragments.detail.VideoDetailFragment;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
/**
* Unit tests for {@link ErrorActivity}.
*/
public class ErrorActivityTest {
@Test
public void getReturnActivity() {
Class<? extends Activity> returnActivity;
returnActivity = ErrorActivity.getReturnActivity(MainActivity.class);
assertEquals(MainActivity.class, returnActivity);
returnActivity = ErrorActivity.getReturnActivity(RouterActivity.class);
assertEquals(RouterActivity.class, returnActivity);
returnActivity = ErrorActivity.getReturnActivity(null);
assertNull(returnActivity);
returnActivity = ErrorActivity.getReturnActivity(Integer.class);
assertEquals(MainActivity.class, returnActivity);
returnActivity = ErrorActivity.getReturnActivity(VideoDetailFragment.class);
assertEquals(MainActivity.class, returnActivity);
}
}

View File

@ -1,14 +1,15 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = '2.0.0'
ext.kotlin_version = '2.0.21'
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:8.2.0'
classpath 'com.android.tools.build:gradle:8.7.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.google.dagger:hilt-android-gradle-plugin:2.51.1'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files

View File

@ -106,7 +106,7 @@ NewPipe এ আপুনি ব্যৱহাৰ কৰা সেৱাৰ অ
## অৱদান
আপোনাৰ ধাৰণা, অনুবাদ, ডিজাইন পৰিবৰ্তন, ক'ড পৰিষ্কাৰ কৰা, বা আনকি ডাঙৰ ক'ড পৰিৱৰ্তন হওক, সহায় সদায় আদৰণীয়। প্ৰতিটো অৱদানৰ লগে লগে এপটো ভাল হৈ পৰে, যিমানেই ডাঙৰ বা সৰু নহওক কিয়! যদি আপুনি জড়িত হ'ব বিচাৰে তেন্তে চাওক আমাৰ [অবদানৰ টোকা সমূহ](.github/CONTRIBUTING.md).<a href="https://hosted.weblate.org/engage/newpipe/"><img src="https://hosted.weblate.org/widgets/newpipe/-/287x66-grey.png" alt="Translation status" /></a>
আপোনাৰ ধাৰণা, অনুবাদ, ডিজাইন পৰিবৰ্তন, ক'ড পৰিষ্কাৰ কৰা, বা আনকি ডাঙৰ ক'ড পৰিৱৰ্তন হওক, সহায় সদায় আদৰণীয়। প্ৰতিটো অৱদানৰ লগে লগে এপটো ভাল হৈ পৰে, যিমানেই ডাঙৰ বা সৰু নহওক কিয়! যদি আপুনি জড়িত হ'ব বিচাৰে তেন্তে চাওক আমাৰ [অবদানৰ টোকা সমূহ](/.github/CONTRIBUTING.md).<a href="https://hosted.weblate.org/engage/newpipe/"><img src="https://hosted.weblate.org/widgets/newpipe/-/287x66-grey.png" alt="Translation status" /></a>
## অনুদান

View File

@ -126,7 +126,7 @@ So eine Aktion wird nicht unterstützt und du solltest sie nur in Erwägung zieh
## Beitrag
Egal ob du neue Ideen, Übersetzungen, Designvorschläge, kleine Code-Bereinigungen, oder sogar große Code-Verbesserungen hast, jegliche Unterstützung ist immer gern gesehen.
Die App wird mit _jedem_ Beitrag besser und besser - egal wie viel Arbeit in ihn gesteckt wird!
Wenn du dich einbringen willst, sehe dir die [Beitragshinweise](.github/CONTRIBUTING.md) an.
Wenn du dich einbringen willst, sieh dir die [Beitragshinweise](/.github/CONTRIBUTING.md) an.
<a href="https://hosted.weblate.org/engage/newpipe/de/">
<img src="https://hosted.weblate.org/widgets/newpipe/de/287x66-grey.png" alt="Übersetzt" />

View File

@ -109,7 +109,7 @@ Entre temps, si vous voulez changer de source pour une raison quelconque (par ex
## Contribuer
Que vous ayez des idées, des traductions, des changements de design, du nettoyage de code, ou encore un changement de code majeur, toute aide est la bienvenue. L'app s'améliore un peu plus à chaque contribution, peu importe qu'elle soit grosse ou petite ! Si vous aimeriez être impliqué, jetez un coup d'oeil à nos [notes pour contribuer](.github/CONTRIBUTING.md).
Que vous ayez des idées, des traductions, des changements de design, du nettoyage de code, ou encore un changement de code majeur, toute aide est la bienvenue. L'app s'améliore un peu plus à chaque contribution, peu importe qu'elle soit grosse ou petite ! Si vous aimeriez être impliqué, jetez un coup d'oeil à nos [notes pour contribuer](/.github/CONTRIBUTING.md).
<a href="https://hosted.weblate.org/engage/newpipe/">
<img src="https://hosted.weblate.org/widgets/newpipe/-/287x66-grey.png" alt="Translation status" />

View File

@ -105,7 +105,7 @@ NewPipe पर कई सेवाएँ उपलब्ध हैं। हम
चाहे आप अपने विचार जोड़ना चाहे, या अनुवाद, डिज़ाइन में बदलाव, कोड में सफ़ाई, या कोड में भारी बदलाव, सहायता ज़रूर करें।
जितने योगदान हो, ऐप उतनी ही बेहतर होती जाती है!
अगर आप योगदान करना चाहते हैं, हमारे [योगदान के दिशानिर्देश](.github/CONTRIBUTING.md) देखें।
अगर आप योगदान करना चाहते हैं, हमारे [योगदान के दिशानिर्देश](/.github/CONTRIBUTING.md) देखें।
<a href="https://hosted.weblate.org/engage/newpipe/">
<img src="https://hosted.weblate.org/widgets/newpipe/-/287x66-grey.png" alt="अनुवाद की स्थिति" />

View File

@ -107,7 +107,7 @@ Nel frattempo, se vuoi cambiare fonte per la stessa ragione (ad es. la funzional
## Contribuire
Se hai idee, traduzioni, cambiamenti di *design*, pulizia di codice, o addirittura grossi cambiamenti di codice, l'aiuto è sempre apprezzato. L'app diventa sempre meglio con ogni contribuzione, non importa quanto grande o piccola essa sia! Se ti piacerebbe essere parte del progetto, vedi le nostre [note di contribuzione](.github/CONTRIBUTING.md).
Se hai idee, traduzioni, cambiamenti di *design*, pulizia di codice, o addirittura grossi cambiamenti di codice, l'aiuto è sempre apprezzato. L'app diventa sempre meglio con ogni contribuzione, non importa quanto grande o piccola essa sia! Se ti piacerebbe essere parte del progetto, vedi le nostre [note di contribuzione](/.github/CONTRIBUTING.md).
<a href="https://hosted.weblate.org/engage/newpipe/">
<img src="https://hosted.weblate.org/widgets/newpipe/-/287x66-grey.png" alt="Stato traduzione" />

View File

@ -105,7 +105,7 @@ NewPipe ਤੁਹਾਡੇ ਦੁਆਰਾ ਵਰਤੀ ਜਾ ਰਹੀ ਸੇ
<b>ਨੋਟ: ਜਦੋਂ ਤੁਸੀਂ ਅਧਿਕਾਰਤ ਐਪ ਵਿੱਚ ਇੱਕ ਡੇਟਾਬੇਸ ਨੂੰ ਆਯਾਤ ਕਰ ਰਹੇ ਹੋ, ਤਾਂ ਹਮੇਸ਼ਾਂ ਯਕੀਨੀ ਬਣਾਓ ਕਿ ਇਹ ਉਹੀ ਹੈ ਜੋ ਤੁਸੀਂ ਅਧਿਕਾਰਤ ਐਪ ਤੋਂ ਨਿਰਯਾਤ ਕੀਤਾ ਹੈ। ਜੇਕਰ ਤੁਸੀਂ ਅਧਿਕਾਰਤ ਐਪ ਤੋਂ ਇਲਾਵਾ ਕਿਸੇ ਏਪੀਕੇ ਤੋਂ ਨਿਰਯਾਤ ਕੀਤੇ ਡੇਟਾਬੇਸ ਨੂੰ ਆਯਾਤ ਕਰਦੇ ਹੋ, ਤਾਂ ਇਹ ਚੀਜ਼ਾਂ ਨੂੰ ਤੋੜ ਸਕਦਾ ਹੈ। ਅਜਿਹੀ ਕਾਰਵਾਈ ਅਸਮਰਥਿਤ ਹੈ, ਅਤੇ ਤੁਹਾਨੂੰ ਅਜਿਹਾ ਉਦੋਂ ਹੀ ਕਰਨਾ ਚਾਹੀਦਾ ਹੈ ਜਦੋਂ ਤੁਹਾਨੂੰ ਪੂਰੀ ਤਰ੍ਹਾਂ ਯਕੀਨ ਹੋਵੇ ਕਿ ਤੁਸੀਂ ਜਾਣਦੇ ਹੋ ਕਿ ਤੁਸੀਂ ਕੀ ਕਰ ਰਹੇ ਹੋ।</b>
## ਯੋਗਦਾਨ
ਭਾਵੇਂ ਤੁਹਾਡੇ ਕੋਲ ਵਿਚਾਰ, ਅਨੁਵਾਦ, ਡਿਜ਼ਾਈਨ ਤਬਦੀਲੀਆਂ, ਕੋਡ ਦੀ ਸਫਾਈ, ਜਾਂ ਇੱਥੋਂ ਤੱਕ ਕਿ ਵੱਡੀਆਂ ਕੋਡ ਤਬਦੀਲੀਆਂ ਹੋਣ, ਮਦਦ ਦਾ ਹਮੇਸ਼ਾ ਸਵਾਗਤ ਹੈ। ਐਪ ਹਰੇਕ ਯੋਗਦਾਨ ਦੇ ਨਾਲ ਬਿਹਤਰ ਅਤੇ ਬਿਹਤਰ ਹੋ ਜਾਂਦੀ ਹੈ, ਚਾਹੇ ਉਹ ਕਿੰਨਾ ਵੱਡਾ ਜਾਂ ਛੋਟਾ ਹੋਵੇ! ਜੇਕਰ ਤੁਸੀਂ ਸ਼ਾਮਲ ਹੋਣਾ ਚਾਹੁੰਦੇ ਹੋ, ਤਾਂ ਸਾਡੀ ਜਾਂਚ ਕਰੋ [contribution notes](.github/CONTRIBUTING.md).
ਭਾਵੇਂ ਤੁਹਾਡੇ ਕੋਲ ਵਿਚਾਰ, ਅਨੁਵਾਦ, ਡਿਜ਼ਾਈਨ ਤਬਦੀਲੀਆਂ, ਕੋਡ ਦੀ ਸਫਾਈ, ਜਾਂ ਇੱਥੋਂ ਤੱਕ ਕਿ ਵੱਡੀਆਂ ਕੋਡ ਤਬਦੀਲੀਆਂ ਹੋਣ, ਮਦਦ ਦਾ ਹਮੇਸ਼ਾ ਸਵਾਗਤ ਹੈ। ਐਪ ਹਰੇਕ ਯੋਗਦਾਨ ਦੇ ਨਾਲ ਬਿਹਤਰ ਅਤੇ ਬਿਹਤਰ ਹੋ ਜਾਂਦੀ ਹੈ, ਚਾਹੇ ਉਹ ਕਿੰਨਾ ਵੱਡਾ ਜਾਂ ਛੋਟਾ ਹੋਵੇ! ਜੇਕਰ ਤੁਸੀਂ ਸ਼ਾਮਲ ਹੋਣਾ ਚਾਹੁੰਦੇ ਹੋ, ਤਾਂ ਸਾਡੀ ਜਾਂਚ ਕਰੋ [contribution notes](/.github/CONTRIBUTING.md).
<a href="https://hosted.weblate.org/engage/newpipe/">
<img src="https://hosted.weblate.org/widgets/newpipe/-/287x66-grey.png" alt="Translation status" />

View File

@ -106,7 +106,7 @@ Enquanto isso, se você quiser trocar de fontes por algum motivo (por exemplo, a
4. Importe os dados da etapa 1 via Configurações > Backup e Restauração > Importar Base de Dados
## Contribuições
Se você tem ideias, traduções, alterações de design, limpeza de códigos ou mudanças reais de código, a ajuda é sempre bem-vinda. O aplicativo fica cada vez melhor a cada contribuição, não importa quão grande ou pequena! Se você quiser se envolver, verifique nossas [notas de contribuição](.github/CONTRIBUTING.md).
Se você tem ideias, traduções, alterações de design, limpeza de códigos ou mudanças reais de código, a ajuda é sempre bem-vinda. O aplicativo fica cada vez melhor a cada contribuição, não importa quão grande ou pequena! Se você quiser se envolver, verifique nossas [notas de contribuição](/.github/CONTRIBUTING.md).
<a href="https://hosted.weblate.org/engage/newpipe/">
<img src="https://hosted.weblate.org/widgets/newpipe/-/287x66-grey.png" alt="Estado da tradução" />

View File

@ -106,7 +106,7 @@ NewPipe работает, извлекая необходимые данные
<b>Примечание: когда вы импортируете базу данных в официальное приложение, убедитесь, что это именно та база данных, которую вы экспортировали _из_ официального приложения. Если вы импортируете базу данных, экспортированную из APK, отличного от официального приложения, это может привести к ошибке. Такое действие не поддерживается, и вы должны делать его только тогда, когда абсолютно уверены, что знаете, что делаете.</b>
## Участие
Если у вас есть идеи, переводы, изменения дизайна, очистка кода или даже серьезные изменения кода, помощь всегда приветствуется. Приложение становится всё лучше и лучше с каждым вкладом, независимо от того, большой он или маленький! Если вы хотите принять участие, ознакомьтесь с нашими [заметками об участии](.github/CONTRIBUTING.md).
Если у вас есть идеи, переводы, изменения дизайна, очистка кода или даже серьезные изменения кода, помощь всегда приветствуется. Приложение становится всё лучше и лучше с каждым вкладом, независимо от того, большой он или маленький! Если вы хотите принять участие, ознакомьтесь с нашими [заметками об участии](/.github/CONTRIBUTING.md).
<a href="https://hosted.weblate.org/engage/newpipe/">
<img src="https://hosted.weblate.org/widgets/newpipe/-/287x66-grey.png" alt="Состояние перевода">

View File

@ -104,7 +104,7 @@ NewPipe ради тако што преузима потребне податк
<b>Напомена: када увозите базу података у званичну апликацију, увек се уверите да је то она коју сте извезли _из_ званичне апликације. Ако увезете базу података извезену из APK-а, који није званична апликација, то може покварити ствари. Таква радња није подржана и требало би да то урадите само када сте потпуно сигурни да знате шта радите.</b>
## Допринос
Без обзира да ли имате идеје, преводе, промене дизајна, чишћење кода или чак велике промене кода, помоћ је увек добродошла. Апликација постаје све боља и боља са сваким доприносом, без обзира колико је он велики или мали! Ако желите да се укључите, погледајте наше [напомене о доприносима](.github/CONTRIBUTING.md).
Без обзира да ли имате идеје, преводе, промене дизајна, чишћење кода или чак велике промене кода, помоћ је увек добродошла. Апликација постаје све боља и боља са сваким доприносом, без обзира колико је он велики или мали! Ако желите да се укључите, погледајте наше [напомене о доприносима](/.github/CONTRIBUTING.md).
<a href="https://hosted.weblate.org/engage/newpipe/">
<img src="https://hosted.weblate.org/widgets/newpipe/-/287x66-grey.png" alt="Статус превода" />

View File

@ -1,4 +1,3 @@
android.defaults.buildfeatures.buildconfig=true
android.enableJetifier=false
android.nonFinalResIds=false
android.nonTransitiveRClass=true

View File

@ -1,7 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionSha256Sum=38f66cd6eef217b4c35855bb11ea4e9fbc53594ccccb5fb82dfd317ef8c2c5a3
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
distributionSha256Sum=d725d707bfabd4dfdc958c624003b3c80accc03f7037b5122c4b1d0ef15cecab
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
networkTimeout=10000
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists