diff --git a/README.md b/README.md index bf1317f17..3cd7927af 100644 --- a/README.md +++ b/README.md @@ -8,13 +8,23 @@

Get it on F-Droid

- + + + + + + + - + +

+ +

+

ScreenshotsSupported ServicesDescriptionFeaturesInstallation and updatesContributionDonateLicense

WebsiteBlogFAQPress

diff --git a/app/src/main/java/org/schabi/newpipe/MainActivity.java b/app/src/main/java/org/schabi/newpipe/MainActivity.java index 95b1f4164..bf23d3d70 100644 --- a/app/src/main/java/org/schabi/newpipe/MainActivity.java +++ b/app/src/main/java/org/schabi/newpipe/MainActivity.java @@ -122,7 +122,10 @@ public class MainActivity extends AppCompatActivity { private static final int ITEM_ID_ABOUT = 2; private static final int ORDER = 0; + public static final String KEY_IS_IN_BACKGROUND = "is_in_background"; + private SharedPreferences sharedPreferences; + private SharedPreferences.Editor sharedPrefEditor; /*////////////////////////////////////////////////////////////////////////// // Activity's LifeCycle //////////////////////////////////////////////////////////////////////////*/ @@ -152,6 +155,8 @@ public class MainActivity extends AppCompatActivity { assureCorrectAppLanguage(this); super.onCreate(savedInstanceState); + sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); + sharedPrefEditor = sharedPreferences.edit(); mainBinding = ActivityMainBinding.inflate(getLayoutInflater()); drawerLayoutBinding = mainBinding.drawerLayout; @@ -195,16 +200,29 @@ public class MainActivity extends AppCompatActivity { super.onPostCreate(savedInstanceState); final App app = App.getInstance(); - final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(app); - if (prefs.getBoolean(app.getString(R.string.update_app_key), false) - && prefs.getBoolean(app.getString(R.string.update_check_consent_key), false)) { + if (sharedPreferences.getBoolean(app.getString(R.string.update_app_key), false) + && sharedPreferences + .getBoolean(app.getString(R.string.update_check_consent_key), false)) { // Start the worker which is checking all conditions // and eventually searching for a new version. NewVersionWorker.enqueueNewVersionCheckingWork(app, false); } } + @Override + protected void onStart() { + super.onStart(); + sharedPrefEditor.putBoolean(KEY_IS_IN_BACKGROUND, false).apply(); + Log.d(TAG, "App moved to foreground"); + } + + @Override + protected void onStop() { + super.onStop(); + sharedPrefEditor.putBoolean(KEY_IS_IN_BACKGROUND, true).apply(); + Log.d(TAG, "App moved to background"); + } private void setupDrawer() throws ExtractionException { addDrawerMenuForCurrentService(); @@ -504,13 +522,11 @@ public class MainActivity extends AppCompatActivity { ErrorUtil.showUiErrorSnackbar(this, "Setting up service toggle", e); } - final SharedPreferences sharedPreferences = - PreferenceManager.getDefaultSharedPreferences(this); if (sharedPreferences.getBoolean(Constants.KEY_THEME_CHANGE, false)) { if (DEBUG) { Log.d(TAG, "Theme has changed, recreating activity..."); } - sharedPreferences.edit().putBoolean(Constants.KEY_THEME_CHANGE, false).apply(); + sharedPrefEditor.putBoolean(Constants.KEY_THEME_CHANGE, false).apply(); ActivityCompat.recreate(this); } @@ -518,7 +534,7 @@ public class MainActivity extends AppCompatActivity { if (DEBUG) { Log.d(TAG, "main page has changed, recreating main fragment..."); } - sharedPreferences.edit().putBoolean(Constants.KEY_MAIN_PAGE_CHANGE, false).apply(); + sharedPrefEditor.putBoolean(Constants.KEY_MAIN_PAGE_CHANGE, false).apply(); NavigationHelper.openMainActivity(this); } diff --git a/app/src/main/java/org/schabi/newpipe/error/ErrorUtil.kt b/app/src/main/java/org/schabi/newpipe/error/ErrorUtil.kt index dcbc11413..93dd8e522 100644 --- a/app/src/main/java/org/schabi/newpipe/error/ErrorUtil.kt +++ b/app/src/main/java/org/schabi/newpipe/error/ErrorUtil.kt @@ -11,7 +11,9 @@ import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat import androidx.core.app.PendingIntentCompat import androidx.fragment.app.Fragment +import androidx.preference.PreferenceManager import com.google.android.material.snackbar.Snackbar +import org.schabi.newpipe.MainActivity import org.schabi.newpipe.R /** @@ -35,12 +37,20 @@ class ErrorUtil { * activity (since the workflow would be interrupted anyway in that case). So never use this * for background services. * + * If the crashed occurred while the app was in the background open a notification instead + * * @param context the context to use to start the new activity * @param errorInfo the error info to be reported */ @JvmStatic fun openActivity(context: Context, errorInfo: ErrorInfo) { - context.startActivity(getErrorActivityIntent(context, errorInfo)) + if (PreferenceManager.getDefaultSharedPreferences(context) + .getBoolean(MainActivity.KEY_IS_IN_BACKGROUND, true) + ) { + createNotification(context, errorInfo) + } else { + context.startActivity(getErrorActivityIntent(context, errorInfo)) + } } /** diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt b/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt index f976f44aa..038f2bed1 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt @@ -274,7 +274,12 @@ class FeedFragment : BaseStateFragment() { @Deprecated("Deprecated in Java") override fun onDestroyOptionsMenu() { super.onDestroyOptionsMenu() - activity?.supportActionBar?.subtitle = null + if ( + (groupName != "") && + (activity?.supportActionBar?.subtitle == groupName) + ) { + activity?.supportActionBar?.subtitle = null + } } override fun onDestroy() { @@ -286,7 +291,13 @@ class FeedFragment : BaseStateFragment() { } super.onDestroy() - activity?.supportActionBar?.subtitle = null + + if ( + (groupName != "") && + (activity?.supportActionBar?.subtitle == groupName) + ) { + activity?.supportActionBar?.subtitle = null + } } override fun onDestroyView() { diff --git a/app/src/main/java/org/schabi/newpipe/settings/SelectFeedGroupFragment.java b/app/src/main/java/org/schabi/newpipe/settings/SelectFeedGroupFragment.java new file mode 100644 index 000000000..662379369 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/settings/SelectFeedGroupFragment.java @@ -0,0 +1,214 @@ +package org.schabi.newpipe.settings; + +import android.content.DialogInterface; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.ProgressBar; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.DialogFragment; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import org.schabi.newpipe.NewPipeDatabase; +import org.schabi.newpipe.R; +import org.schabi.newpipe.database.AppDatabase; +import org.schabi.newpipe.database.feed.model.FeedGroupEntity; +import org.schabi.newpipe.error.ErrorUtil; +import org.schabi.newpipe.util.ThemeHelper; + +import java.util.List; +import java.util.Vector; + +import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; +import io.reactivex.rxjava3.core.Observer; +import io.reactivex.rxjava3.disposables.Disposable; +import io.reactivex.rxjava3.schedulers.Schedulers; + +/** + * Created by Christian Schabesberger on 26.09.17. + * SelectChannelFragment.java is part of NewPipe. + *

+ * NewPipe is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + *

+ *

+ * NewPipe is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + *

+ *

+ * You should have received a copy of the GNU General Public License + * along with NewPipe. If not, see . + *

+ */ + +public class SelectFeedGroupFragment extends DialogFragment { + + private OnSelectedListener onSelectedListener = null; + private OnCancelListener onCancelListener = null; + + private ProgressBar progressBar; + private TextView emptyView; + private RecyclerView recyclerView; + + private List feedGroups = new Vector<>(); + + public void setOnSelectedListener(final OnSelectedListener listener) { + onSelectedListener = listener; + } + + public void setOnCancelListener(final OnCancelListener listener) { + onCancelListener = listener; + } + + /*////////////////////////////////////////////////////////////////////////// + // Init + //////////////////////////////////////////////////////////////////////////*/ + + @Override + public void onCreate(@Nullable final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setStyle(STYLE_NO_TITLE, ThemeHelper.getMinWidthDialogTheme(requireContext())); + } + + @Override + public View onCreateView(@NonNull final LayoutInflater inflater, final ViewGroup container, + final Bundle savedInstanceState) { + final View v = inflater.inflate(R.layout.select_feed_group_fragment, container, false); + recyclerView = v.findViewById(R.id.items_list); + recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); + final SelectFeedGroupAdapter feedGroupAdapter = new SelectFeedGroupAdapter(); + recyclerView.setAdapter(feedGroupAdapter); + + progressBar = v.findViewById(R.id.progressBar); + emptyView = v.findViewById(R.id.empty_state_view); + progressBar.setVisibility(View.VISIBLE); + recyclerView.setVisibility(View.GONE); + emptyView.setVisibility(View.GONE); + + + final AppDatabase database = NewPipeDatabase.getInstance(requireContext()); + database.feedGroupDAO().getAll().toObservable() + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(getFeedGroupObserver()); + + return v; + } + + /*////////////////////////////////////////////////////////////////////////// + // Handle actions + //////////////////////////////////////////////////////////////////////////*/ + + @Override + public void onCancel(@NonNull final DialogInterface dialogInterface) { + super.onCancel(dialogInterface); + if (onCancelListener != null) { + onCancelListener.onCancel(); + } + } + + private void clickedItem(final int position) { + if (onSelectedListener != null) { + final FeedGroupEntity entry = feedGroups.get(position); + onSelectedListener + .onFeedGroupSelected(entry.getUid(), entry.getName(), + entry.getIcon().getDrawableResource()); + } + dismiss(); + } + + /*////////////////////////////////////////////////////////////////////////// + // Item handling + //////////////////////////////////////////////////////////////////////////*/ + + private void displayFeedGroups(final List newFeedGroups) { + this.feedGroups = newFeedGroups; + progressBar.setVisibility(View.GONE); + if (newFeedGroups.isEmpty()) { + emptyView.setVisibility(View.VISIBLE); + return; + } + recyclerView.setVisibility(View.VISIBLE); + + } + + private Observer> getFeedGroupObserver() { + return new Observer>() { + @Override + public void onSubscribe(@NonNull final Disposable disposable) { } + + @Override + public void onNext(@NonNull final List newGroups) { + displayFeedGroups(newGroups); + } + + @Override + public void onError(@NonNull final Throwable exception) { + ErrorUtil.showUiErrorSnackbar(SelectFeedGroupFragment.this, + "Loading Feed Groups", exception); + } + + @Override + public void onComplete() { } + }; + } + + /*////////////////////////////////////////////////////////////////////////// + // Interfaces + //////////////////////////////////////////////////////////////////////////*/ + + public interface OnSelectedListener { + void onFeedGroupSelected(Long groupId, String name, int icon); + } + + public interface OnCancelListener { + void onCancel(); + } + + private class SelectFeedGroupAdapter + extends RecyclerView.Adapter { + @NonNull + @Override + public SelectFeedGroupItemHolder onCreateViewHolder(final ViewGroup parent, + final int viewType) { + final View item = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.select_feed_group_item, parent, false); + return new SelectFeedGroupItemHolder(item); + } + + @Override + public void onBindViewHolder(final SelectFeedGroupItemHolder holder, final int position) { + final FeedGroupEntity entry = feedGroups.get(position); + holder.titleView.setText(entry.getName()); + holder.view.setOnClickListener(view -> clickedItem(position)); + holder.thumbnailView.setImageResource(entry.getIcon().getDrawableResource()); + } + + @Override + public int getItemCount() { + return feedGroups.size(); + } + + public class SelectFeedGroupItemHolder extends RecyclerView.ViewHolder { + public final View view; + final ImageView thumbnailView; + final TextView titleView; + SelectFeedGroupItemHolder(final View v) { + super(v); + this.view = v; + thumbnailView = v.findViewById(R.id.itemThumbnailView); + titleView = v.findViewById(R.id.itemTitleView); + } + } + } +} diff --git a/app/src/main/java/org/schabi/newpipe/settings/tabs/ChooseTabsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/tabs/ChooseTabsFragment.java index 289c824ba..738a9c926 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/tabs/ChooseTabsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/tabs/ChooseTabsFragment.java @@ -34,6 +34,7 @@ import org.schabi.newpipe.error.UserAction; import org.schabi.newpipe.settings.SelectChannelFragment; import org.schabi.newpipe.settings.SelectKioskFragment; import org.schabi.newpipe.settings.SelectPlaylistFragment; +import org.schabi.newpipe.settings.SelectFeedGroupFragment; import org.schabi.newpipe.settings.tabs.AddTabDialog.ChooseTabListItem; import org.schabi.newpipe.util.ThemeHelper; @@ -203,6 +204,14 @@ public class ChooseTabsFragment extends Fragment { }); selectPlaylistFragment.show(getParentFragmentManager(), "select_playlist"); return; + case FEEDGROUP: + final SelectFeedGroupFragment selectFeedGroupFragment = + new SelectFeedGroupFragment(); + selectFeedGroupFragment.setOnSelectedListener( + (groupId, name, iconId) -> + addTab(new Tab.FeedGroupTab(groupId, name, iconId))); + selectFeedGroupFragment.show(getParentFragmentManager(), "select_feed_group"); + return; default: addTab(type.getTab()); break; @@ -244,6 +253,11 @@ public class ChooseTabsFragment extends Fragment { getString(R.string.playlist_page_summary), tab.getTabIconRes(context))); break; + case FEEDGROUP: + returnList.add(new ChooseTabListItem(tab.getTabId(), + getString(R.string.feed_group_page_summary), + tab.getTabIconRes(context))); + break; default: if (!tabList.contains(tab)) { returnList.add(new ChooseTabListItem(context, tab)); @@ -396,6 +410,9 @@ public class ChooseTabsFragment extends Fragment { ? getString(R.string.local) : getNameOfServiceById(serviceId); return serviceName + "/" + tab.getTabName(requireContext()); + case FEEDGROUP: + return getString(R.string.feed_groups_header_title) + + "/" + ((Tab.FeedGroupTab) tab).getFeedGroupName(); default: return tab.getTabName(requireContext()); } diff --git a/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java b/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java index 7e3f5d0c8..4c1f65df2 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java +++ b/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java @@ -93,6 +93,8 @@ public abstract class Tab { return new ChannelTab(jsonObject); case PLAYLIST: return new PlaylistTab(jsonObject); + case FEEDGROUP: + return new FeedGroupTab(jsonObject); } } @@ -162,7 +164,8 @@ public abstract class Tab { HISTORY(new HistoryTab()), KIOSK(new KioskTab()), CHANNEL(new ChannelTab()), - PLAYLIST(new PlaylistTab()); + PLAYLIST(new PlaylistTab()), + FEEDGROUP(new FeedGroupTab()); private final Tab tab; @@ -458,7 +461,7 @@ public abstract class Tab { final ChannelTab other = (ChannelTab) obj; return super.equals(obj) && channelServiceId == other.channelServiceId - && channelUrl.equals(other.channelName) + && channelUrl.equals(other.channelUrl) && channelName.equals(other.channelName); } @@ -652,4 +655,93 @@ public abstract class Tab { return playlistType; } } + public static class FeedGroupTab extends Tab { + public static final int ID = 9; + private static final String JSON_FEED_GROUP_ID_KEY = "feed_group_id"; + private static final String JSON_FEED_GROUP_NAME_KEY = "feed_group_name"; + private static final String JSON_FEED_GROUP_ICON_KEY = "feed_group_icon"; + private Long feedGroupId; + private String feedGroupName; + private int iconId; + + private FeedGroupTab() { + this((long) -1, NO_NAME, R.drawable.ic_asterisk); + } + + public FeedGroupTab(final Long feedGroupId, final String feedGroupName, + final int iconId) { + this.feedGroupId = feedGroupId; + this.feedGroupName = feedGroupName; + this.iconId = iconId; + + } + + public FeedGroupTab(final JsonObject jsonObject) { + super(jsonObject); + } + + @Override + public int getTabId() { + return ID; + } + + @Override + public String getTabName(final Context context) { + return context.getString(R.string.fragment_feed_title); + } + + @DrawableRes + @Override + public int getTabIconRes(final Context context) { + return this.iconId; + } + + @Override + public FeedFragment getFragment(final Context context) { + return FeedFragment.newInstance(feedGroupId, feedGroupName); + } + + @Override + protected void writeDataToJson(final JsonStringWriter writerSink) { + writerSink.value(JSON_FEED_GROUP_ID_KEY, feedGroupId) + .value(JSON_FEED_GROUP_NAME_KEY, feedGroupName) + .value(JSON_FEED_GROUP_ICON_KEY, iconId); + } + + @Override + protected void readDataFromJson(final JsonObject jsonObject) { + feedGroupId = jsonObject.getLong(JSON_FEED_GROUP_ID_KEY, -1); + feedGroupName = jsonObject.getString(JSON_FEED_GROUP_NAME_KEY, NO_NAME); + iconId = jsonObject.getInt(JSON_FEED_GROUP_ICON_KEY, R.drawable.ic_asterisk); + } + + @Override + public boolean equals(final Object obj) { + if (!(obj instanceof FeedGroupTab)) { + return false; + } + final FeedGroupTab other = (FeedGroupTab) obj; + return super.equals(obj) + && feedGroupId.equals(other.feedGroupId) + && feedGroupName.equals(other.feedGroupName) + && iconId == other.iconId; + } + + @Override + public int hashCode() { + return Objects.hash(getTabId(), feedGroupId, feedGroupName, iconId); + } + + public Long getFeedGroupId() { + return feedGroupId; + } + + public String getFeedGroupName() { + return feedGroupName; + } + + public int getIconId() { + return iconId; + } + } } diff --git a/app/src/main/java/org/schabi/newpipe/util/Localization.java b/app/src/main/java/org/schabi/newpipe/util/Localization.java index 8c7b30a9b..825bee343 100644 --- a/app/src/main/java/org/schabi/newpipe/util/Localization.java +++ b/app/src/main/java/org/schabi/newpipe/util/Localization.java @@ -90,19 +90,14 @@ public final class Localization { * Localize a user name like @foobar. * * Will correctly handle right-to-left usernames by using a {@link BidiFormatter}. + * For right-to-left usernames, it will put the @ on the right side to read more naturally. * * @param plainName username, with an optional leading @ * @return a usernames that can include RTL-characters */ @NonNull public static String localizeUserName(final String plainName) { - final BidiFormatter bidi = BidiFormatter.getInstance(); - - if (plainName.startsWith("@")) { - return "@" + bidi.unicodeWrap(plainName.substring(1)); - } else { - return bidi.unicodeWrap(plainName); - } + return BidiFormatter.getInstance().unicodeWrap(plainName); } public static org.schabi.newpipe.extractor.localization.Localization getPreferredLocalization( diff --git a/app/src/main/res/layout/select_feed_group_fragment.xml b/app/src/main/res/layout/select_feed_group_fragment.xml new file mode 100644 index 000000000..bb17d5f6e --- /dev/null +++ b/app/src/main/res/layout/select_feed_group_fragment.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/layout/select_feed_group_item.xml b/app/src/main/res/layout/select_feed_group_item.xml new file mode 100644 index 000000000..ccce555f5 --- /dev/null +++ b/app/src/main/res/layout/select_feed_group_item.xml @@ -0,0 +1,38 @@ + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 011c69fcb..6c8704a7c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -412,6 +412,8 @@ Do you want to also import settings? Could not load comments The language will change once the app is restarted + Select a feed group + No feed group created yet Trending Top 50 @@ -686,6 +688,7 @@ What\'s New + Channel group page Channel groups Feed last updated: %s Not loaded: %d diff --git a/app/src/test/java/org/schabi/newpipe/settings/tabs/TabsJsonHelperTest.java b/app/src/test/java/org/schabi/newpipe/settings/tabs/TabsJsonHelperTest.java index bddb130fe..561a8cbec 100644 --- a/app/src/test/java/org/schabi/newpipe/settings/tabs/TabsJsonHelperTest.java +++ b/app/src/test/java/org/schabi/newpipe/settings/tabs/TabsJsonHelperTest.java @@ -93,9 +93,11 @@ public class TabsJsonHelperTest { final Tab.ChannelTab channelTab = new Tab.ChannelTab( 666, "https://example.org", "testName"); final Tab.KioskTab kioskTab = new Tab.KioskTab(123, "trending_key"); + final Tab.FeedGroupTab feedGroupTab = new Tab.FeedGroupTab( + 1L, "x", 123); final List tabs = Arrays.asList( - blankTab, defaultKioskTab, subscriptionsTab, channelTab, kioskTab); + blankTab, defaultKioskTab, subscriptionsTab, channelTab, kioskTab, feedGroupTab); final String returnedJson = TabsJsonHelper.getJsonToSave(tabs); // Reading @@ -130,5 +132,13 @@ public class TabsJsonHelperTest { assertEquals(kioskTab.getTabId(), kioskTabFromReturnedJson.getTabId()); assertEquals(kioskTab.getKioskServiceId(), kioskTabFromReturnedJson.getKioskServiceId()); assertEquals(kioskTab.getKioskId(), kioskTabFromReturnedJson.getKioskId()); + + final Tab.FeedGroupTab grpTabFromReturnedJson = requireNonNull( + (Tab.FeedGroupTab) Tab.from((JsonObject) tabsFromArray.get(5) + )); + assertEquals(feedGroupTab.getTabId(), grpTabFromReturnedJson.getTabId()); + assertEquals(feedGroupTab.getFeedGroupId(), grpTabFromReturnedJson.getFeedGroupId()); + assertEquals(feedGroupTab.getIconId(), grpTabFromReturnedJson.getIconId()); + assertEquals(feedGroupTab.getFeedGroupName(), grpTabFromReturnedJson.getFeedGroupName()); } } diff --git a/doc/README.ar.md b/doc/README.ar.md index 242516cdc..8747d3e2c 100644 --- a/doc/README.ar.md +++ b/doc/README.ar.md @@ -6,11 +6,22 @@

+ + + + + + - + -

+ +

+ + +

+

لقطات الشاشةالخدمات المدعومةوصفسماتالتثبيت والتحديثاتمساهمةالتبرعاترخصة

موقعمدونةالأسئلة الشائعةإضغط

diff --git a/doc/README.asm.md b/doc/README.asm.md index 8042b3db9..c2d919d09 100644 --- a/doc/README.asm.md +++ b/doc/README.asm.md @@ -6,12 +6,22 @@

+ + + + + + - + +

+ +

+

স্ক্ৰীণশ্বটসমৰ্থিত সেৱাসমূহবিৱৰণ • diff --git a/doc/README.de.md b/doc/README.de.md index 5b3275d07..03dd2b364 100644 --- a/doc/README.de.md +++ b/doc/README.de.md @@ -9,11 +9,22 @@

+ + + + + + - + -

+ +

+ + +

+

ScreenshotsUnterstützte DiensteBeschreibungFeaturesInstallation und UpdatesBeitragSpendenLizenz

WebsiteBlogFAQÜber NewPipe

diff --git a/doc/README.es.md b/doc/README.es.md index 8ec58e771..338b3242a 100644 --- a/doc/README.es.md +++ b/doc/README.es.md @@ -6,11 +6,22 @@

+ + + + + + - + -

+ +

+ + +

+

Capturas de PantallaDescripciónCaracterísticasInstalación y ActualizacionesContribuciónDonarLicencia

diff --git a/doc/README.fr.md b/doc/README.fr.md index 772f4a1ae..ee3621e27 100644 --- a/doc/README.fr.md +++ b/doc/README.fr.md @@ -9,11 +9,22 @@

+ + + + + + - + -

+ +

+ + +

+

Captures d'écranServices SupportésDescriptionFonctionnalitésInstallation et mises à jourContribuerDonsLicence

SiteBlogFAQPresse

diff --git a/doc/README.hi.md b/doc/README.hi.md index 37ae71a4a..ed56fca14 100644 --- a/doc/README.hi.md +++ b/doc/README.hi.md @@ -6,12 +6,22 @@

+ + + + + + - + +

+ +

+

ऐप कैसी दिखती हैसमर्थित सेवाएँविवरणसुविधाएँस्थापित करना और अपडेट करनायोगदान करेंआर्थिक योगदान करेंलाइसेंस

वेबसाइटब्लॉगसाधारण सवाल-जवाबप्रेस

diff --git a/doc/README.it.md b/doc/README.it.md index 6c227ea2f..930959c77 100644 --- a/doc/README.it.md +++ b/doc/README.it.md @@ -6,11 +6,22 @@

+ + + + + + - + -

+ +

+ + +

+

ScreenshotServizi SupportatiDescrizioneFunzionalitàInstallazione e aggiornamentiContribuireDonareLicenza

SitoBlogFAQStampa

diff --git a/doc/README.ja.md b/doc/README.ja.md index e8f708a8a..19902d57e 100644 --- a/doc/README.ja.md +++ b/doc/README.ja.md @@ -6,11 +6,22 @@

+ + + + + + - + -

+ +

+ + +

+

スクリーンショット説明機能インストールと更新貢献寄付ライセンス

ウェブサイトブログFAQニュース

diff --git a/doc/README.ko.md b/doc/README.ko.md index 3215bd713..3c2f9f39e 100644 --- a/doc/README.ko.md +++ b/doc/README.ko.md @@ -6,11 +6,22 @@

+ + + + + + - + -

+ +

+ + +

+

ScreenshotsDescriptionFeaturesUpdatesContributionDonateLicense

WebsiteBlogFAQPress

diff --git a/doc/README.pa.md b/doc/README.pa.md index 0e254adf1..2dbc94c14 100644 --- a/doc/README.pa.md +++ b/doc/README.pa.md @@ -6,12 +6,22 @@

+ + + + + + - + +

+ +

+

ਐਪ ਕਿਹੋ-ਜਿਹੀ ਦਿਖਦੀ ਹੈਸਮਰਥਿਤ ਸੇਵਾਵਾਂਵਰਣਨਵਿਸ਼ੇਸ਼ਤਾਵਾਂਇੰਸਟਾਲੇਸ਼ਨ ਅਤੇ ਅੱਪਡੇਟਯੋਗਦਾਨਦਾਨਲਾਈਸੈਂਸ

ਵੈੱਬਸਾਈਟਬਲੌਗਆਮ ਸਵਾਲ ਜਵਾਬਪ੍ਰੈਸ

diff --git a/doc/README.pl.md b/doc/README.pl.md index 96d493153..9d216c590 100644 --- a/doc/README.pl.md +++ b/doc/README.pl.md @@ -6,11 +6,22 @@

+ + + + + + - + -

+ +

+ + +

+

ScreenshotyOpisFunkcjeInstalacja i aktualizacjeWkładWesprzyjLicencja

StronaBlogFAQPress

diff --git a/doc/README.pt_BR.md b/doc/README.pt_BR.md index da6c4fce6..d65fa9790 100644 --- a/doc/README.pt_BR.md +++ b/doc/README.pt_BR.md @@ -10,11 +10,22 @@

+ + + + + + - + -

+ +

+ + +

+

ScreenshotsServiços SuportadosDescriçãoRecursosInstalação e atualizaçõesContribuiçõesDoarLicença

SiteBlogFAQPress

diff --git a/doc/README.ro.md b/doc/README.ro.md index 29c1d3666..5363ef7bc 100644 --- a/doc/README.ro.md +++ b/doc/README.ro.md @@ -6,11 +6,22 @@

+ + + + + + - + -

+ +

+ + +

+

Capturi de ecranDescriereFuncţiiInstalare şi actualizăriContribuţieDonaţiLicenţă

WebsiteBlogFAQPresă

diff --git a/doc/README.ru.md b/doc/README.ru.md index e3c76d329..894e5f2e0 100644 --- a/doc/README.ru.md +++ b/doc/README.ru.md @@ -6,11 +6,22 @@

+ + + + + + - + -

+ +

+ + +

+

СкриншотыПоддерживаемые сервисыОписаниеВозможностиУстановка и обновленияУчастиеПожертвованиеЛицензия

СайтБлогЧЗВПресса

diff --git a/doc/README.ryu.md b/doc/README.ryu.md index 2e24aa41c..8676f1bfd 100644 --- a/doc/README.ryu.md +++ b/doc/README.ryu.md @@ -6,11 +6,22 @@

+ + + + + + - + -

+ +

+ + +

+

スクリーンショットしちめいちぬーインストールとぅこうしんこうきんちーふライセンス

ウェブサイトブログFAQニュース

diff --git a/doc/README.so.md b/doc/README.so.md index 640feae60..82e544d93 100644 --- a/doc/README.so.md +++ b/doc/README.so.md @@ -6,11 +6,22 @@

+ + + + + + - + -

+ +

+ + +

+

Sawir-shaashadeedFaahfaahinWaxqabadkaKushubida iyo cusboonaysiintaKusoo KordhinUgu DeeqLaysinka

Website-kaMaqaaladaSu'aalaha Aalaa La-iswaydiiyoWarbaahinta

diff --git a/doc/README.sr.md b/doc/README.sr.md index 1a9118638..d8b0fe435 100644 --- a/doc/README.sr.md +++ b/doc/README.sr.md @@ -9,11 +9,22 @@

+ + + + + + - + -

+ +

+ + +

+

Снимци екранаПодржане услугеОписКарактеристикеИнсталација и ажурирањаДоприносДонацијаЛиценца

Веб-сајтБлогЧППШтампа

diff --git a/doc/README.tr.md b/doc/README.tr.md index bbdd85f76..c6610d97d 100644 --- a/doc/README.tr.md +++ b/doc/README.tr.md @@ -6,11 +6,22 @@

+ + + + + + - + -

+ +

+ + +

+

Ekran fotoğraflarıAçıklamaÖzelliklerKurulum ve güncellemelerKatkıda bulunmaBağışLisans

Web sitesiBlogSSSBasın

diff --git a/doc/README.zh_TW.md b/doc/README.zh_TW.md index 760a43ad5..04a8355cb 100644 --- a/doc/README.zh_TW.md +++ b/doc/README.zh_TW.md @@ -6,11 +6,22 @@

+ + + + + + - + -

+ +

+ + +

+

截圖說明功能安裝與更新貢獻捐款授權憑證

網站部落格FAQ媒體