*Read this in other languages: [English](README.md), [한국어](README.ko.md).*
@@ -86,7 +86,7 @@ NewPipe 코드의 변경이 있을 때(기능 추가 또는 버그 수정으로
1. 직접 디버그 APK를 생성할 수 있습니다. 이 방법은 당신의 기기에서 새로운 기능을 얻을 수 있는 가장 빠른 방법이지만, 꽤 많이 복잡합니다.
따라서 우리는 다른 방법들 중 하나를 사용하는 것을 추천합니다.
2. 우리의 커스텀 저장소를 F-Droid에 추가하고 우리가 릴리즈를 게시하는 대로 저곳에서 릴리즈를 설치할 수 있습니다.
- 이에 대한 설명서는 이곳에서 확인할 수 있습니다: https://newpipe.schabi.org/FAQ/tutorials/install-add-fdroid-repo/
+ 이에 대한 설명서는 이곳에서 확인할 수 있습니다: https://newpipe.net/FAQ/tutorials/install-add-fdroid-repo/
3. 우리가 릴리즈를 게시하는 대로 [Github Releases](https://github.com/TeamNewPipe/NewPipe/releases)에서 APK를 다운받고 이것을 설치할 수 있습니다.
4. F-Droid를 통해 업데이트 할 수 있습니다. F-Droid는 변화를 인식하고, 스스로 APK를 생성하고, 이것에 서명하고, 사용자들에서 업데이트를 전달해야만 하기 때문에,
이것은 업데이트를 받는 가장 느린 방법입니다.
@@ -111,7 +111,7 @@ NewPipe 코드의 변경이 있을 때(기능 추가 또는 버그 수정으로
## Donate
-만약 NewPipe가 마음에 들었다면, 우리는 기부에 대해 기꺼이 환영합니다. bitcoin을 보내거나, Bountysource 또는 Liberapay를 통해 기부할 수 있습니다. NewPipe에 기부하는 것에 대한 자세한 정보를 원한다면, 우리의 [웹사이트](https://newpipe.schabi.org/donate)를 방문하여 주십시오.
+만약 NewPipe가 마음에 들었다면, 우리는 기부에 대해 기꺼이 환영합니다. bitcoin을 보내거나, Bountysource 또는 Liberapay를 통해 기부할 수 있습니다. NewPipe에 기부하는 것에 대한 자세한 정보를 원한다면, 우리의 [웹사이트](https://newpipe.net/donate)를 방문하여 주십시오.
@@ -134,7 +134,7 @@ NewPipe 코드의 변경이 있을 때(기능 추가 또는 버그 수정으로
## Privacy Policy
NewPipe 프로젝트는 미디어 웹 서비스를 사용하는 것에 대한 사적의, 익명의 경험을 제공하는 것을 목표로 하고 있습니다.
-그러므로, 앱은 당신의 동의 없이 어떤 데이터도 수집하지 않습니다. NewPipe의 개인정보보호정책은 당신이 충돌 리포트를 보내거나, 또는 우리의 블로그에 글을 남길 때 어떤 데이터가 보내지고 저장되는지에 대해 상세히 설명합니다. 이 문서는 [여기](https://newpipe.schabi.org/legal/privacy/)에서 확인할 수 있습니다.
+그러므로, 앱은 당신의 동의 없이 어떤 데이터도 수집하지 않습니다. NewPipe의 개인정보보호정책은 당신이 충돌 리포트를 보내거나, 또는 우리의 블로그에 글을 남길 때 어떤 데이터가 보내지고 저장되는지에 대해 상세히 설명합니다. 이 문서는 [여기](https://newpipe.net/legal/privacy/)에서 확인할 수 있습니다.
## License
[![GNU GPLv3 Image](https://www.gnu.org/graphics/gplv3-127x51.png)](http://www.gnu.org/licenses/gpl-3.0.en.html)
diff --git a/README.md b/README.md
index 2568b7624b8..c66bcfa7fb9 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-
+
NewPipe
A libre lightweight streaming frontend for Android.
*Read this in other languages: [English](README.md), [한국어](README.ko.md).*
@@ -83,7 +83,7 @@ NewPipe supports multiple services. Our [docs](https://teamnewpipe.github.io/doc
## Updates
When a change to the NewPipe code occurs (due to either adding features or bug fixing), eventually a release will occur. These are in the format x.xx.x . In order to get this new version, you can:
1. Build a debug APK yourself. This is the fastest way to get new features on your device, but is much more complicated, so we recommend using one of the other methods.
- 2. Add our custom repo to F-Droid and install it from there as soon as we publish a release. The instructions are here: https://newpipe.schabi.org/FAQ/tutorials/install-add-fdroid-repo/
+ 2. Add our custom repo to F-Droid and install it from there as soon as we publish a release. The instructions are here: https://newpipe.net/FAQ/tutorials/install-add-fdroid-repo/
3. Download the APK from [Github Releases](https://github.com/TeamNewPipe/NewPipe/releases) and install it as soon as we publish a release.
4. Update via F-droid. This is the slowest method of getting updates, as F-Droid must recognize changes, build the APK itself, sign it, then push the update to users.
@@ -106,7 +106,7 @@ If you'd like to get involved, check our [contribution notes](.github/CONTRIBUTI
## Donate
-If you like NewPipe we'd be happy about a donation. You can either send bitcoin or donate via Bountysource or Liberapay. For further info on donating to NewPipe, please visit our [website](https://newpipe.schabi.org/donate).
+If you like NewPipe we'd be happy about a donation. You can either send bitcoin or donate via Bountysource or Liberapay. For further info on donating to NewPipe, please visit our [website](https://newpipe.net/donate).
@@ -129,7 +129,7 @@ If you like NewPipe we'd be happy about a donation. You can either send bitcoin
## Privacy Policy
The NewPipe project aims to provide a private, anonymous experience for using media web services.
-Therefore, the app does not collect any data without your consent. NewPipe's privacy policy explains in detail what data is sent and stored when you send a crash report, or comment in our blog. You can find the document [here](https://newpipe.schabi.org/legal/privacy/).
+Therefore, the app does not collect any data without your consent. NewPipe's privacy policy explains in detail what data is sent and stored when you send a crash report, or comment in our blog. You can find the document [here](https://newpipe.net/legal/privacy/).
## License
[![GNU GPLv3 Image](https://www.gnu.org/graphics/gplv3-127x51.png)](http://www.gnu.org/licenses/gpl-3.0.en.html)
diff --git a/app/build.gradle b/app/build.gradle
index 87215b38523..522c3606295 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -13,8 +13,8 @@ android {
resValue "string", "app_name", "NewPipe"
minSdkVersion 19
targetSdkVersion 29
- versionCode 960
- versionName "0.20.6"
+ versionCode 961
+ versionName "0.20.7"
multiDexEnabled true
@@ -85,11 +85,15 @@ android {
sourceSets {
androidTest.assets.srcDirs += files("$projectDir/schemas".toString())
}
+
+ buildFeatures {
+ viewBinding true
+ }
}
ext {
icepickVersion = '3.2.0'
- checkstyleVersion = '8.37'
+ checkstyleVersion = '8.38'
stethoVersion = '1.5.1'
leakCanaryVersion = '2.5'
exoPlayerVersion = '2.11.8'
@@ -162,7 +166,7 @@ dependencies {
kapt "frankiesardo:icepick-processor:${icepickVersion}"
checkstyle "com.puppycrawl.tools:checkstyle:${checkstyleVersion}"
- ktlint "com.pinterest:ktlint:0.39.0"
+ ktlint "com.pinterest:ktlint:0.40.0"
debugImplementation "com.facebook.stetho:stetho:${stethoVersion}"
debugImplementation "com.facebook.stetho:stetho-okhttp3:${stethoVersion}"
@@ -175,7 +179,7 @@ dependencies {
// NewPipe dependencies
// You can use a local version by uncommenting a few lines in settings.gradle
- implementation 'com.github.TeamNewPipe:NewPipeExtractor:b3835bd616ab28b861c83dcefd56e1754c6d20be'
+ implementation 'com.github.TeamNewPipe:NewPipeExtractor:b2837698f55296e00aeca5cb1847755dd1174af4'
implementation "com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751"
implementation "org.jsoup:jsoup:1.13.1"
@@ -199,6 +203,7 @@ dependencies {
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.documentfile:documentfile:1.0.1'
implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0'
+ implementation 'androidx.webkit:webkit:1.4.0'
implementation "androidx.lifecycle:lifecycle-livedata:${androidxLifecycleVersion}"
implementation "androidx.lifecycle:lifecycle-viewmodel:${androidxLifecycleVersion}"
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 1b9de562016..d240d123f75 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -227,20 +227,18 @@
+
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
-
-
@@ -268,7 +266,7 @@
-
+
diff --git a/app/src/main/java/org/schabi/newpipe/App.java b/app/src/main/java/org/schabi/newpipe/App.java
index de401d4f5d9..e6dce4d6763 100644
--- a/app/src/main/java/org/schabi/newpipe/App.java
+++ b/app/src/main/java/org/schabi/newpipe/App.java
@@ -242,8 +242,9 @@ private void initNotificationChannels() {
String name = getString(R.string.notification_channel_name);
String description = getString(R.string.notification_channel_description);
- // Keep this below DEFAULT to avoid making noise on every notification update
- final int importance = NotificationManager.IMPORTANCE_LOW;
+ // Keep this below DEFAULT to avoid making noise on every notification update for the main
+ // and update channels
+ int importance = NotificationManager.IMPORTANCE_LOW;
final NotificationChannel mainChannel = new NotificationChannel(id, name, importance);
mainChannel.setDescription(description);
@@ -255,9 +256,17 @@ private void initNotificationChannels() {
final NotificationChannel appUpdateChannel = new NotificationChannel(id, name, importance);
appUpdateChannel.setDescription(description);
+ id = getString(R.string.hash_channel_id);
+ name = getString(R.string.hash_channel_name);
+ description = getString(R.string.hash_channel_description);
+ importance = NotificationManager.IMPORTANCE_HIGH;
+
+ final NotificationChannel hashChannel = new NotificationChannel(id, name, importance);
+ hashChannel.setDescription(description);
+
final NotificationManager notificationManager = getSystemService(NotificationManager.class);
notificationManager.createNotificationChannels(Arrays.asList(mainChannel,
- appUpdateChannel));
+ appUpdateChannel, hashChannel));
}
protected boolean isDisposedRxExceptionsReported() {
diff --git a/app/src/main/java/org/schabi/newpipe/CheckForNewAppVersion.java b/app/src/main/java/org/schabi/newpipe/CheckForNewAppVersion.java
index 916630ae6e4..0fecc3f96d3 100644
--- a/app/src/main/java/org/schabi/newpipe/CheckForNewAppVersion.java
+++ b/app/src/main/java/org/schabi/newpipe/CheckForNewAppVersion.java
@@ -48,7 +48,7 @@ private CheckForNewAppVersion() { }
private static final String GITHUB_APK_SHA1
= "B0:2E:90:7C:1C:D6:FC:57:C3:35:F0:88:D0:8F:50:5F:94:E4:D2:15";
- private static final String NEWPIPE_API_URL = "https://newpipe.schabi.org/api/data.json";
+ private static final String NEWPIPE_API_URL = "https://newpipe.net/api/data.json";
/**
* Method to get the APK's SHA1 key. See https://stackoverflow.com/questions/9293019/#22506133.
diff --git a/app/src/main/java/org/schabi/newpipe/MainActivity.java b/app/src/main/java/org/schabi/newpipe/MainActivity.java
index f51ecf2d370..0c784e9d50a 100644
--- a/app/src/main/java/org/schabi/newpipe/MainActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/MainActivity.java
@@ -39,17 +39,14 @@
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
-import android.widget.Button;
import android.widget.FrameLayout;
-import android.widget.ImageView;
import android.widget.Spinner;
-import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.ActionBarDrawerToggle;
import androidx.appcompat.app.AppCompatActivity;
-import androidx.appcompat.widget.Toolbar;
+import androidx.core.app.ActivityCompat;
import androidx.core.view.GravityCompat;
import androidx.drawerlayout.widget.DrawerLayout;
import androidx.fragment.app.Fragment;
@@ -57,8 +54,12 @@
import androidx.preference.PreferenceManager;
import com.google.android.material.bottomsheet.BottomSheetBehavior;
-import com.google.android.material.navigation.NavigationView;
+import org.schabi.newpipe.databinding.ActivityMainBinding;
+import org.schabi.newpipe.databinding.DrawerHeaderBinding;
+import org.schabi.newpipe.databinding.DrawerLayoutBinding;
+import org.schabi.newpipe.databinding.InstanceSpinnerLayoutBinding;
+import org.schabi.newpipe.databinding.ToolbarLayoutBinding;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
@@ -96,15 +97,14 @@ public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
public static final boolean DEBUG = !BuildConfig.BUILD_TYPE.equals("release");
+ private ActivityMainBinding mainBinding;
+ private DrawerHeaderBinding drawerHeaderBinding;
+ private DrawerLayoutBinding drawerLayoutBinding;
+ private ToolbarLayoutBinding toolbarLayoutBinding;
+
private ActionBarDrawerToggle toggle;
- private DrawerLayout drawer;
- private NavigationView drawerItems;
- private ImageView headerServiceIcon;
- private TextView headerServiceView;
- private Button toggleServiceButton;
private boolean servicesShown = false;
- private ImageView serviceArrow;
private BroadcastReceiver broadcastReceiver;
@@ -129,7 +129,7 @@ protected void onCreate(final Bundle savedInstanceState) {
+ "savedInstanceState = [" + savedInstanceState + "]");
}
- // enable TLS1.1/1.2 for kitkat devices, to fix download and play for mediaCCC sources
+ // enable TLS1.1/1.2 for kitkat devices, to fix download and play for media.ccc.de sources
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) {
TLSSocketFactoryCompat.setAsDefault();
}
@@ -137,13 +137,19 @@ protected void onCreate(final Bundle savedInstanceState) {
assureCorrectAppLanguage(this);
super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
+
+ mainBinding = ActivityMainBinding.inflate(getLayoutInflater());
+ drawerLayoutBinding = mainBinding.drawerLayout;
+ drawerHeaderBinding = DrawerHeaderBinding.bind(drawerLayoutBinding.navigation
+ .getHeaderView(0));
+ toolbarLayoutBinding = mainBinding.toolbarLayout;
+ setContentView(mainBinding.getRoot());
if (getSupportFragmentManager().getBackStackEntryCount() == 0) {
initFragments();
}
- setSupportActionBar(findViewById(R.id.toolbar));
+ setSupportActionBar(toolbarLayoutBinding.toolbar);
try {
setupDrawer();
} catch (final Exception e) {
@@ -157,10 +163,6 @@ protected void onCreate(final Bundle savedInstanceState) {
}
private void setupDrawer() throws Exception {
- final Toolbar toolbar = findViewById(R.id.toolbar);
- drawer = findViewById(R.id.drawer_layout);
- drawerItems = findViewById(R.id.navigation);
-
//Tabs
final int currentServiceId = ServiceHelper.getSelectedServiceId(this);
final StreamingService service = NewPipe.getService(currentServiceId);
@@ -168,43 +170,43 @@ private void setupDrawer() throws Exception {
int kioskId = 0;
for (final String ks : service.getKioskList().getAvailableKiosks()) {
- drawerItems.getMenu()
+ drawerLayoutBinding.navigation.getMenu()
.add(R.id.menu_tabs_group, kioskId, 0, KioskTranslator
.getTranslatedKioskName(ks, this))
.setIcon(KioskTranslator.getKioskIcon(ks, this));
kioskId++;
}
- drawerItems.getMenu()
+ drawerLayoutBinding.navigation.getMenu()
.add(R.id.menu_tabs_group, ITEM_ID_SUBSCRIPTIONS, ORDER,
R.string.tab_subscriptions)
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_channel));
- drawerItems.getMenu()
+ drawerLayoutBinding.navigation.getMenu()
.add(R.id.menu_tabs_group, ITEM_ID_FEED, ORDER, R.string.fragment_feed_title)
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_rss));
- drawerItems.getMenu()
+ drawerLayoutBinding.navigation.getMenu()
.add(R.id.menu_tabs_group, ITEM_ID_BOOKMARKS, ORDER, R.string.tab_bookmarks)
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_bookmark));
- drawerItems.getMenu()
+ drawerLayoutBinding.navigation.getMenu()
.add(R.id.menu_tabs_group, ITEM_ID_DOWNLOADS, ORDER, R.string.downloads)
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_file_download));
- drawerItems.getMenu()
+ drawerLayoutBinding.navigation.getMenu()
.add(R.id.menu_tabs_group, ITEM_ID_HISTORY, ORDER, R.string.action_history)
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_history));
//Settings and About
- drawerItems.getMenu()
+ drawerLayoutBinding.navigation.getMenu()
.add(R.id.menu_options_about_group, ITEM_ID_SETTINGS, ORDER, R.string.settings)
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_settings));
- drawerItems.getMenu()
+ drawerLayoutBinding.navigation.getMenu()
.add(R.id.menu_options_about_group, ITEM_ID_ABOUT, ORDER, R.string.tab_about)
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_info_outline));
- toggle = new ActionBarDrawerToggle(this, drawer, toolbar, R.string.drawer_open,
- R.string.drawer_close);
+ toggle = new ActionBarDrawerToggle(this, mainBinding.getRoot(),
+ toolbarLayoutBinding.toolbar, R.string.drawer_open, R.string.drawer_close);
toggle.syncState();
- drawer.addDrawerListener(toggle);
- drawer.addDrawerListener(new DrawerLayout.SimpleDrawerListener() {
+ mainBinding.getRoot().addDrawerListener(toggle);
+ mainBinding.getRoot().addDrawerListener(new DrawerLayout.SimpleDrawerListener() {
private int lastService;
@Override
@@ -218,12 +220,12 @@ public void onDrawerClosed(final View drawerView) {
toggleServices();
}
if (lastService != ServiceHelper.getSelectedServiceId(MainActivity.this)) {
- new Handler(Looper.getMainLooper()).post(MainActivity.this::recreate);
+ ActivityCompat.recreate(MainActivity.this);
}
}
});
- drawerItems.setNavigationItemSelectedListener(this::drawerItemSelected);
+ drawerLayoutBinding.navigation.setNavigationItemSelectedListener(this::drawerItemSelected);
setupDrawerHeader();
}
@@ -246,15 +248,17 @@ private boolean drawerItemSelected(final MenuItem item) {
return false;
}
- drawer.closeDrawers();
+ mainBinding.getRoot().closeDrawers();
return true;
}
private void changeService(final MenuItem item) {
- drawerItems.getMenu().getItem(ServiceHelper.getSelectedServiceId(this))
+ drawerLayoutBinding.navigation.getMenu()
+ .getItem(ServiceHelper.getSelectedServiceId(this))
.setChecked(false);
ServiceHelper.setSelectedServiceId(this, item.getItemId());
- drawerItems.getMenu().getItem(ServiceHelper.getSelectedServiceId(this))
+ drawerLayoutBinding.navigation.getMenu()
+ .getItem(ServiceHelper.getSelectedServiceId(this))
.setChecked(true);
}
@@ -306,26 +310,19 @@ private void optionsAboutSelected(final MenuItem item) {
}
private void setupDrawerHeader() {
- final NavigationView navigationView = findViewById(R.id.navigation);
- final View hView = navigationView.getHeaderView(0);
-
- serviceArrow = hView.findViewById(R.id.drawer_arrow);
- headerServiceIcon = hView.findViewById(R.id.drawer_header_service_icon);
- headerServiceView = hView.findViewById(R.id.drawer_header_service_view);
- toggleServiceButton = hView.findViewById(R.id.drawer_header_action_button);
- toggleServiceButton.setOnClickListener(view -> toggleServices());
+ drawerHeaderBinding.drawerHeaderActionButton.setOnClickListener(view -> toggleServices());
// If the current app name is bigger than the default "NewPipe" (7 chars),
// let the text view grow a little more as well.
if (getString(R.string.app_name).length() > "NewPipe".length()) {
- final TextView headerTitle = hView.findViewById(R.id.drawer_header_newpipe_title);
- final ViewGroup.LayoutParams layoutParams = headerTitle.getLayoutParams();
+ final ViewGroup.LayoutParams layoutParams =
+ drawerHeaderBinding.drawerHeaderNewpipeTitle.getLayoutParams();
layoutParams.width = ViewGroup.LayoutParams.WRAP_CONTENT;
- headerTitle.setLayoutParams(layoutParams);
- headerTitle.setMaxLines(2);
- headerTitle.setMinWidth(getResources()
+ drawerHeaderBinding.drawerHeaderNewpipeTitle.setLayoutParams(layoutParams);
+ drawerHeaderBinding.drawerHeaderNewpipeTitle.setMaxLines(2);
+ drawerHeaderBinding.drawerHeaderNewpipeTitle.setMinWidth(getResources()
.getDimensionPixelSize(R.dimen.drawer_header_newpipe_title_default_width));
- headerTitle.setMaxWidth(getResources()
+ drawerHeaderBinding.drawerHeaderNewpipeTitle.setMaxWidth(getResources()
.getDimensionPixelSize(R.dimen.drawer_header_newpipe_title_max_width));
}
}
@@ -333,9 +330,9 @@ private void setupDrawerHeader() {
private void toggleServices() {
servicesShown = !servicesShown;
- drawerItems.getMenu().removeGroup(R.id.menu_services_group);
- drawerItems.getMenu().removeGroup(R.id.menu_tabs_group);
- drawerItems.getMenu().removeGroup(R.id.menu_options_about_group);
+ drawerLayoutBinding.navigation.getMenu().removeGroup(R.id.menu_services_group);
+ drawerLayoutBinding.navigation.getMenu().removeGroup(R.id.menu_tabs_group);
+ drawerLayoutBinding.navigation.getMenu().removeGroup(R.id.menu_options_about_group);
if (servicesShown) {
showServices();
@@ -349,13 +346,13 @@ private void toggleServices() {
}
private void showServices() {
- serviceArrow.setImageResource(R.drawable.ic_arrow_drop_up_white_24dp);
+ drawerHeaderBinding.drawerArrow.setImageResource(R.drawable.ic_arrow_drop_up_white_24dp);
for (final StreamingService s : NewPipe.getServices()) {
final String title = s.getServiceInfo().getName()
+ (ServiceHelper.isBeta(s) ? " (beta)" : "");
- final MenuItem menuItem = drawerItems.getMenu()
+ final MenuItem menuItem = drawerLayoutBinding.navigation.getMenu()
.add(R.id.menu_services_group, s.getServiceId(), ORDER, title)
.setIcon(ServiceHelper.getIcon(s.getServiceId()));
@@ -364,21 +361,22 @@ private void showServices() {
enhancePeertubeMenu(s, menuItem);
}
}
- drawerItems.getMenu().getItem(ServiceHelper.getSelectedServiceId(this))
+ drawerLayoutBinding.navigation.getMenu()
+ .getItem(ServiceHelper.getSelectedServiceId(this))
.setChecked(true);
}
private void enhancePeertubeMenu(final StreamingService s, final MenuItem menuItem) {
- final PeertubeInstance currentInstace = PeertubeHelper.getCurrentInstance();
- menuItem.setTitle(currentInstace.getName() + (ServiceHelper.isBeta(s) ? " (beta)" : ""));
- final Spinner spinner = (Spinner) LayoutInflater.from(this)
- .inflate(R.layout.instance_spinner_layout, null);
+ final PeertubeInstance currentInstance = PeertubeHelper.getCurrentInstance();
+ menuItem.setTitle(currentInstance.getName() + (ServiceHelper.isBeta(s) ? " (beta)" : ""));
+ final Spinner spinner = InstanceSpinnerLayoutBinding.inflate(LayoutInflater.from(this))
+ .getRoot();
final List instances = PeertubeHelper.getInstanceList(this);
final List items = new ArrayList<>();
int defaultSelect = 0;
for (final PeertubeInstance instance : instances) {
items.add(instance.getName());
- if (instance.getUrl().equals(currentInstace.getUrl())) {
+ if (instance.getUrl().equals(currentInstance.getUrl())) {
defaultSelect = items.size() - 1;
}
}
@@ -397,7 +395,7 @@ public void onItemSelected(final AdapterView> parent, final View view,
}
PeertubeHelper.selectInstance(newInstance, getApplicationContext());
changeService(menuItem);
- drawer.closeDrawers();
+ mainBinding.getRoot().closeDrawers();
new Handler(Looper.getMainLooper()).postDelayed(() -> {
getSupportFragmentManager().popBackStack(null,
FragmentManager.POP_BACK_STACK_INCLUSIVE);
@@ -414,7 +412,7 @@ public void onNothingSelected(final AdapterView> parent) {
}
private void showTabs() throws ExtractionException {
- serviceArrow.setImageResource(R.drawable.ic_arrow_drop_down_white_24dp);
+ drawerHeaderBinding.drawerArrow.setImageResource(R.drawable.ic_arrow_drop_down_white_24dp);
//Tabs
final int currentServiceId = ServiceHelper.getSelectedServiceId(this);
@@ -423,34 +421,34 @@ private void showTabs() throws ExtractionException {
int kioskId = 0;
for (final String ks : service.getKioskList().getAvailableKiosks()) {
- drawerItems.getMenu()
+ drawerLayoutBinding.navigation.getMenu()
.add(R.id.menu_tabs_group, kioskId, ORDER,
KioskTranslator.getTranslatedKioskName(ks, this))
.setIcon(KioskTranslator.getKioskIcon(ks, this));
kioskId++;
}
- drawerItems.getMenu()
+ drawerLayoutBinding.navigation.getMenu()
.add(R.id.menu_tabs_group, ITEM_ID_SUBSCRIPTIONS, ORDER, R.string.tab_subscriptions)
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_channel));
- drawerItems.getMenu()
+ drawerLayoutBinding.navigation.getMenu()
.add(R.id.menu_tabs_group, ITEM_ID_FEED, ORDER, R.string.fragment_feed_title)
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_rss));
- drawerItems.getMenu()
+ drawerLayoutBinding.navigation.getMenu()
.add(R.id.menu_tabs_group, ITEM_ID_BOOKMARKS, ORDER, R.string.tab_bookmarks)
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_bookmark));
- drawerItems.getMenu()
+ drawerLayoutBinding.navigation.getMenu()
.add(R.id.menu_tabs_group, ITEM_ID_DOWNLOADS, ORDER, R.string.downloads)
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_file_download));
- drawerItems.getMenu()
+ drawerLayoutBinding.navigation.getMenu()
.add(R.id.menu_tabs_group, ITEM_ID_HISTORY, ORDER, R.string.action_history)
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_history));
//Settings and About
- drawerItems.getMenu()
+ drawerLayoutBinding.navigation.getMenu()
.add(R.id.menu_options_about_group, ITEM_ID_SETTINGS, ORDER, R.string.settings)
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_settings));
- drawerItems.getMenu()
+ drawerLayoutBinding.navigation.getMenu()
.add(R.id.menu_options_about_group, ITEM_ID_ABOUT, ORDER, R.string.tab_about)
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_info_outline));
}
@@ -475,16 +473,18 @@ protected void onResume() {
// Close drawer on return, and don't show animation,
// so it looks like the drawer isn't open when the user returns to MainActivity
- drawer.closeDrawer(GravityCompat.START, false);
+ mainBinding.getRoot().closeDrawer(GravityCompat.START, false);
try {
final int selectedServiceId = ServiceHelper.getSelectedServiceId(this);
final String selectedServiceName = NewPipe.getService(selectedServiceId)
.getServiceInfo().getName();
- headerServiceView.setText(selectedServiceName);
- headerServiceIcon.setImageResource(ServiceHelper.getIcon(selectedServiceId));
+ drawerHeaderBinding.drawerHeaderServiceView.setText(selectedServiceName);
+ drawerHeaderBinding.drawerHeaderServiceIcon.setImageResource(ServiceHelper
+ .getIcon(selectedServiceId));
- headerServiceView.post(() -> headerServiceView.setSelected(true));
- toggleServiceButton.setContentDescription(
+ drawerHeaderBinding.drawerHeaderServiceView.post(() -> drawerHeaderBinding
+ .drawerHeaderServiceView.setSelected(true));
+ drawerHeaderBinding.drawerHeaderActionButton.setContentDescription(
getString(R.string.drawer_header_description) + selectedServiceName);
} catch (final Exception e) {
ErrorActivity.reportUiError(this, e);
@@ -497,10 +497,7 @@ protected void onResume() {
Log.d(TAG, "Theme has changed, recreating activity...");
}
sharedPreferences.edit().putBoolean(Constants.KEY_THEME_CHANGE, false).apply();
- // https://stackoverflow.com/questions/10844112/
- // Briefly, let the activity resume
- // properly posting the recreate call to end of the message queue
- new Handler(Looper.getMainLooper()).post(MainActivity.this::recreate);
+ ActivityCompat.recreate(this);
}
if (sharedPreferences.getBoolean(Constants.KEY_MAIN_PAGE_CHANGE, false)) {
@@ -513,7 +510,8 @@ protected void onResume() {
final boolean isHistoryEnabled = sharedPreferences.getBoolean(
getString(R.string.enable_watch_history_key), true);
- drawerItems.getMenu().findItem(ITEM_ID_HISTORY).setVisible(isHistoryEnabled);
+ drawerLayoutBinding.navigation.getMenu().findItem(ITEM_ID_HISTORY)
+ .setVisible(isHistoryEnabled);
}
@Override
@@ -557,9 +555,8 @@ public void onBackPressed() {
}
if (DeviceUtils.isTv(this)) {
- final View drawerPanel = findViewById(R.id.navigation);
- if (drawer.isDrawerOpen(drawerPanel)) {
- drawer.closeDrawers();
+ if (mainBinding.getRoot().isDrawerOpen(drawerLayoutBinding.navigation)) {
+ mainBinding.getRoot().closeDrawers();
return;
}
}
@@ -585,9 +582,7 @@ public void onBackPressed() {
// delegate the back press to it
if (fragmentPlayer instanceof BackPressable) {
if (!((BackPressable) fragmentPlayer).onBackPressed()) {
- final FrameLayout bottomSheetLayout =
- findViewById(R.id.fragment_player_holder);
- BottomSheetBehavior.from(bottomSheetLayout)
+ BottomSheetBehavior.from(mainBinding.fragmentPlayerHolder)
.setState(BottomSheetBehavior.STATE_COLLAPSED);
}
return;
@@ -670,8 +665,7 @@ public boolean onCreateOptionsMenu(final Menu menu) {
final Fragment fragment
= getSupportFragmentManager().findFragmentById(R.id.fragment_holder);
if (!(fragment instanceof SearchFragment)) {
- findViewById(R.id.toolbar).findViewById(R.id.toolbar_search_container)
- .setVisibility(View.GONE);
+ toolbarLayoutBinding.toolbarSearchContainer.getRoot().setVisibility(View.GONE);
}
final ActionBar actionBar = getSupportActionBar();
@@ -732,21 +726,20 @@ private void updateDrawerNavigation() {
return;
}
- final Toolbar toolbar = findViewById(R.id.toolbar);
-
final Fragment fragment = getSupportFragmentManager()
.findFragmentById(R.id.fragment_holder);
if (fragment instanceof MainFragment) {
getSupportActionBar().setDisplayHomeAsUpEnabled(false);
if (toggle != null) {
toggle.syncState();
- toolbar.setNavigationOnClickListener(v -> drawer.openDrawer(GravityCompat.START));
- drawer.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNDEFINED);
+ toolbarLayoutBinding.toolbar.setNavigationOnClickListener(v -> mainBinding.getRoot()
+ .openDrawer(GravityCompat.START));
+ mainBinding.getRoot().setDrawerLockMode(DrawerLayout.LOCK_MODE_UNDEFINED);
}
} else {
- drawer.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
+ mainBinding.getRoot().setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
- toolbar.setNavigationOnClickListener(v -> onHomeButtonPressed());
+ toolbarLayoutBinding.toolbar.setNavigationOnClickListener(v -> onHomeButtonPressed());
}
}
@@ -854,9 +847,8 @@ public void onReceive(final Context context, final Intent intent) {
}
private boolean bottomSheetHiddenOrCollapsed() {
- final FrameLayout bottomSheetLayout = findViewById(R.id.fragment_player_holder);
final BottomSheetBehavior bottomSheetBehavior =
- BottomSheetBehavior.from(bottomSheetLayout);
+ BottomSheetBehavior.from(mainBinding.fragmentPlayerHolder);
final int sheetState = bottomSheetBehavior.getState();
return sheetState == BottomSheetBehavior.STATE_HIDDEN
diff --git a/app/src/main/java/org/schabi/newpipe/ReCaptchaActivity.java b/app/src/main/java/org/schabi/newpipe/ReCaptchaActivity.java
index c962ed99dc4..463fc24acab 100644
--- a/app/src/main/java/org/schabi/newpipe/ReCaptchaActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/ReCaptchaActivity.java
@@ -8,20 +8,18 @@
import android.view.Menu;
import android.view.MenuItem;
import android.webkit.CookieManager;
-import android.webkit.WebResourceRequest;
import android.webkit.WebSettings;
import android.webkit.WebView;
-import android.webkit.WebViewClient;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
-import androidx.appcompat.widget.Toolbar;
import androidx.core.app.NavUtils;
import androidx.preference.PreferenceManager;
+import androidx.webkit.WebViewClientCompat;
+import org.schabi.newpipe.databinding.ActivityRecaptchaBinding;
import org.schabi.newpipe.util.ThemeHelper;
import java.io.UnsupportedEncodingException;
@@ -53,46 +51,37 @@ public class ReCaptchaActivity extends AppCompatActivity {
public static final String YT_URL = "https://www.youtube.com";
public static final String RECAPTCHA_COOKIES_KEY = "recaptcha_cookies";
- private WebView webView;
+ public static String sanitizeRecaptchaUrl(@Nullable final String url) {
+ if (url == null || url.trim().isEmpty()) {
+ return YT_URL; // YouTube is the most likely service to have thrown a recaptcha
+ } else {
+ // remove "pbj=1" parameter from YouYube urls, as it makes the page JSON and not HTML
+ return url.replace("&pbj=1", "").replace("pbj=1&", "").replace("?pbj=1", "");
+ }
+ }
+
+ private ActivityRecaptchaBinding recaptchaBinding;
private String foundCookies = "";
@Override
protected void onCreate(final Bundle savedInstanceState) {
ThemeHelper.setTheme(this);
super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_recaptcha);
- final Toolbar toolbar = findViewById(R.id.toolbar);
- setSupportActionBar(toolbar);
- String url = getIntent().getStringExtra(RECAPTCHA_URL_EXTRA);
- if (url == null || url.isEmpty()) {
- url = YT_URL;
- }
+ recaptchaBinding = ActivityRecaptchaBinding.inflate(getLayoutInflater());
+ setContentView(recaptchaBinding.getRoot());
+ setSupportActionBar(recaptchaBinding.toolbar);
+ final String url = sanitizeRecaptchaUrl(getIntent().getStringExtra(RECAPTCHA_URL_EXTRA));
// set return to Cancel by default
setResult(RESULT_CANCELED);
-
- webView = findViewById(R.id.reCaptchaWebView);
-
// enable Javascript
- final WebSettings webSettings = webView.getSettings();
+ final WebSettings webSettings = recaptchaBinding.reCaptchaWebView.getSettings();
webSettings.setJavaScriptEnabled(true);
+ webSettings.setUserAgentString(DownloaderImpl.USER_AGENT);
- webView.setWebViewClient(new WebViewClient() {
- @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
- @Override
- public boolean shouldOverrideUrlLoading(final WebView view,
- final WebResourceRequest request) {
- final String url = request.getUrl().toString();
- if (MainActivity.DEBUG) {
- Log.d(TAG, "shouldOverrideUrlLoading: request.url=" + url);
- }
-
- handleCookiesFromUrl(url);
- return false;
- }
-
+ recaptchaBinding.reCaptchaWebView.setWebViewClient(new WebViewClientCompat() {
@Override
public boolean shouldOverrideUrlLoading(final WebView view, final String url) {
if (MainActivity.DEBUG) {
@@ -111,17 +100,16 @@ public void onPageFinished(final WebView view, final String url) {
});
// cleaning cache, history and cookies from webView
- webView.clearCache(true);
- webView.clearHistory();
- final android.webkit.CookieManager cookieManager = CookieManager.getInstance();
+ recaptchaBinding.reCaptchaWebView.clearCache(true);
+ recaptchaBinding.reCaptchaWebView.clearHistory();
+ final CookieManager cookieManager = CookieManager.getInstance();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
- cookieManager.removeAllCookies(aBoolean -> {
- });
+ cookieManager.removeAllCookies(value -> { });
} else {
cookieManager.removeAllCookie();
}
- webView.loadUrl(url);
+ recaptchaBinding.reCaptchaWebView.loadUrl(url);
}
@Override
@@ -145,18 +133,16 @@ public void onBackPressed() {
@Override
public boolean onOptionsItemSelected(final MenuItem item) {
- final int id = item.getItemId();
- switch (id) {
- case R.id.menu_item_done:
- saveCookiesAndFinish();
- return true;
- default:
- return false;
+ if (item.getItemId() == R.id.menu_item_done) {
+ saveCookiesAndFinish();
+ return true;
}
+ return false;
}
private void saveCookiesAndFinish() {
- handleCookiesFromUrl(webView.getUrl()); // try to get cookies of unclosed page
+ // try to get cookies of unclosed page
+ handleCookiesFromUrl(recaptchaBinding.reCaptchaWebView.getUrl());
if (MainActivity.DEBUG) {
Log.d(TAG, "saveCookiesAndFinish: foundCookies=" + foundCookies);
}
diff --git a/app/src/main/java/org/schabi/newpipe/RouterActivity.java b/app/src/main/java/org/schabi/newpipe/RouterActivity.java
index 9ad993de183..98a0921e4ea 100644
--- a/app/src/main/java/org/schabi/newpipe/RouterActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/RouterActivity.java
@@ -14,7 +14,6 @@
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
-import android.widget.LinearLayout;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.Toast;
@@ -26,10 +25,13 @@
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.core.app.NotificationCompat;
+import androidx.core.app.ServiceCompat;
import androidx.core.widget.TextViewCompat;
import androidx.fragment.app.FragmentManager;
import androidx.preference.PreferenceManager;
+import org.schabi.newpipe.databinding.ListRadioIconItemBinding;
+import org.schabi.newpipe.databinding.SingleChoiceDialogViewBinding;
import org.schabi.newpipe.download.DownloadDialog;
import org.schabi.newpipe.extractor.Info;
import org.schabi.newpipe.extractor.NewPipe;
@@ -267,9 +269,8 @@ private void showDialog(final List choices) {
final Context themeWrapperContext = getThemeWrapperContext();
final LayoutInflater inflater = LayoutInflater.from(themeWrapperContext);
- final LinearLayout rootLayout = (LinearLayout) inflater.inflate(
- R.layout.single_choice_dialog_view, null, false);
- final RadioGroup radioGroup = rootLayout.findViewById(android.R.id.list);
+ final RadioGroup radioGroup = SingleChoiceDialogViewBinding.inflate(getLayoutInflater())
+ .list;
final DialogInterface.OnClickListener dialogButtonsClickListener = (dialog, which) -> {
final int indexOfChild = radioGroup.indexOfChild(
@@ -322,8 +323,7 @@ private void showDialog(final List choices) {
int id = 12345;
for (final AdapterChoiceItem item : choices) {
- final RadioButton radioButton
- = (RadioButton) inflater.inflate(R.layout.list_radio_icon_item, null);
+ final RadioButton radioButton = ListRadioIconItemBinding.inflate(inflater).getRoot();
radioButton.setText(item.description);
TextViewCompat.setCompoundDrawablesRelativeWithIntrinsicBounds(radioButton,
AppCompatResources.getDrawable(getApplicationContext(), item.icon),
@@ -696,7 +696,7 @@ public Consumer getResultHandler(final Choice choice) {
@Override
public void onDestroy() {
super.onDestroy();
- stopForeground(true);
+ ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE);
if (fetcher != null) {
fetcher.dispose();
}
diff --git a/app/src/main/java/org/schabi/newpipe/about/AboutActivity.java b/app/src/main/java/org/schabi/newpipe/about/AboutActivity.java
index e3e56816c61..6ff69156112 100644
--- a/app/src/main/java/org/schabi/newpipe/about/AboutActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/about/AboutActivity.java
@@ -6,22 +6,19 @@
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
-import androidx.appcompat.widget.Toolbar;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
-import androidx.recyclerview.widget.RecyclerView;
import androidx.viewpager2.adapter.FragmentStateAdapter;
-import androidx.viewpager2.widget.ViewPager2;
-import com.google.android.material.tabs.TabLayout;
import com.google.android.material.tabs.TabLayoutMediator;
import org.schabi.newpipe.BuildConfig;
import org.schabi.newpipe.R;
+import org.schabi.newpipe.databinding.ActivityAboutBinding;
+import org.schabi.newpipe.databinding.FragmentAboutBinding;
import org.schabi.newpipe.util.ThemeHelper;
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
@@ -68,40 +65,27 @@ public class AboutActivity extends AppCompatActivity {
private static final int POS_ABOUT = 0;
private static final int POS_LICENSE = 1;
private static final int TOTAL_COUNT = 2;
- /**
- * The {@link RecyclerView.Adapter} that will provide
- * fragments for each of the sections. We use a
- * {@link FragmentStateAdapter} derivative, which will keep every
- * loaded fragment in memory.
- */
- private SectionsPagerAdapter mSectionsPagerAdapter;
- /**
- * The {@link ViewPager2} that will host the section contents.
- */
- private ViewPager2 mViewPager;
@Override
protected void onCreate(final Bundle savedInstanceState) {
assureCorrectAppLanguage(this);
super.onCreate(savedInstanceState);
ThemeHelper.setTheme(this);
- this.setTitle(getString(R.string.title_activity_about));
+ setTitle(getString(R.string.title_activity_about));
- setContentView(R.layout.activity_about);
+ final ActivityAboutBinding aboutBinding = ActivityAboutBinding.inflate(getLayoutInflater());
+ setContentView(aboutBinding.getRoot());
- final Toolbar toolbar = findViewById(R.id.toolbar);
- setSupportActionBar(toolbar);
+ setSupportActionBar(aboutBinding.toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
// Create the adapter that will return a fragment for each of the three
// primary sections of the activity.
- mSectionsPagerAdapter = new SectionsPagerAdapter(this);
+ final SectionsPagerAdapter mSectionsPagerAdapter = new SectionsPagerAdapter(this);
// Set up the ViewPager with the sections adapter.
- mViewPager = findViewById(R.id.container);
- mViewPager.setAdapter(mSectionsPagerAdapter);
+ aboutBinding.container.setAdapter(mSectionsPagerAdapter);
- final TabLayout tabLayout = findViewById(R.id.tabs);
- new TabLayoutMediator(tabLayout, mViewPager, (tab, position) -> {
+ new TabLayoutMediator(aboutBinding.tabs, aboutBinding.container, (tab, position) -> {
switch (position) {
default:
case POS_ABOUT:
@@ -143,33 +127,28 @@ public static AboutFragment newInstance() {
}
@Override
- public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
+ public View onCreateView(@NonNull final LayoutInflater inflater, final ViewGroup container,
final Bundle savedInstanceState) {
- final View rootView = inflater.inflate(R.layout.fragment_about, container, false);
- final Context context = this.getContext();
+ final FragmentAboutBinding aboutBinding =
+ FragmentAboutBinding.inflate(inflater, container, false);
+ final Context context = getContext();
- final TextView version = rootView.findViewById(R.id.app_version);
- version.setText(BuildConfig.VERSION_NAME);
+ aboutBinding.appVersion.setText(BuildConfig.VERSION_NAME);
- final View githubLink = rootView.findViewById(R.id.github_link);
- githubLink.setOnClickListener(nv ->
+ aboutBinding.githubLink.setOnClickListener(nv ->
openUrlInBrowser(context, context.getString(R.string.github_url)));
- final View donationLink = rootView.findViewById(R.id.donation_link);
- donationLink.setOnClickListener(v ->
+ aboutBinding.donationLink.setOnClickListener(v ->
openUrlInBrowser(context, context.getString(R.string.donation_url)));
- final View websiteLink = rootView.findViewById(R.id.website_link);
- websiteLink.setOnClickListener(nv ->
+ aboutBinding.websiteLink.setOnClickListener(nv ->
openUrlInBrowser(context, context.getString(R.string.website_url)));
- final View privacyPolicyLink = rootView.findViewById(R.id.privacy_policy_link);
- privacyPolicyLink.setOnClickListener(v ->
+ aboutBinding.privacyPolicyLink.setOnClickListener(v ->
openUrlInBrowser(context, context.getString(R.string.privacy_policy_url)));
- return rootView;
+ return aboutBinding.getRoot();
}
-
}
/**
diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistStreamEntry.kt b/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistStreamEntry.kt
index d9c892099aa..aff6205f27e 100644
--- a/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistStreamEntry.kt
+++ b/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistStreamEntry.kt
@@ -9,7 +9,7 @@ import org.schabi.newpipe.database.stream.model.StreamStateEntity
import org.schabi.newpipe.extractor.stream.StreamInfoItem
import kotlin.jvm.Throws
-class PlaylistStreamEntry(
+data class PlaylistStreamEntry(
@Embedded
val streamEntity: StreamEntity,
diff --git a/app/src/main/java/org/schabi/newpipe/download/DownloadActivity.java b/app/src/main/java/org/schabi/newpipe/download/DownloadActivity.java
index 979f8be7533..37eefed96c6 100644
--- a/app/src/main/java/org/schabi/newpipe/download/DownloadActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/download/DownloadActivity.java
@@ -9,10 +9,10 @@
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
-import androidx.appcompat.widget.Toolbar;
import androidx.fragment.app.FragmentTransaction;
import org.schabi.newpipe.R;
+import org.schabi.newpipe.databinding.ActivityDownloaderBinding;
import org.schabi.newpipe.util.DeviceUtils;
import org.schabi.newpipe.util.ThemeHelper;
import org.schabi.newpipe.views.FocusOverlayView;
@@ -35,11 +35,14 @@ protected void onCreate(final Bundle savedInstanceState) {
assureCorrectAppLanguage(this);
ThemeHelper.setTheme(this);
+
super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_downloader);
- final Toolbar toolbar = findViewById(R.id.toolbar);
- setSupportActionBar(toolbar);
+ final ActivityDownloaderBinding downloaderBinding =
+ ActivityDownloaderBinding.inflate(getLayoutInflater());
+ setContentView(downloaderBinding.getRoot());
+
+ setSupportActionBar(downloaderBinding.toolbarLayout.toolbar);
final ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java
index 866b324ecbe..a77109f86f2 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java
@@ -3,7 +3,6 @@
import android.content.Context;
import android.content.res.ColorStateList;
import android.os.Bundle;
-import androidx.preference.PreferenceManager;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
@@ -19,6 +18,7 @@
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentStatePagerAdapterMenuWorkaround;
+import androidx.preference.PreferenceManager;
import androidx.viewpager.widget.ViewPager;
import com.google.android.material.tabs.TabLayout;
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java
index 427cff06ee2..6560ab404d2 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java
@@ -16,7 +16,6 @@
import android.os.Handler;
import android.os.Looper;
import android.provider.Settings;
-import android.text.TextUtils;
import android.text.util.Linkify;
import android.util.DisplayMetrics;
import android.util.Log;
@@ -122,12 +121,14 @@
import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.rxjava3.schedulers.Schedulers;
+import static android.text.TextUtils.isEmpty;
import static org.schabi.newpipe.extractor.StreamingService.ServiceInfo.MediaCapability.COMMENTS;
import static org.schabi.newpipe.extractor.stream.StreamExtractor.NO_AGE_LIMIT;
import static org.schabi.newpipe.player.helper.PlayerHelper.globalScreenOrientationLocked;
import static org.schabi.newpipe.player.helper.PlayerHelper.isClearingQueueConfirmationRequired;
import static org.schabi.newpipe.player.playqueue.PlayQueueItem.RECOVERY_UNSET;
import static org.schabi.newpipe.util.AnimationUtils.animateView;
+import static org.schabi.newpipe.util.ExtractorHelper.showMetaInfoInTextView;
public final class VideoDetailFragment
extends BaseStateFragment
@@ -218,6 +219,9 @@ public final class VideoDetailFragment
private TextView detailDurationView;
private TextView detailPositionView;
+ private View detailMetaInfoSeparator;
+ private TextView detailMetaInfoTextView;
+
private LinearLayout videoDescriptionRootLayout;
private TextView videoUploadDateView;
private TextView videoDescriptionView;
@@ -508,8 +512,8 @@ public void onClick(final View v) {
}
break;
case R.id.detail_uploader_root_layout:
- if (TextUtils.isEmpty(currentInfo.getSubChannelUrl())) {
- if (!TextUtils.isEmpty(currentInfo.getUploaderUrl())) {
+ if (isEmpty(currentInfo.getSubChannelUrl())) {
+ if (!isEmpty(currentInfo.getUploaderUrl())) {
openChannel(currentInfo.getUploaderUrl(), currentInfo.getUploaderName());
}
@@ -583,7 +587,7 @@ public boolean onLongClick(final View v) {
}
break;
case R.id.detail_uploader_root_layout:
- if (TextUtils.isEmpty(currentInfo.getSubChannelUrl())) {
+ if (isEmpty(currentInfo.getSubChannelUrl())) {
Log.w(TAG,
"Can't open parent channel because we got no parent channel URL");
} else {
@@ -644,6 +648,9 @@ protected void initViews(final View rootView, final Bundle savedInstanceState) {
detailDurationView = rootView.findViewById(R.id.detail_duration_view);
detailPositionView = rootView.findViewById(R.id.detail_position_view);
+ detailMetaInfoSeparator = rootView.findViewById(R.id.detail_meta_info_separator);
+ detailMetaInfoTextView = rootView.findViewById(R.id.detail_meta_info_text_view);
+
videoDescriptionRootLayout = rootView.findViewById(R.id.detail_description_root_layout);
videoUploadDateView = rootView.findViewById(R.id.detail_upload_date_view);
videoDescriptionView = rootView.findViewById(R.id.detail_description_view);
@@ -748,7 +755,7 @@ private View.OnTouchListener getOnControlsTouchListener() {
private void initThumbnailViews(@NonNull final StreamInfo info) {
thumbnailImageView.setImageResource(R.drawable.dummy_thumbnail_dark);
- if (!TextUtils.isEmpty(info.getThumbnailUrl())) {
+ if (!isEmpty(info.getThumbnailUrl())) {
final String infoServiceName = NewPipe.getNameOfService(info.getServiceId());
final ImageLoadingListener onFailListener = new SimpleImageLoadingListener() {
@Override
@@ -763,12 +770,12 @@ public void onLoadingFailed(final String imageUri, final View view,
ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS, onFailListener);
}
- if (!TextUtils.isEmpty(info.getSubChannelAvatarUrl())) {
+ if (!isEmpty(info.getSubChannelAvatarUrl())) {
IMAGE_LOADER.displayImage(info.getSubChannelAvatarUrl(), subChannelThumb,
ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS);
}
- if (!TextUtils.isEmpty(info.getUploaderAvatarUrl())) {
+ if (!isEmpty(info.getUploaderAvatarUrl())) {
IMAGE_LOADER.displayImage(info.getUploaderAvatarUrl(), uploaderThumb,
ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS);
}
@@ -1217,7 +1224,7 @@ private void makeDefaultHeightForVideoPlaceholder() {
}
private void prepareDescription(final Description description) {
- if (description == null || TextUtils.isEmpty(description.getContent())
+ if (description == null || isEmpty(description.getContent())
|| description == Description.emptyDescription) {
return;
}
@@ -1462,9 +1469,9 @@ public void handleResult(@NonNull final StreamInfo info) {
animateView(thumbnailPlayButton, true, 200);
videoTitleTextView.setText(title);
- if (!TextUtils.isEmpty(info.getSubChannelName())) {
+ if (!isEmpty(info.getSubChannelName())) {
displayBothUploaderAndSubChannel(info);
- } else if (!TextUtils.isEmpty(info.getUploaderName())) {
+ } else if (!isEmpty(info.getUploaderName())) {
displayUploaderAsSubChannel(info);
} else {
uploaderTextView.setVisibility(View.GONE);
@@ -1559,6 +1566,8 @@ public void handleResult(@NonNull final StreamInfo info) {
prepareDescription(info.getDescription());
updateProgressInfo(info);
initThumbnailViews(info);
+ showMetaInfoInTextView(info.getMetaInfo(), detailMetaInfoTextView, detailMetaInfoSeparator);
+
if (player == null || player.isPlayerStopped()) {
updateOverlayData(info.getName(), info.getUploaderName(), info.getThumbnailUrl());
@@ -1610,7 +1619,7 @@ private void displayBothUploaderAndSubChannel(final StreamInfo info) {
subChannelThumb.setVisibility(View.VISIBLE);
- if (!TextUtils.isEmpty(info.getUploaderName())) {
+ if (!isEmpty(info.getUploaderName())) {
uploaderTextView.setText(
String.format(getString(R.string.video_detail_by), info.getUploaderName()));
uploaderTextView.setVisibility(View.VISIBLE);
@@ -2305,10 +2314,10 @@ public void onSlide(@NonNull final View bottomSheet, final float slideOffset) {
private void updateOverlayData(@Nullable final String overlayTitle,
@Nullable final String uploader,
@Nullable final String thumbnailUrl) {
- overlayTitleTextView.setText(TextUtils.isEmpty(overlayTitle) ? "" : overlayTitle);
- overlayChannelTextView.setText(TextUtils.isEmpty(uploader) ? "" : uploader);
+ overlayTitleTextView.setText(isEmpty(title) ? "" : title);
+ overlayChannelTextView.setText(isEmpty(uploader) ? "" : uploader);
overlayThumbnailImageView.setImageResource(R.drawable.dummy_thumbnail_dark);
- if (!TextUtils.isEmpty(thumbnailUrl)) {
+ if (!isEmpty(thumbnailUrl)) {
IMAGE_LOADER.displayImage(thumbnailUrl, overlayThumbnailImageView,
ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS, null);
}
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java
index d1a964fb2b5..6fa7eb700b9 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java
@@ -11,12 +11,12 @@
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.content.res.AppCompatResources;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
@@ -26,8 +26,10 @@
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.ListExtractor;
import org.schabi.newpipe.extractor.NewPipe;
+import org.schabi.newpipe.extractor.ServiceList;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
+import org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.extractor.stream.StreamType;
import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
@@ -44,13 +46,13 @@
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.ShareUtils;
import org.schabi.newpipe.util.StreamDialogEntry;
-import org.schabi.newpipe.util.ThemeHelper;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
+import de.hdodenhof.circleimageview.CircleImageView;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Flowable;
import io.reactivex.rxjava3.core.Single;
@@ -58,6 +60,7 @@
import io.reactivex.rxjava3.disposables.Disposable;
import static org.schabi.newpipe.util.AnimationUtils.animateView;
+import static org.schabi.newpipe.util.ThemeHelper.resolveResourceIdFromAttr;
public class PlaylistFragment extends BaseListInfoFragment {
private CompositeDisposable disposables;
@@ -74,7 +77,7 @@ public class PlaylistFragment extends BaseListInfoFragment {
private TextView headerTitleView;
private View headerUploaderLayout;
private TextView headerUploaderName;
- private ImageView headerUploaderAvatar;
+ private CircleImageView headerUploaderAvatar;
private TextView headerStreamCount;
private View playlistCtrl;
@@ -301,8 +304,22 @@ public void handleResult(@NonNull final PlaylistInfo result) {
playlistCtrl.setVisibility(View.VISIBLE);
- IMAGE_LOADER.displayImage(result.getUploaderAvatarUrl(), headerUploaderAvatar,
- ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS);
+ final String avatarUrl = result.getUploaderAvatarUrl();
+ if (result.getServiceId() == ServiceList.YouTube.getServiceId()
+ && (YoutubeParsingHelper.isYoutubeMixId(result.getId())
+ || YoutubeParsingHelper.isYoutubeMusicMixId(result.getId()))) {
+ // this is an auto-generated playlist (e.g. Youtube mix), so a radio is shown
+ headerUploaderAvatar.setDisableCircularTransformation(true);
+ headerUploaderAvatar.setBorderColor(
+ getResources().getColor(R.color.transparent_background_color));
+ headerUploaderAvatar.setImageDrawable(AppCompatResources.getDrawable(requireContext(),
+ resolveResourceIdFromAttr(requireContext(), R.attr.ic_radio)));
+
+ } else {
+ IMAGE_LOADER.displayImage(avatarUrl, headerUploaderAvatar,
+ ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS);
+ }
+
headerStreamCount.setText(Localization
.localizeStreamCount(getContext(), result.getStreamCount()));
@@ -476,7 +493,7 @@ private void updateBookmarkButtons() {
final int titleRes = playlistEntity == null
? R.string.bookmark_playlist : R.string.unbookmark_playlist;
- playlistBookmarkButton.setIcon(ThemeHelper.resolveResourceIdFromAttr(activity, iconAttr));
+ playlistBookmarkButton.setIcon(resolveResourceIdFromAttr(activity, iconAttr));
playlistBookmarkButton.setTitle(titleRes);
}
}
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java
index 02dbf176bc5..2dac6d11b38 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java
@@ -39,6 +39,7 @@
import org.schabi.newpipe.database.history.model.SearchHistoryEntry;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.ListExtractor;
+import org.schabi.newpipe.extractor.MetaInfo;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.Page;
import org.schabi.newpipe.extractor.StreamingService;
@@ -79,6 +80,7 @@
import static androidx.recyclerview.widget.ItemTouchHelper.Callback.makeMovementFlags;
import static java.util.Arrays.asList;
import static org.schabi.newpipe.util.AnimationUtils.animateView;
+import static org.schabi.newpipe.util.ExtractorHelper.showMetaInfoInTextView;
public class SearchFragment extends BaseListFragment>
implements BackPressable {
@@ -129,6 +131,9 @@ public class SearchFragment extends BaseListFragment cannot be bundled without creating some containers
+ metaInfo = new MetaInfo[result.getMetaInfo().size()];
+ metaInfo = result.getMetaInfo().toArray(metaInfo);
+
handleSearchSuggestion();
+ showMetaInfoInTextView(result.getMetaInfo(), metaInfoTextView, metaInfoSeparator);
+
lastSearchedString = searchString;
nextPage = result.getNextPage();
diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadService.kt b/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadService.kt
index ddbbea23dfe..2a0aa1c90fd 100644
--- a/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadService.kt
+++ b/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadService.kt
@@ -30,6 +30,7 @@ import android.os.IBinder
import android.util.Log
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
+import androidx.core.app.ServiceCompat
import androidx.preference.PreferenceManager
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.core.Flowable
@@ -147,7 +148,7 @@ class FeedLoadService : Service() {
private fun stopService() {
disposeAll()
- stopForeground(true)
+ ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE)
notificationManager.cancel(NOTIFICATION_ID)
stopSelf()
}
diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/services/BaseImportExportService.java b/app/src/main/java/org/schabi/newpipe/local/subscription/services/BaseImportExportService.java
index 73c0d23a031..34543b56587 100644
--- a/app/src/main/java/org/schabi/newpipe/local/subscription/services/BaseImportExportService.java
+++ b/app/src/main/java/org/schabi/newpipe/local/subscription/services/BaseImportExportService.java
@@ -31,6 +31,7 @@
import androidx.annotation.StringRes;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
+import androidx.core.app.ServiceCompat;
import org.reactivestreams.Publisher;
import org.schabi.newpipe.R;
@@ -162,7 +163,7 @@ protected void stopAndReportError(@Nullable final Throwable error, final String
protected void postErrorResult(final String title, final String text) {
disposeAll();
- stopForeground(true);
+ ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE);
stopSelf();
if (title == null) {
diff --git a/app/src/main/java/org/schabi/newpipe/player/NotificationUtil.java b/app/src/main/java/org/schabi/newpipe/player/NotificationUtil.java
index 860ace84c31..c1c2e4eba87 100644
--- a/app/src/main/java/org/schabi/newpipe/player/NotificationUtil.java
+++ b/app/src/main/java/org/schabi/newpipe/player/NotificationUtil.java
@@ -15,6 +15,7 @@
import androidx.annotation.StringRes;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
+import androidx.core.app.ServiceCompat;
import androidx.core.content.ContextCompat;
import org.schabi.newpipe.MainActivity;
@@ -188,7 +189,7 @@ void createNotificationAndStartForeground(final VideoPlayerImpl player, final Se
}
void cancelNotificationAndStopForeground(final Service service) {
- service.stopForeground(true);
+ ServiceCompat.stopForeground(service, ServiceCompat.STOP_FOREGROUND_REMOVE);
if (notificationManager != null) {
notificationManager.cancel(NOTIFICATION_ID);
diff --git a/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java b/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java
index d1c2be014eb..fd20fd175b2 100644
--- a/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java
@@ -1,6 +1,7 @@
package org.schabi.newpipe.player;
import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
@@ -11,15 +12,11 @@
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.ImageButton;
-import android.widget.LinearLayout;
import android.widget.PopupMenu;
-import android.widget.ProgressBar;
import android.widget.SeekBar;
-import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
-import androidx.appcompat.widget.Toolbar;
+import androidx.core.app.ActivityCompat;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
@@ -28,6 +25,7 @@
import com.google.android.exoplayer2.Player;
import org.schabi.newpipe.R;
+import org.schabi.newpipe.databinding.ActivityPlayerQueueControlBinding;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.fragments.OnScrollBelowItemsListener;
import org.schabi.newpipe.local.dialog.PlaylistAppendDialog;
@@ -69,30 +67,10 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
// Views
////////////////////////////////////////////////////////////////////////////
- private View rootView;
+ private ActivityPlayerQueueControlBinding queueControlBinding;
- private RecyclerView itemsList;
private ItemTouchHelper itemTouchHelper;
- private LinearLayout metadata;
- private TextView metadataTitle;
- private TextView metadataArtist;
-
- private SeekBar progressSeekBar;
- private TextView progressCurrentTime;
- private TextView progressEndTime;
- private TextView progressLiveSync;
- private TextView seekDisplay;
-
- private ImageButton repeatButton;
- private ImageButton backwardButton;
- private ImageButton fastRewindButton;
- private ImageButton playPauseButton;
- private ImageButton fastForwardButton;
- private ImageButton forwardButton;
- private ImageButton shuffleButton;
- private ProgressBar progressBar;
-
private Menu menu;
////////////////////////////////////////////////////////////////////////////
@@ -122,11 +100,11 @@ protected void onCreate(final Bundle savedInstanceState) {
assureCorrectAppLanguage(this);
super.onCreate(savedInstanceState);
ThemeHelper.setTheme(this);
- setContentView(R.layout.activity_player_queue_control);
- rootView = findViewById(R.id.main_content);
- final Toolbar toolbar = rootView.findViewById(R.id.toolbar);
- setSupportActionBar(toolbar);
+ queueControlBinding = ActivityPlayerQueueControlBinding.inflate(getLayoutInflater());
+ setContentView(queueControlBinding.getRoot());
+
+ setSupportActionBar(queueControlBinding.toolbar);
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setTitle(getSupportActionTitle());
@@ -140,7 +118,7 @@ protected void onCreate(final Bundle savedInstanceState) {
protected void onResume() {
super.onResume();
if (redraw) {
- recreate();
+ ActivityCompat.recreate(this);
redraw = false;
}
}
@@ -229,14 +207,11 @@ private void unbind() {
if (player != null && player.getPlayQueueAdapter() != null) {
player.getPlayQueueAdapter().unsetSelectedListener();
}
- if (itemsList != null) {
- itemsList.setAdapter(null);
- }
+ queueControlBinding.playQueue.setAdapter(null);
if (itemTouchHelper != null) {
itemTouchHelper.attachToRecyclerView(null);
}
- itemsList = null;
itemTouchHelper = null;
player = null;
}
@@ -283,58 +258,38 @@ private void buildComponents() {
}
private void buildQueue() {
- itemsList = findViewById(R.id.play_queue);
- itemsList.setLayoutManager(new LinearLayoutManager(this));
- itemsList.setAdapter(player.getPlayQueueAdapter());
- itemsList.setClickable(true);
- itemsList.setLongClickable(true);
- itemsList.clearOnScrollListeners();
- itemsList.addOnScrollListener(getQueueScrollListener());
+ queueControlBinding.playQueue.setLayoutManager(new LinearLayoutManager(this));
+ queueControlBinding.playQueue.setAdapter(player.getPlayQueueAdapter());
+ queueControlBinding.playQueue.setClickable(true);
+ queueControlBinding.playQueue.setLongClickable(true);
+ queueControlBinding.playQueue.clearOnScrollListeners();
+ queueControlBinding.playQueue.addOnScrollListener(getQueueScrollListener());
itemTouchHelper = new ItemTouchHelper(getItemTouchCallback());
- itemTouchHelper.attachToRecyclerView(itemsList);
+ itemTouchHelper.attachToRecyclerView(queueControlBinding.playQueue);
player.getPlayQueueAdapter().setSelectedListener(getOnSelectedListener());
}
private void buildMetadata() {
- metadata = rootView.findViewById(R.id.metadata);
- metadataTitle = rootView.findViewById(R.id.song_name);
- metadataArtist = rootView.findViewById(R.id.artist_name);
-
- metadata.setOnClickListener(this);
- metadataTitle.setSelected(true);
- metadataArtist.setSelected(true);
+ queueControlBinding.metadata.setOnClickListener(this);
+ queueControlBinding.songName.setSelected(true);
+ queueControlBinding.artistName.setSelected(true);
}
private void buildSeekBar() {
- progressCurrentTime = rootView.findViewById(R.id.current_time);
- progressSeekBar = rootView.findViewById(R.id.seek_bar);
- progressEndTime = rootView.findViewById(R.id.end_time);
- progressLiveSync = rootView.findViewById(R.id.live_sync);
- seekDisplay = rootView.findViewById(R.id.seek_display);
-
- progressSeekBar.setOnSeekBarChangeListener(this);
- progressLiveSync.setOnClickListener(this);
+ queueControlBinding.seekBar.setOnSeekBarChangeListener(this);
+ queueControlBinding.liveSync.setOnClickListener(this);
}
private void buildControls() {
- repeatButton = rootView.findViewById(R.id.control_repeat);
- backwardButton = rootView.findViewById(R.id.control_backward);
- fastRewindButton = rootView.findViewById(R.id.control_fast_rewind);
- playPauseButton = rootView.findViewById(R.id.control_play_pause);
- fastForwardButton = rootView.findViewById(R.id.control_fast_forward);
- forwardButton = rootView.findViewById(R.id.control_forward);
- shuffleButton = rootView.findViewById(R.id.control_shuffle);
- progressBar = rootView.findViewById(R.id.control_progress_bar);
-
- repeatButton.setOnClickListener(this);
- backwardButton.setOnClickListener(this);
- fastRewindButton.setOnClickListener(this);
- playPauseButton.setOnClickListener(this);
- fastForwardButton.setOnClickListener(this);
- forwardButton.setOnClickListener(this);
- shuffleButton.setOnClickListener(this);
+ queueControlBinding.controlRepeat.setOnClickListener(this);
+ queueControlBinding.controlBackward.setOnClickListener(this);
+ queueControlBinding.controlFastRewind.setOnClickListener(this);
+ queueControlBinding.controlPlayPause.setOnClickListener(this);
+ queueControlBinding.controlFastForward.setOnClickListener(this);
+ queueControlBinding.controlForward.setOnClickListener(this);
+ queueControlBinding.controlShuffle.setOnClickListener(this);
}
private void buildItemPopupMenu(final PlayQueueItem item, final View view) {
@@ -390,8 +345,8 @@ public void onScrolledDown(final RecyclerView recyclerView) {
if (player != null && player.getPlayQueue() != null
&& !player.getPlayQueue().isComplete()) {
player.getPlayQueue().fetch();
- } else if (itemsList != null) {
- itemsList.clearOnScrollListeners();
+ } else {
+ queueControlBinding.playQueue.clearOnScrollListeners();
}
}
};
@@ -452,8 +407,9 @@ private void scrollToSelected() {
final int currentPlayingIndex = player.getPlayQueue().getIndex();
final int currentVisibleIndex;
- if (itemsList.getLayoutManager() instanceof LinearLayoutManager) {
- final LinearLayoutManager layout = ((LinearLayoutManager) itemsList.getLayoutManager());
+ if (queueControlBinding.playQueue.getLayoutManager() instanceof LinearLayoutManager) {
+ final LinearLayoutManager layout =
+ (LinearLayoutManager) queueControlBinding.playQueue.getLayoutManager();
currentVisibleIndex = layout.findFirstVisibleItemPosition();
} else {
currentVisibleIndex = 0;
@@ -461,9 +417,9 @@ private void scrollToSelected() {
final int distance = Math.abs(currentPlayingIndex - currentVisibleIndex);
if (distance < SMOOTH_SCROLL_MAXIMUM_DISTANCE) {
- itemsList.smoothScrollToPosition(currentPlayingIndex);
+ queueControlBinding.playQueue.smoothScrollToPosition(currentPlayingIndex);
} else {
- itemsList.scrollToPosition(currentPlayingIndex);
+ queueControlBinding.playQueue.scrollToPosition(currentPlayingIndex);
}
}
@@ -477,23 +433,23 @@ public void onClick(final View view) {
return;
}
- if (view.getId() == repeatButton.getId()) {
+ if (view.getId() == queueControlBinding.controlRepeat.getId()) {
player.onRepeatClicked();
- } else if (view.getId() == backwardButton.getId()) {
+ } else if (view.getId() == queueControlBinding.controlBackward.getId()) {
player.onPlayPrevious();
- } else if (view.getId() == fastRewindButton.getId()) {
+ } else if (view.getId() == queueControlBinding.controlFastRewind.getId()) {
player.onFastRewind();
- } else if (view.getId() == playPauseButton.getId()) {
+ } else if (view.getId() == queueControlBinding.controlPlayPause.getId()) {
player.onPlayPause();
- } else if (view.getId() == fastForwardButton.getId()) {
+ } else if (view.getId() == queueControlBinding.controlFastForward.getId()) {
player.onFastForward();
- } else if (view.getId() == forwardButton.getId()) {
+ } else if (view.getId() == queueControlBinding.controlForward.getId()) {
player.onPlayNext();
- } else if (view.getId() == shuffleButton.getId()) {
+ } else if (view.getId() == queueControlBinding.controlShuffle.getId()) {
player.onShuffleClicked();
- } else if (view.getId() == metadata.getId()) {
+ } else if (view.getId() == queueControlBinding.metadata.getId()) {
scrollToSelected();
- } else if (view.getId() == progressLiveSync.getId()) {
+ } else if (view.getId() == queueControlBinding.liveSync.getId()) {
player.seekToDefault();
}
}
@@ -527,15 +483,15 @@ public void onProgressChanged(final SeekBar seekBar, final int progress,
final boolean fromUser) {
if (fromUser) {
final String seekTime = Localization.getDurationString(progress / 1000);
- progressCurrentTime.setText(seekTime);
- seekDisplay.setText(seekTime);
+ queueControlBinding.currentTime.setText(seekTime);
+ queueControlBinding.seekDisplay.setText(seekTime);
}
}
@Override
public void onStartTrackingTouch(final SeekBar seekBar) {
seeking = true;
- seekDisplay.setVisibility(View.VISIBLE);
+ queueControlBinding.seekDisplay.setVisibility(View.VISIBLE);
}
@Override
@@ -543,7 +499,7 @@ public void onStopTrackingTouch(final SeekBar seekBar) {
if (player != null) {
player.seekTo(seekBar.getProgress());
}
- seekDisplay.setVisibility(View.GONE);
+ queueControlBinding.seekDisplay.setVisibility(View.GONE);
seeking = false;
}
@@ -601,45 +557,46 @@ public void onPlaybackUpdate(final int state, final int repeatMode, final boolea
public void onProgressUpdate(final int currentProgress, final int duration,
final int bufferPercent) {
// Set buffer progress
- progressSeekBar.setSecondaryProgress((int) (progressSeekBar.getMax()
+ queueControlBinding.seekBar.setSecondaryProgress((int) (queueControlBinding.seekBar.getMax()
* ((float) bufferPercent / 100)));
// Set Duration
- progressSeekBar.setMax(duration);
- progressEndTime.setText(Localization.getDurationString(duration / 1000));
+ queueControlBinding.seekBar.setMax(duration);
+ queueControlBinding.endTime.setText(Localization.getDurationString(duration / 1000));
// Set current time if not seeking
if (!seeking) {
- progressSeekBar.setProgress(currentProgress);
- progressCurrentTime.setText(Localization.getDurationString(currentProgress / 1000));
+ queueControlBinding.seekBar.setProgress(currentProgress);
+ queueControlBinding.currentTime.setText(Localization
+ .getDurationString(currentProgress / 1000));
}
if (player != null) {
- progressLiveSync.setClickable(!player.isLiveEdge());
+ queueControlBinding.liveSync.setClickable(!player.isLiveEdge());
}
// this will make sure progressCurrentTime has the same width as progressEndTime
- final ViewGroup.LayoutParams endTimeParams = progressEndTime.getLayoutParams();
- final ViewGroup.LayoutParams currentTimeParams = progressCurrentTime.getLayoutParams();
- currentTimeParams.width = progressEndTime.getWidth();
- progressCurrentTime.setLayoutParams(currentTimeParams);
+ final ViewGroup.LayoutParams currentTimeParams =
+ queueControlBinding.currentTime.getLayoutParams();
+ currentTimeParams.width = queueControlBinding.endTime.getWidth();
+ queueControlBinding.currentTime.setLayoutParams(currentTimeParams);
}
@Override
public void onMetadataUpdate(final StreamInfo info, final PlayQueue queue) {
if (info != null) {
- metadataTitle.setText(info.getName());
- metadataArtist.setText(info.getUploaderName());
+ queueControlBinding.songName.setText(info.getName());
+ queueControlBinding.artistName.setText(info.getUploaderName());
- progressEndTime.setVisibility(View.GONE);
- progressLiveSync.setVisibility(View.GONE);
+ queueControlBinding.endTime.setVisibility(View.GONE);
+ queueControlBinding.liveSync.setVisibility(View.GONE);
switch (info.getStreamType()) {
case LIVE_STREAM:
case AUDIO_LIVE_STREAM:
- progressLiveSync.setVisibility(View.VISIBLE);
+ queueControlBinding.liveSync.setVisibility(View.VISIBLE);
break;
default:
- progressEndTime.setVisibility(View.VISIBLE);
+ queueControlBinding.endTime.setVisibility(View.VISIBLE);
break;
}
@@ -660,13 +617,16 @@ public void onServiceStopped() {
private void onStateChanged(final int state) {
switch (state) {
case BasePlayer.STATE_PAUSED:
- playPauseButton.setImageResource(R.drawable.ic_play_arrow_white_24dp);
+ queueControlBinding.controlPlayPause
+ .setImageResource(R.drawable.ic_play_arrow_white_24dp);
break;
case BasePlayer.STATE_PLAYING:
- playPauseButton.setImageResource(R.drawable.ic_pause_white_24dp);
+ queueControlBinding.controlPlayPause
+ .setImageResource(R.drawable.ic_pause_white_24dp);
break;
case BasePlayer.STATE_COMPLETED:
- playPauseButton.setImageResource(R.drawable.ic_replay_white_24dp);
+ queueControlBinding.controlPlayPause
+ .setImageResource(R.drawable.ic_replay_white_24dp);
break;
default:
break;
@@ -676,14 +636,14 @@ private void onStateChanged(final int state) {
case BasePlayer.STATE_PAUSED:
case BasePlayer.STATE_PLAYING:
case BasePlayer.STATE_COMPLETED:
- playPauseButton.setClickable(true);
- playPauseButton.setVisibility(View.VISIBLE);
- progressBar.setVisibility(View.GONE);
+ queueControlBinding.controlPlayPause.setClickable(true);
+ queueControlBinding.controlPlayPause.setVisibility(View.VISIBLE);
+ queueControlBinding.controlProgressBar.setVisibility(View.GONE);
break;
default:
- playPauseButton.setClickable(false);
- playPauseButton.setVisibility(View.INVISIBLE);
- progressBar.setVisibility(View.VISIBLE);
+ queueControlBinding.controlPlayPause.setClickable(false);
+ queueControlBinding.controlPlayPause.setVisibility(View.INVISIBLE);
+ queueControlBinding.controlProgressBar.setVisibility(View.VISIBLE);
break;
}
}
@@ -691,18 +651,21 @@ private void onStateChanged(final int state) {
private void onPlayModeChanged(final int repeatMode, final boolean shuffled) {
switch (repeatMode) {
case Player.REPEAT_MODE_OFF:
- repeatButton.setImageResource(R.drawable.exo_controls_repeat_off);
+ queueControlBinding.controlRepeat
+ .setImageResource(R.drawable.exo_controls_repeat_off);
break;
case Player.REPEAT_MODE_ONE:
- repeatButton.setImageResource(R.drawable.exo_controls_repeat_one);
+ queueControlBinding.controlRepeat
+ .setImageResource(R.drawable.exo_controls_repeat_one);
break;
case Player.REPEAT_MODE_ALL:
- repeatButton.setImageResource(R.drawable.exo_controls_repeat_all);
+ queueControlBinding.controlRepeat
+ .setImageResource(R.drawable.exo_controls_repeat_all);
break;
}
final int shuffleAlpha = shuffled ? 255 : 77;
- shuffleButton.setImageAlpha(shuffleAlpha);
+ queueControlBinding.controlShuffle.setImageAlpha(shuffleAlpha);
}
private void onPlaybackParameterChanged(final PlaybackParameters parameters) {
@@ -715,12 +678,13 @@ private void onPlaybackParameterChanged(final PlaybackParameters parameters) {
}
private void onMaybePlaybackAdapterChanged() {
- if (itemsList == null || player == null) {
+ if (player == null) {
return;
}
final PlayQueueAdapter maybeNewAdapter = player.getPlayQueueAdapter();
- if (maybeNewAdapter != null && itemsList.getAdapter() != maybeNewAdapter) {
- itemsList.setAdapter(maybeNewAdapter);
+ if (maybeNewAdapter != null
+ && queueControlBinding.playQueue.getAdapter() != maybeNewAdapter) {
+ queueControlBinding.playQueue.setAdapter(maybeNewAdapter);
}
}
@@ -734,7 +698,8 @@ private void onMaybeMuteChanged() {
//2) Icon change accordingly to current App Theme
// using rootView.getContext() because getApplicationContext() didn't work
- item.setIcon(ThemeHelper.resolveResourceIdFromAttr(rootView.getContext(),
+ final Context context = queueControlBinding.getRoot().getContext();
+ item.setIcon(ThemeHelper.resolveResourceIdFromAttr(context,
player.isMuted()
? R.attr.ic_volume_off
: R.attr.ic_volume_up));
diff --git a/app/src/main/java/org/schabi/newpipe/player/VideoPlayerImpl.java b/app/src/main/java/org/schabi/newpipe/player/VideoPlayerImpl.java
index 3cbcb87a35b..a304b44300b 100644
--- a/app/src/main/java/org/schabi/newpipe/player/VideoPlayerImpl.java
+++ b/app/src/main/java/org/schabi/newpipe/player/VideoPlayerImpl.java
@@ -103,6 +103,7 @@
import java.util.List;
+import static org.schabi.newpipe.extractor.ServiceList.YouTube;
import static org.schabi.newpipe.player.MainPlayer.ACTION_CLOSE;
import static org.schabi.newpipe.player.MainPlayer.ACTION_FAST_FORWARD;
import static org.schabi.newpipe.player.MainPlayer.ACTION_FAST_REWIND;
@@ -889,10 +890,17 @@ private void onMoreOptionsClicked() {
private void onShareClicked() {
// share video at the current time (youtube.com/watch?v=ID&t=SECONDS)
// Timestamp doesn't make sense in a live stream so drop it
- final String ts = isLive() ? "" : ("&t=" + (getPlaybackSeekBar().getProgress() / 1000));
+
+ final int ts = getPlaybackSeekBar().getProgress() / 1000;
+ final MediaSourceTag metadata = getCurrentMetadata();
+ String videoUrl = getVideoUrl();
+ if (!isLive() && ts >= 0 && metadata != null
+ && metadata.getMetadata().getServiceId() == YouTube.getServiceId()) {
+ videoUrl += ("&t=" + ts);
+ }
ShareUtils.shareUrl(service,
getVideoTitle(),
- getVideoUrl() + ts);
+ videoUrl);
}
private void onPlayWithKodiClicked() {
diff --git a/app/src/main/java/org/schabi/newpipe/report/ErrorActivity.java b/app/src/main/java/org/schabi/newpipe/report/ErrorActivity.java
index 3213821cd3c..a4b6af2ab01 100644
--- a/app/src/main/java/org/schabi/newpipe/report/ErrorActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/report/ErrorActivity.java
@@ -14,15 +14,11 @@
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
-import android.widget.Button;
-import android.widget.EditText;
-import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
-import androidx.appcompat.widget.Toolbar;
import androidx.core.app.NavUtils;
import com.google.android.material.snackbar.Snackbar;
@@ -34,6 +30,7 @@
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;
import org.schabi.newpipe.util.ShareUtils;
import org.schabi.newpipe.util.ThemeHelper;
@@ -87,7 +84,8 @@ public class ErrorActivity extends AppCompatActivity {
private ErrorInfo errorInfo;
private Class returnActivity;
private String currentTimeStamp;
- private EditText userCommentBox;
+
+ private ActivityErrorBinding activityErrorBinding;
public static void reportUiError(final AppCompatActivity activity, final Throwable el) {
reportError(activity, el, activity.getClass(), null, ErrorInfo.make(UserAction.UI_ERROR,
@@ -181,12 +179,13 @@ protected void onCreate(final Bundle savedInstanceState) {
assureCorrectAppLanguage(this);
super.onCreate(savedInstanceState);
ThemeHelper.setTheme(this);
- setContentView(R.layout.activity_error);
+
+ activityErrorBinding = ActivityErrorBinding.inflate(getLayoutInflater());
+ setContentView(activityErrorBinding.getRoot());
final Intent intent = getIntent();
- final Toolbar toolbar = findViewById(R.id.toolbar);
- setSupportActionBar(toolbar);
+ setSupportActionBar(activityErrorBinding.toolbarLayout.toolbar);
final ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
@@ -195,15 +194,6 @@ protected void onCreate(final Bundle savedInstanceState) {
actionBar.setDisplayShowTitleEnabled(true);
}
- final Button reportEmailButton = findViewById(R.id.errorReportEmailButton);
- final Button copyButton = findViewById(R.id.errorReportCopyButton);
- final Button reportGithubButton = findViewById(R.id.errorReportGitHubButton);
-
- userCommentBox = findViewById(R.id.errorCommentBox);
- final TextView errorView = findViewById(R.id.errorView);
- final TextView infoView = findViewById(R.id.errorInfosView);
- final TextView errorMessageView = findViewById(R.id.errorMessageView);
-
final ActivityCommunicator ac = ActivityCommunicator.getCommunicator();
returnActivity = ac.getReturnActivity();
errorInfo = intent.getParcelableExtra(ERROR_INFO);
@@ -213,28 +203,27 @@ protected void onCreate(final Bundle savedInstanceState) {
addGuruMeditation();
currentTimeStamp = getCurrentTimeStamp();
- reportEmailButton.setOnClickListener(v ->
+ activityErrorBinding.errorReportEmailButton.setOnClickListener(v ->
openPrivacyPolicyDialog(this, "EMAIL"));
- copyButton.setOnClickListener(v -> {
+ activityErrorBinding.errorReportCopyButton.setOnClickListener(v -> {
ShareUtils.copyToClipboard(this, buildMarkdown());
Toast.makeText(this, R.string.msg_copied, Toast.LENGTH_SHORT).show();
});
- reportGithubButton.setOnClickListener(v ->
+ activityErrorBinding.errorReportGitHubButton.setOnClickListener(v ->
openPrivacyPolicyDialog(this, "GITHUB"));
-
// normal bugreport
buildInfo(errorInfo);
if (errorInfo.getMessage() != 0) {
- errorMessageView.setText(errorInfo.getMessage());
+ activityErrorBinding.errorMessageView.setText(errorInfo.getMessage());
} else {
- errorMessageView.setVisibility(View.GONE);
- findViewById(R.id.messageWhatHappenedView).setVisibility(View.GONE);
+ activityErrorBinding.errorMessageView.setVisibility(View.GONE);
+ activityErrorBinding.messageWhatHappenedView.setVisibility(View.GONE);
}
- errorView.setText(formErrorText(errorList));
+ activityErrorBinding.errorView.setText(formErrorText(errorList));
// print stack trace once again for debugging:
for (final String e : errorList) {
@@ -339,11 +328,10 @@ private void goToReturnActivity() {
}
private void buildInfo(final ErrorInfo info) {
- final TextView infoLabelView = findViewById(R.id.errorInfoLabelsView);
- final TextView infoView = findViewById(R.id.errorInfosView);
String text = "";
- infoLabelView.setText(getString(R.string.info_labels).replace("\\n", "\n"));
+ activityErrorBinding.errorInfoLabelsView.setText(getString(R.string.info_labels)
+ .replace("\\n", "\n"));
text += getUserActionString(info.getUserAction()) + "\n"
+ info.getRequest() + "\n"
@@ -356,7 +344,7 @@ private void buildInfo(final ErrorInfo info) {
+ BuildConfig.VERSION_NAME + "\n"
+ getOsString();
- infoView.setText(text);
+ activityErrorBinding.errorInfosView.setText(text);
}
private String buildJson() {
@@ -374,7 +362,8 @@ private String buildJson() {
.value("os", getOsString())
.value("time", currentTimeStamp)
.array("exceptions", Arrays.asList(errorList))
- .value("user_comment", userCommentBox.getText().toString())
+ .value("user_comment", activityErrorBinding.errorCommentBox.getText()
+ .toString())
.end()
.done();
} catch (final Throwable e) {
@@ -389,7 +378,7 @@ private String buildMarkdown() {
try {
final StringBuilder htmlErrorReport = new StringBuilder();
- final String userComment = userCommentBox.getText().toString();
+ final String userComment = activityErrorBinding.errorCommentBox.getText().toString();
if (!userComment.isEmpty()) {
htmlErrorReport.append(userComment).append("\n");
}
@@ -473,10 +462,9 @@ private String getOsString() {
private void addGuruMeditation() {
//just an easter egg
- final TextView sorryView = findViewById(R.id.errorSorryView);
- String text = sorryView.getText().toString();
+ String text = activityErrorBinding.errorSorryView.getText().toString();
text += "\n" + getString(R.string.guru_meditation);
- sorryView.setText(text);
+ activityErrorBinding.errorSorryView.setText(text);
}
@Override
diff --git a/app/src/main/java/org/schabi/newpipe/settings/AppearanceSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/AppearanceSettingsFragment.java
index ab875ed5d32..8126bd2c56b 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/AppearanceSettingsFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/AppearanceSettingsFragment.java
@@ -8,6 +8,7 @@
import android.widget.Toast;
import androidx.annotation.Nullable;
+import androidx.core.app.ActivityCompat;
import androidx.preference.Preference;
import org.schabi.newpipe.R;
@@ -31,7 +32,7 @@ public boolean onPreferenceChange(final Preference preference, final Object newV
if (!newValue.equals(startThemeKey) && getActivity() != null) {
// If it's not the current theme
- getActivity().recreate();
+ ActivityCompat.recreate(requireActivity());
}
return false;
diff --git a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java
index b0425ebfaba..d7766f7b091 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java
@@ -11,6 +11,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.core.content.ContextCompat;
import androidx.preference.Preference;
import androidx.preference.PreferenceManager;
@@ -30,19 +31,15 @@
import org.schabi.newpipe.util.FilePickerActivityHelper;
import org.schabi.newpipe.util.ZipHelper;
-import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
-import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.Map;
import java.util.zip.ZipFile;
-import java.util.zip.ZipOutputStream;
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
@@ -50,6 +47,8 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
private static final int REQUEST_IMPORT_PATH = 8945;
private static final int REQUEST_EXPORT_PATH = 30945;
+ private ContentSettingsManager manager;
+
private File databasesDir;
private File newpipeDb;
private File newpipeDbJournal;
@@ -120,17 +119,18 @@ public boolean onPreferenceTreeClick(final Preference preference) {
@Override
public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
-
- final String homeDir = getActivity().getApplicationInfo().dataDir;
- databasesDir = new File(homeDir + "/databases");
- newpipeDb = new File(homeDir + "/databases/newpipe.db");
- newpipeDbJournal = new File(homeDir + "/databases/newpipe.db-journal");
- newpipeDbShm = new File(homeDir + "/databases/newpipe.db-shm");
- newpipeDbWal = new File(homeDir + "/databases/newpipe.db-wal");
-
- newpipeSettings = new File(homeDir + "/databases/newpipe.settings");
+ final File homeDir = ContextCompat.getDataDir(requireContext());
+ databasesDir = new File(homeDir, "/databases");
+ newpipeDb = new File(homeDir, "/databases/newpipe.db");
+ newpipeDbJournal = new File(homeDir, "/databases/newpipe.db-journal");
+ newpipeDbShm = new File(homeDir, "/databases/newpipe.db-shm");
+ newpipeDbWal = new File(homeDir, "/databases/newpipe.db-wal");
+
+ newpipeSettings = new File(homeDir, "/databases/newpipe.settings");
newpipeSettings.delete();
+ manager = new ContentSettingsManager(homeDir);
+
addPreferencesFromResource(R.xml.content_settings);
final Preference importDataPreference = findPreference(getString(R.string.import_data));
@@ -212,33 +212,16 @@ private void exportDatabase(final String path) {
//checkpoint before export
NewPipeDatabase.checkpoint();
- try (ZipOutputStream outZip = new ZipOutputStream(new BufferedOutputStream(
- new FileOutputStream(path)))) {
- ZipHelper.addFileToZip(outZip, newpipeDb.getPath(), "newpipe.db");
-
- saveSharedPreferencesToFile(newpipeSettings);
- ZipHelper.addFileToZip(outZip, newpipeSettings.getPath(),
- "newpipe.settings");
- }
+ final SharedPreferences preferences = PreferenceManager
+ .getDefaultSharedPreferences(requireContext());
+ manager.exportDatabase(preferences, path);
- Toast.makeText(getContext(), R.string.export_complete_toast, Toast.LENGTH_SHORT)
- .show();
+ Toast.makeText(getContext(), R.string.export_complete_toast, Toast.LENGTH_SHORT).show();
} catch (final Exception e) {
onError(e);
}
}
- private void saveSharedPreferencesToFile(final File dst) {
- try (ObjectOutputStream output = new ObjectOutputStream(new FileOutputStream(dst))) {
- final SharedPreferences pref
- = PreferenceManager.getDefaultSharedPreferences(requireContext());
- output.writeObject(pref.getAll());
- output.flush();
- } catch (final IOException e) {
- e.printStackTrace();
- }
- }
-
private void importDatabase(final String filePath) {
// check if file is supported
try (ZipFile zipFile = new ZipFile(filePath)) {
diff --git a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsManager.kt b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsManager.kt
new file mode 100644
index 00000000000..2682ac5e0a6
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsManager.kt
@@ -0,0 +1,45 @@
+package org.schabi.newpipe.settings
+
+import android.content.SharedPreferences
+import org.schabi.newpipe.util.ZipHelper
+import java.io.BufferedOutputStream
+import java.io.File
+import java.io.FileOutputStream
+import java.io.IOException
+import java.io.ObjectOutputStream
+import java.lang.Exception
+import java.util.zip.ZipOutputStream
+
+class ContentSettingsManager(
+ private val newpipeDb: File,
+ private val newpipeSettings: File
+) {
+
+ constructor(homeDir: File) : this(
+ File(homeDir, "databases/newpipe.db"),
+ File(homeDir, "databases/newpipe.settings")
+ )
+
+ /**
+ * Exports given [SharedPreferences] to the file in given outputPath.
+ * It also creates the file.
+ */
+ @Throws(Exception::class)
+ fun exportDatabase(preferences: SharedPreferences, outputPath: String) {
+ ZipOutputStream(BufferedOutputStream(FileOutputStream(outputPath)))
+ .use { outZip ->
+ ZipHelper.addFileToZip(outZip, newpipeDb.path, "newpipe.db")
+
+ try {
+ ObjectOutputStream(FileOutputStream(newpipeSettings)).use { output ->
+ output.writeObject(preferences.all)
+ output.flush()
+ }
+ } catch (e: IOException) {
+ e.printStackTrace()
+ }
+
+ ZipHelper.addFileToZip(outZip, newpipeSettings.path, "newpipe.settings")
+ }
+ }
+}
diff --git a/app/src/main/java/org/schabi/newpipe/settings/SettingsActivity.java b/app/src/main/java/org/schabi/newpipe/settings/SettingsActivity.java
index d2d4c240439..4de166a5599 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/SettingsActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/SettingsActivity.java
@@ -7,12 +7,12 @@
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
-import androidx.appcompat.widget.Toolbar;
import androidx.fragment.app.Fragment;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
import org.schabi.newpipe.R;
+import org.schabi.newpipe.databinding.SettingsLayoutBinding;
import org.schabi.newpipe.util.DeviceUtils;
import org.schabi.newpipe.util.ThemeHelper;
import org.schabi.newpipe.views.FocusOverlayView;
@@ -51,10 +51,12 @@ protected void onCreate(final Bundle savedInstanceBundle) {
setTheme(ThemeHelper.getSettingsThemeStyle(this));
assureCorrectAppLanguage(this);
super.onCreate(savedInstanceBundle);
- setContentView(R.layout.settings_layout);
- final Toolbar toolbar = findViewById(R.id.toolbar);
- setSupportActionBar(toolbar);
+ final SettingsLayoutBinding settingsLayoutBinding =
+ SettingsLayoutBinding.inflate(getLayoutInflater());
+ setContentView(settingsLayoutBinding.getRoot());
+
+ setSupportActionBar(settingsLayoutBinding.toolbarLayout.toolbar);
if (savedInstanceBundle == null) {
getSupportFragmentManager().beginTransaction()
diff --git a/app/src/main/java/org/schabi/newpipe/streams/Mp4FromDashWriter.java b/app/src/main/java/org/schabi/newpipe/streams/Mp4FromDashWriter.java
index 5efffe118bc..ca3da9d2449 100644
--- a/app/src/main/java/org/schabi/newpipe/streams/Mp4FromDashWriter.java
+++ b/app/src/main/java/org/schabi/newpipe/streams/Mp4FromDashWriter.java
@@ -483,7 +483,7 @@ private void initChunkTables(final TablesInfo tables, final int firstCount,
// stsc_table_entry = [first_chunk, samples_per_chunk, sample_description_index]
tables.stscBEntries = new int[tables.stsc * 3];
- tables.stco = remainChunkOffset + 1; // total entrys in chunk offset box
+ tables.stco = remainChunkOffset + 1; // total entries in chunk offset box
tables.stscBEntries[index++] = 1;
tables.stscBEntries[index++] = firstCount;
diff --git a/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java b/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java
index 650c5ae116d..1f1b945452a 100644
--- a/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java
+++ b/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java
@@ -22,9 +22,16 @@
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
+import android.text.method.LinkMovementMethod;
import android.util.Log;
+import android.view.View;
+import android.widget.TextView;
import android.widget.Toast;
+import androidx.annotation.Nullable;
+import androidx.core.text.HtmlCompat;
+import androidx.preference.PreferenceManager;
+
import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.R;
import org.schabi.newpipe.ReCaptchaActivity;
@@ -32,6 +39,7 @@
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.ListExtractor.InfoItemsPage;
import org.schabi.newpipe.extractor.ListInfo;
+import org.schabi.newpipe.extractor.MetaInfo;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.Page;
import org.schabi.newpipe.extractor.StreamingService;
@@ -60,6 +68,8 @@
import io.reactivex.rxjava3.core.Maybe;
import io.reactivex.rxjava3.core.Single;
+import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
+
public final class ExtractorHelper {
private static final String TAG = ExtractorHelper.class.getSimpleName();
private static final InfoCache CACHE = InfoCache.getInstance();
@@ -306,4 +316,73 @@ public static void handleGeneralException(final Context context, final int servi
}
});
}
+
+ /**
+ * Formats the text contained in the meta info list as HTML and puts it into the text view,
+ * while also making the separator visible. If the list is null or empty, or the user chose not
+ * to see meta information, both the text view and the separator are hidden
+ * @param metaInfos a list of meta information, can be null or empty
+ * @param metaInfoTextView the text view in which to show the formatted HTML
+ * @param metaInfoSeparator another view to be shown or hidden accordingly to the text view
+ */
+ public static void showMetaInfoInTextView(@Nullable final List metaInfos,
+ final TextView metaInfoTextView,
+ final View metaInfoSeparator) {
+ final Context context = metaInfoTextView.getContext();
+ final boolean showMetaInfo = PreferenceManager.getDefaultSharedPreferences(context)
+ .getBoolean(context.getString(R.string.show_meta_info_key), true);
+
+ if (!showMetaInfo || metaInfos == null || metaInfos.isEmpty()) {
+ metaInfoTextView.setVisibility(View.GONE);
+ metaInfoSeparator.setVisibility(View.GONE);
+
+ } else {
+ final StringBuilder stringBuilder = new StringBuilder();
+ for (final MetaInfo metaInfo : metaInfos) {
+ if (!isNullOrEmpty(metaInfo.getTitle())) {
+ stringBuilder.append("").append(metaInfo.getTitle()).append("")
+ .append(Localization.DOT_SEPARATOR);
+ }
+
+ String content = metaInfo.getContent().getContent().trim();
+ if (content.endsWith(".")) {
+ content = content.substring(0, content.length() - 1); // remove . at end
+ }
+ stringBuilder.append(content);
+
+ for (int i = 0; i < metaInfo.getUrls().size(); i++) {
+ if (i == 0) {
+ stringBuilder.append(Localization.DOT_SEPARATOR);
+ } else {
+ stringBuilder.append("