Merge branch 'dev' into dev
| @@ -62,7 +62,8 @@ dependencies { | ||||
|         exclude module: 'support-annotations' | ||||
|     }) | ||||
|  | ||||
|     implementation 'com.github.TeamNewPipe:NewPipeExtractor:b6d3252' | ||||
|     implementation 'com.github.TeamNewPipe:NewPipeExtractor:43b54cc' | ||||
|  | ||||
|     testImplementation 'junit:junit:4.12' | ||||
|     testImplementation 'org.mockito:mockito-core:2.23.0' | ||||
|  | ||||
|   | ||||
| @@ -6,9 +6,9 @@ import android.app.NotificationChannel; | ||||
| import android.app.NotificationManager; | ||||
| import android.content.Context; | ||||
| import android.os.Build; | ||||
| import android.util.Log; | ||||
|  | ||||
| import androidx.annotation.Nullable; | ||||
| import android.util.Log; | ||||
|  | ||||
| import com.nostra13.universalimageloader.cache.memory.impl.LRULimitedMemoryCache; | ||||
| import com.nostra13.universalimageloader.core.ImageLoader; | ||||
| @@ -29,6 +29,7 @@ import org.schabi.newpipe.report.UserAction; | ||||
| import org.schabi.newpipe.settings.SettingsActivity; | ||||
| import org.schabi.newpipe.util.ExtractorHelper; | ||||
| import org.schabi.newpipe.util.Localization; | ||||
| import org.schabi.newpipe.util.ServiceHelper; | ||||
| import org.schabi.newpipe.util.StateSaver; | ||||
|  | ||||
| import java.io.IOException; | ||||
| @@ -103,6 +104,8 @@ public class App extends Application { | ||||
|         StateSaver.init(this); | ||||
|         initNotificationChannel(); | ||||
|  | ||||
|         ServiceHelper.initServices(this); | ||||
|  | ||||
|         // Initialize image loader | ||||
|         ImageLoader.getInstance().init(getImageLoaderConfigurations(10, 50)); | ||||
|  | ||||
|   | ||||
| @@ -29,14 +29,18 @@ import android.os.Handler; | ||||
| import android.os.Looper; | ||||
| import android.preference.PreferenceManager; | ||||
| import android.util.Log; | ||||
| import android.view.LayoutInflater; | ||||
| import android.view.Menu; | ||||
| import android.view.MenuInflater; | ||||
| import android.view.MenuItem; | ||||
| import android.view.View; | ||||
| import android.view.Window; | ||||
| import android.view.WindowManager; | ||||
| import android.widget.AdapterView; | ||||
| import android.widget.ArrayAdapter; | ||||
| import android.widget.Button; | ||||
| import android.widget.ImageView; | ||||
| import android.widget.Spinner; | ||||
| import android.widget.TextView; | ||||
|  | ||||
| import androidx.annotation.NonNull; | ||||
| @@ -47,12 +51,15 @@ import androidx.appcompat.widget.Toolbar; | ||||
| import androidx.core.view.GravityCompat; | ||||
| import androidx.drawerlayout.widget.DrawerLayout; | ||||
| import androidx.fragment.app.Fragment; | ||||
| import androidx.fragment.app.FragmentManager; | ||||
|  | ||||
| import com.google.android.material.navigation.NavigationView; | ||||
|  | ||||
| import org.schabi.newpipe.extractor.NewPipe; | ||||
| import org.schabi.newpipe.extractor.ServiceList; | ||||
| import org.schabi.newpipe.extractor.StreamingService; | ||||
| import org.schabi.newpipe.extractor.exceptions.ExtractionException; | ||||
| import org.schabi.newpipe.extractor.services.peertube.PeertubeInstance; | ||||
| import org.schabi.newpipe.fragments.BackPressable; | ||||
| import org.schabi.newpipe.fragments.MainFragment; | ||||
| import org.schabi.newpipe.fragments.detail.VideoDetailFragment; | ||||
| @@ -61,11 +68,15 @@ import org.schabi.newpipe.report.ErrorActivity; | ||||
| import org.schabi.newpipe.util.Constants; | ||||
| import org.schabi.newpipe.util.KioskTranslator; | ||||
| import org.schabi.newpipe.util.NavigationHelper; | ||||
| import org.schabi.newpipe.util.PeertubeHelper; | ||||
| import org.schabi.newpipe.util.PermissionHelper; | ||||
| import org.schabi.newpipe.util.ServiceHelper; | ||||
| import org.schabi.newpipe.util.StateSaver; | ||||
| import org.schabi.newpipe.util.ThemeHelper; | ||||
|  | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
|  | ||||
| public class MainActivity extends AppCompatActivity { | ||||
|     private static final String TAG = "MainActivity"; | ||||
|     public static final boolean DEBUG = !BuildConfig.BUILD_TYPE.equals("release"); | ||||
| @@ -300,13 +311,57 @@ public class MainActivity extends AppCompatActivity { | ||||
|             final String title = s.getServiceInfo().getName() + | ||||
|                     (ServiceHelper.isBeta(s) ? " (beta)" : ""); | ||||
|  | ||||
|             drawerItems.getMenu() | ||||
|             MenuItem menuItem = drawerItems.getMenu() | ||||
|                     .add(R.id.menu_services_group, s.getServiceId(), ORDER, title) | ||||
|                     .setIcon(ServiceHelper.getIcon(s.getServiceId())); | ||||
|  | ||||
|             // peertube specifics | ||||
|             if(s.getServiceId() == 3){ | ||||
|                 enhancePeertubeMenu(s, menuItem); | ||||
|             } | ||||
|         } | ||||
|         drawerItems.getMenu().getItem(ServiceHelper.getSelectedServiceId(this)).setChecked(true); | ||||
|     } | ||||
|  | ||||
|     private void enhancePeertubeMenu(StreamingService s, MenuItem menuItem) { | ||||
|         PeertubeInstance currentInstace = PeertubeHelper.getCurrentInstance(); | ||||
|         menuItem.setTitle(currentInstace.getName() + (ServiceHelper.isBeta(s) ? " (beta)" : "")); | ||||
|         Spinner spinner = (Spinner) LayoutInflater.from(this).inflate(R.layout.instance_spinner_layout, null); | ||||
|         List<PeertubeInstance> instances = PeertubeHelper.getInstanceList(this); | ||||
|         List<String> items = new ArrayList<>(); | ||||
|         int defaultSelect = 0; | ||||
|         for(PeertubeInstance instance: instances){ | ||||
|             items.add(instance.getName()); | ||||
|             if(instance.getUrl().equals(currentInstace.getUrl())){ | ||||
|                 defaultSelect = items.size()-1; | ||||
|             } | ||||
|         } | ||||
|         ArrayAdapter<String> adapter = new ArrayAdapter<>(this, R.layout.instance_spinner_item, items); | ||||
|         adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); | ||||
|         spinner.setAdapter(adapter); | ||||
|         spinner.setSelection(defaultSelect, false); | ||||
|         spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { | ||||
|             @Override | ||||
|             public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { | ||||
|                 PeertubeInstance newInstance = instances.get(position); | ||||
|                 if(newInstance.getUrl().equals(PeertubeHelper.getCurrentInstance().getUrl())) return; | ||||
|                 PeertubeHelper.selectInstance(newInstance, getApplicationContext()); | ||||
|                 changeService(menuItem); | ||||
|                 drawer.closeDrawers(); | ||||
|                 new Handler(Looper.getMainLooper()).postDelayed(() -> { | ||||
|                     getSupportFragmentManager().popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE); | ||||
|                     recreate(); | ||||
|                 }, 300); | ||||
|             } | ||||
|  | ||||
|             @Override | ||||
|             public void onNothingSelected(AdapterView<?> parent) { | ||||
|  | ||||
|             } | ||||
|         }); | ||||
|         menuItem.setActionView(spinner); | ||||
|     } | ||||
|  | ||||
|     private void showTabs() throws ExtractionException { | ||||
|         serviceArrow.setImageResource(R.drawable.ic_arrow_down_white); | ||||
|  | ||||
| @@ -367,6 +422,7 @@ public class MainActivity extends AppCompatActivity { | ||||
|             String selectedServiceName = NewPipe.getService( | ||||
|                     ServiceHelper.getSelectedServiceId(this)).getServiceInfo().getName(); | ||||
|             headerServiceView.setText(selectedServiceName); | ||||
|             headerServiceView.post(() -> headerServiceView.setSelected(true)); | ||||
|             toggleServiceButton.setContentDescription( | ||||
|                     getString(R.string.drawer_header_description) + selectedServiceName); | ||||
|         } catch (Exception e) { | ||||
|   | ||||
| @@ -29,6 +29,7 @@ import org.schabi.newpipe.extractor.InfoItem; | ||||
| import org.schabi.newpipe.extractor.ListExtractor; | ||||
| import org.schabi.newpipe.extractor.NewPipe; | ||||
| import org.schabi.newpipe.extractor.channel.ChannelInfo; | ||||
| import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException; | ||||
| import org.schabi.newpipe.extractor.exceptions.ExtractionException; | ||||
| import org.schabi.newpipe.extractor.stream.StreamInfoItem; | ||||
| import org.schabi.newpipe.fragments.list.BaseListInfoFragment; | ||||
| @@ -98,7 +99,7 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> { | ||||
|     @Override | ||||
|     public void setUserVisibleHint(boolean isVisibleToUser) { | ||||
|         super.setUserVisibleHint(isVisibleToUser); | ||||
|         if(activity != null | ||||
|         if (activity != null | ||||
|                 && useAsFrontPage | ||||
|                 && isVisibleToUser) { | ||||
|             setTitle(currentInfo != null ? currentInfo.getName() : name); | ||||
| @@ -152,7 +153,7 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> { | ||||
|     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { | ||||
|         super.onCreateOptionsMenu(menu, inflater); | ||||
|         ActionBar supportActionBar = activity.getSupportActionBar(); | ||||
|         if(useAsFrontPage && supportActionBar != null) { | ||||
|         if (useAsFrontPage && supportActionBar != null) { | ||||
|             supportActionBar.setDisplayHomeAsUpEnabled(false); | ||||
|         } else { | ||||
|             inflater.inflate(R.menu.menu_channel, menu); | ||||
| @@ -165,7 +166,7 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> { | ||||
|  | ||||
|     private void openRssFeed() { | ||||
|         final ChannelInfo info = currentInfo; | ||||
|         if(info != null) { | ||||
|         if (info != null) { | ||||
|             Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(info.getFeedUrl())); | ||||
|             startActivity(intent); | ||||
|         } | ||||
| @@ -178,10 +179,14 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> { | ||||
|                 openRssFeed(); | ||||
|                 break; | ||||
|             case R.id.menu_item_openInBrowser: | ||||
|                 if (currentInfo != null) { | ||||
|                     ShareUtils.openUrlInBrowser(this.getContext(), currentInfo.getOriginalUrl()); | ||||
|                 } | ||||
|                 break; | ||||
|             case R.id.menu_item_share: | ||||
|                 if (currentInfo != null) { | ||||
|                     ShareUtils.shareUrl(this.getContext(), name, currentInfo.getOriginalUrl()); | ||||
|                 } | ||||
|                 break; | ||||
|             default: | ||||
|                 return super.onOptionsItemSelected(item); | ||||
| @@ -397,8 +402,8 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> { | ||||
|  | ||||
|     private PlayQueue getPlayQueue(final int index) { | ||||
|         final List<StreamInfoItem> streamItems = new ArrayList<>(); | ||||
|         for(InfoItem i : infoListAdapter.getItemsList()) { | ||||
|             if(i instanceof StreamInfoItem) { | ||||
|         for (InfoItem i : infoListAdapter.getItemsList()) { | ||||
|             if (i instanceof StreamInfoItem) { | ||||
|                 streamItems.add((StreamInfoItem) i); | ||||
|             } | ||||
|         } | ||||
| @@ -432,12 +437,16 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> { | ||||
|     protected boolean onError(Throwable exception) { | ||||
|         if (super.onError(exception)) return true; | ||||
|  | ||||
|         if (exception instanceof ContentNotAvailableException) { | ||||
|             showError(getString(R.string.content_not_available), false); | ||||
|         } else { | ||||
|             int errorId = exception instanceof ExtractionException ? R.string.parsing_error : R.string.general_error; | ||||
|             onUnrecoverableError(exception, | ||||
|                     UserAction.REQUESTED_CHANNEL, | ||||
|                     NewPipe.getNameOfService(serviceId), | ||||
|                     url, | ||||
|                     errorId); | ||||
|         } | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -0,0 +1,417 @@ | ||||
| package org.schabi.newpipe.settings; | ||||
|  | ||||
| import android.annotation.SuppressLint; | ||||
| import android.content.Context; | ||||
| import android.content.SharedPreferences; | ||||
| import android.os.Bundle; | ||||
| import android.preference.PreferenceManager; | ||||
| import android.view.LayoutInflater; | ||||
| import android.view.Menu; | ||||
| import android.view.MenuInflater; | ||||
| import android.view.MenuItem; | ||||
| import android.view.MotionEvent; | ||||
| import android.view.View; | ||||
| import android.view.ViewGroup; | ||||
| import android.widget.EditText; | ||||
| import android.widget.ImageView; | ||||
| import android.widget.ProgressBar; | ||||
| import android.widget.RadioButton; | ||||
| import android.widget.TextView; | ||||
| import android.widget.Toast; | ||||
|  | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.annotation.Nullable; | ||||
| import androidx.appcompat.app.ActionBar; | ||||
| import androidx.appcompat.app.AlertDialog; | ||||
| import androidx.appcompat.app.AppCompatActivity; | ||||
| import androidx.appcompat.content.res.AppCompatResources; | ||||
| import androidx.appcompat.widget.AppCompatImageView; | ||||
| import androidx.fragment.app.Fragment; | ||||
| import androidx.recyclerview.widget.ItemTouchHelper; | ||||
| import androidx.recyclerview.widget.LinearLayoutManager; | ||||
| import androidx.recyclerview.widget.RecyclerView; | ||||
|  | ||||
| import com.google.android.material.floatingactionbutton.FloatingActionButton; | ||||
| import com.grack.nanojson.JsonStringWriter; | ||||
| import com.grack.nanojson.JsonWriter; | ||||
|  | ||||
| import org.schabi.newpipe.R; | ||||
| import org.schabi.newpipe.extractor.services.peertube.PeertubeInstance; | ||||
| import org.schabi.newpipe.util.Constants; | ||||
| import org.schabi.newpipe.util.PeertubeHelper; | ||||
| import org.schabi.newpipe.util.ThemeHelper; | ||||
|  | ||||
| import java.util.ArrayList; | ||||
| import java.util.Collections; | ||||
| import java.util.List; | ||||
|  | ||||
| import io.reactivex.Single; | ||||
| import io.reactivex.android.schedulers.AndroidSchedulers; | ||||
| import io.reactivex.disposables.CompositeDisposable; | ||||
| import io.reactivex.disposables.Disposable; | ||||
| import io.reactivex.schedulers.Schedulers; | ||||
|  | ||||
| public class PeertubeInstanceListFragment extends Fragment { | ||||
|  | ||||
|     private List<PeertubeInstance> instanceList = new ArrayList<>(); | ||||
|     private PeertubeInstance selectedInstance; | ||||
|     private String savedInstanceListKey; | ||||
|     public InstanceListAdapter instanceListAdapter; | ||||
|  | ||||
|     private ProgressBar progressBar; | ||||
|     private SharedPreferences sharedPreferences; | ||||
|  | ||||
|     private CompositeDisposable disposables = new CompositeDisposable(); | ||||
|  | ||||
|     /*////////////////////////////////////////////////////////////////////////// | ||||
|     // Lifecycle | ||||
|     //////////////////////////////////////////////////////////////////////////*/ | ||||
|  | ||||
|     @Override | ||||
|     public void onCreate(@Nullable Bundle savedInstanceState) { | ||||
|         super.onCreate(savedInstanceState); | ||||
|  | ||||
|         sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext()); | ||||
|         savedInstanceListKey = getString(R.string.peertube_instance_list_key); | ||||
|         selectedInstance = PeertubeHelper.getCurrentInstance(); | ||||
|         updateInstanceList(); | ||||
|  | ||||
|         setHasOptionsMenu(true); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { | ||||
|         return inflater.inflate(R.layout.fragment_instance_list, container, false); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onViewCreated(@NonNull View rootView, @Nullable Bundle savedInstanceState) { | ||||
|         super.onViewCreated(rootView, savedInstanceState); | ||||
|  | ||||
|         initButton(rootView); | ||||
|  | ||||
|         RecyclerView listInstances = rootView.findViewById(R.id.instances); | ||||
|         listInstances.setLayoutManager(new LinearLayoutManager(requireContext())); | ||||
|  | ||||
|         ItemTouchHelper itemTouchHelper = new ItemTouchHelper(getItemTouchCallback()); | ||||
|         itemTouchHelper.attachToRecyclerView(listInstances); | ||||
|  | ||||
|         instanceListAdapter = new InstanceListAdapter(requireContext(), itemTouchHelper); | ||||
|         listInstances.setAdapter(instanceListAdapter); | ||||
|  | ||||
|         progressBar = rootView.findViewById(R.id.loading_progress_bar); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onResume() { | ||||
|         super.onResume(); | ||||
|         updateTitle(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onPause() { | ||||
|         super.onPause(); | ||||
|         saveChanges(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onDestroy() { | ||||
|         super.onDestroy(); | ||||
|         if (disposables != null) disposables.clear(); | ||||
|         disposables = null; | ||||
|     } | ||||
|     /*////////////////////////////////////////////////////////////////////////// | ||||
|     // Menu | ||||
|     //////////////////////////////////////////////////////////////////////////*/ | ||||
|  | ||||
|     private final int MENU_ITEM_RESTORE_ID = 123456; | ||||
|  | ||||
|     @Override | ||||
|     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { | ||||
|         super.onCreateOptionsMenu(menu, inflater); | ||||
|  | ||||
|         final MenuItem restoreItem = menu.add(Menu.NONE, MENU_ITEM_RESTORE_ID, Menu.NONE, R.string.restore_defaults); | ||||
|         restoreItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); | ||||
|  | ||||
|         final int restoreIcon = ThemeHelper.resolveResourceIdFromAttr(requireContext(), R.attr.ic_restore_defaults); | ||||
|         restoreItem.setIcon(AppCompatResources.getDrawable(requireContext(), restoreIcon)); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public boolean onOptionsItemSelected(MenuItem item) { | ||||
|         if (item.getItemId() == MENU_ITEM_RESTORE_ID) { | ||||
|             restoreDefaults(); | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
|         return super.onOptionsItemSelected(item); | ||||
|     } | ||||
|  | ||||
|     /*////////////////////////////////////////////////////////////////////////// | ||||
|     // Utils | ||||
|     //////////////////////////////////////////////////////////////////////////*/ | ||||
|  | ||||
|     private void updateInstanceList() { | ||||
|         instanceList.clear(); | ||||
|         instanceList.addAll(PeertubeHelper.getInstanceList(requireContext())); | ||||
|     } | ||||
|  | ||||
|     private void selectInstance(PeertubeInstance instance) { | ||||
|         selectedInstance = PeertubeHelper.selectInstance(instance, requireContext()); | ||||
|         sharedPreferences.edit().putBoolean(Constants.KEY_MAIN_PAGE_CHANGE, true).apply(); | ||||
|     } | ||||
|  | ||||
|     private void updateTitle() { | ||||
|         if (getActivity() instanceof AppCompatActivity) { | ||||
|             ActionBar actionBar = ((AppCompatActivity) getActivity()).getSupportActionBar(); | ||||
|             if (actionBar != null) actionBar.setTitle(R.string.peertube_instance_url_title); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private void saveChanges() { | ||||
|         JsonStringWriter jsonWriter = JsonWriter.string().object().array("instances"); | ||||
|         for (PeertubeInstance instance : instanceList) { | ||||
|             jsonWriter.object(); | ||||
|             jsonWriter.value("name", instance.getName()); | ||||
|             jsonWriter.value("url", instance.getUrl()); | ||||
|             jsonWriter.end(); | ||||
|         } | ||||
|         String jsonToSave = jsonWriter.end().end().done(); | ||||
|         sharedPreferences.edit().putString(savedInstanceListKey, jsonToSave).apply(); | ||||
|     } | ||||
|  | ||||
|     private void restoreDefaults() { | ||||
|         new AlertDialog.Builder(requireContext(), ThemeHelper.getDialogTheme(requireContext())) | ||||
|                 .setTitle(R.string.restore_defaults) | ||||
|                 .setMessage(R.string.restore_defaults_confirmation) | ||||
|                 .setNegativeButton(R.string.cancel, null) | ||||
|                 .setPositiveButton(R.string.yes, (dialog, which) -> { | ||||
|                     sharedPreferences.edit().remove(savedInstanceListKey).apply(); | ||||
|                     selectInstance(PeertubeInstance.defaultInstance); | ||||
|                     updateInstanceList(); | ||||
|                     instanceListAdapter.notifyDataSetChanged(); | ||||
|                 }) | ||||
|                 .show(); | ||||
|     } | ||||
|  | ||||
|     private void initButton(View rootView) { | ||||
|         final FloatingActionButton fab = rootView.findViewById(R.id.addInstanceButton); | ||||
|         fab.setOnClickListener(v -> { | ||||
|             showAddItemDialog(requireContext()); | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     private void showAddItemDialog(Context c) { | ||||
|         final EditText urlET = new EditText(c); | ||||
|         urlET.setHint(R.string.peertube_instance_add_help); | ||||
|         AlertDialog dialog = new AlertDialog.Builder(c) | ||||
|                 .setTitle(R.string.peertube_instance_add_title) | ||||
|                 .setIcon(R.drawable.place_holder_peertube) | ||||
|                 .setView(urlET) | ||||
|                 .setNegativeButton(R.string.cancel, null) | ||||
|                 .setPositiveButton(R.string.finish, (dialog1, which) -> { | ||||
|                     String url = urlET.getText().toString(); | ||||
|                     addInstance(url); | ||||
|                 }) | ||||
|                 .create(); | ||||
|         dialog.show(); | ||||
|     } | ||||
|  | ||||
|     private void addInstance(String url) { | ||||
|         String cleanUrl = cleanUrl(url); | ||||
|         if(null == cleanUrl) return; | ||||
|         progressBar.setVisibility(View.VISIBLE); | ||||
|         Disposable disposable = Single.fromCallable(() -> { | ||||
|             PeertubeInstance instance = new PeertubeInstance(cleanUrl); | ||||
|             instance.fetchInstanceMetaData(); | ||||
|             return instance; | ||||
|         }).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe((instance) -> { | ||||
|             progressBar.setVisibility(View.GONE); | ||||
|             add(instance); | ||||
|         }, e -> { | ||||
|             progressBar.setVisibility(View.GONE); | ||||
|             Toast.makeText(getActivity(), R.string.peertube_instance_add_fail, Toast.LENGTH_SHORT).show(); | ||||
|         }); | ||||
|         disposables.add(disposable); | ||||
|     } | ||||
|  | ||||
|     @Nullable | ||||
|     private String cleanUrl(String url){ | ||||
|         // if protocol not present, add https | ||||
|         if(!url.startsWith("http")){ | ||||
|             url = "https://" + url; | ||||
|         } | ||||
|         // remove trailing slash | ||||
|         url = url.replaceAll("/$", ""); | ||||
|         // only allow https | ||||
|         if (!url.startsWith("https://")) { | ||||
|             Toast.makeText(getActivity(), R.string.peertube_instance_add_https_only, Toast.LENGTH_SHORT).show(); | ||||
|             return null; | ||||
|         } | ||||
|         // only allow if not already exists | ||||
|         for (PeertubeInstance instance : instanceList) { | ||||
|             if (instance.getUrl().equals(url)) { | ||||
|                 Toast.makeText(getActivity(), R.string.peertube_instance_add_exists, Toast.LENGTH_SHORT).show(); | ||||
|                 return null; | ||||
|             } | ||||
|         } | ||||
|         return url; | ||||
|     } | ||||
|  | ||||
|     private void add(final PeertubeInstance instance) { | ||||
|         instanceList.add(instance); | ||||
|         instanceListAdapter.notifyDataSetChanged(); | ||||
|     } | ||||
|  | ||||
|     /*////////////////////////////////////////////////////////////////////////// | ||||
|     // List Handling | ||||
|     //////////////////////////////////////////////////////////////////////////*/ | ||||
|  | ||||
|     private class InstanceListAdapter extends RecyclerView.Adapter<InstanceListAdapter.TabViewHolder> { | ||||
|         private ItemTouchHelper itemTouchHelper; | ||||
|         private final LayoutInflater inflater; | ||||
|         private RadioButton lastChecked; | ||||
|  | ||||
|         InstanceListAdapter(Context context, ItemTouchHelper itemTouchHelper) { | ||||
|             this.itemTouchHelper = itemTouchHelper; | ||||
|             this.inflater = LayoutInflater.from(context); | ||||
|         } | ||||
|  | ||||
|         public void swapItems(int fromPosition, int toPosition) { | ||||
|             Collections.swap(instanceList, fromPosition, toPosition); | ||||
|             notifyItemMoved(fromPosition, toPosition); | ||||
|         } | ||||
|  | ||||
|         @NonNull | ||||
|         @Override | ||||
|         public InstanceListAdapter.TabViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { | ||||
|             View view = inflater.inflate(R.layout.item_instance, parent, false); | ||||
|             return new InstanceListAdapter.TabViewHolder(view); | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public void onBindViewHolder(@NonNull InstanceListAdapter.TabViewHolder holder, int position) { | ||||
|             holder.bind(position, holder); | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public int getItemCount() { | ||||
|             return instanceList.size(); | ||||
|         } | ||||
|  | ||||
|         class TabViewHolder extends RecyclerView.ViewHolder { | ||||
|             private AppCompatImageView instanceIconView; | ||||
|             private TextView instanceNameView; | ||||
|             private TextView instanceUrlView; | ||||
|             private RadioButton instanceRB; | ||||
|             private ImageView handle; | ||||
|  | ||||
|             TabViewHolder(View itemView) { | ||||
|                 super(itemView); | ||||
|  | ||||
|                 instanceIconView = itemView.findViewById(R.id.instanceIcon); | ||||
|                 instanceNameView = itemView.findViewById(R.id.instanceName); | ||||
|                 instanceUrlView = itemView.findViewById(R.id.instanceUrl); | ||||
|                 instanceRB = itemView.findViewById(R.id.selectInstanceRB); | ||||
|                 handle = itemView.findViewById(R.id.handle); | ||||
|             } | ||||
|  | ||||
|             @SuppressLint("ClickableViewAccessibility") | ||||
|             void bind(int position, TabViewHolder holder) { | ||||
|                 handle.setOnTouchListener(getOnTouchListener(holder)); | ||||
|  | ||||
|                 final PeertubeInstance instance = instanceList.get(position); | ||||
|                 instanceNameView.setText(instance.getName()); | ||||
|                 instanceUrlView.setText(instance.getUrl()); | ||||
|                 instanceRB.setOnCheckedChangeListener(null); | ||||
|                 if (selectedInstance.getUrl().equals(instance.getUrl())) { | ||||
|                     if (lastChecked != null && lastChecked != instanceRB) { | ||||
|                         lastChecked.setChecked(false); | ||||
|                     } | ||||
|                     instanceRB.setChecked(true); | ||||
|                     lastChecked = instanceRB; | ||||
|                 } | ||||
|                 instanceRB.setOnCheckedChangeListener((buttonView, isChecked) -> { | ||||
|                     if (isChecked) { | ||||
|                         selectInstance(instance); | ||||
|                         if (lastChecked != null && lastChecked != instanceRB) { | ||||
|                             lastChecked.setChecked(false); | ||||
|                         } | ||||
|                         lastChecked = instanceRB; | ||||
|                     } | ||||
|                 }); | ||||
|                 instanceIconView.setImageResource(R.drawable.place_holder_peertube); | ||||
|             } | ||||
|  | ||||
|             @SuppressLint("ClickableViewAccessibility") | ||||
|             private View.OnTouchListener getOnTouchListener(final RecyclerView.ViewHolder item) { | ||||
|                 return (view, motionEvent) -> { | ||||
|                     if (motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) { | ||||
|                         if (itemTouchHelper != null && getItemCount() > 1) { | ||||
|                             itemTouchHelper.startDrag(item); | ||||
|                             return true; | ||||
|                         } | ||||
|                     } | ||||
|                     return false; | ||||
|                 }; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private ItemTouchHelper.SimpleCallback getItemTouchCallback() { | ||||
|         return new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN, | ||||
|                 ItemTouchHelper.START | ItemTouchHelper.END) { | ||||
|             @Override | ||||
|             public int interpolateOutOfBoundsScroll(RecyclerView recyclerView, int viewSize, | ||||
|                                                     int viewSizeOutOfBounds, int totalSize, | ||||
|                                                     long msSinceStartScroll) { | ||||
|                 final int standardSpeed = super.interpolateOutOfBoundsScroll(recyclerView, viewSize, | ||||
|                         viewSizeOutOfBounds, totalSize, msSinceStartScroll); | ||||
|                 final int minimumAbsVelocity = Math.max(12, | ||||
|                         Math.abs(standardSpeed)); | ||||
|                 return minimumAbsVelocity * (int) Math.signum(viewSizeOutOfBounds); | ||||
|             } | ||||
|  | ||||
|             @Override | ||||
|             public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder source, | ||||
|                                   RecyclerView.ViewHolder target) { | ||||
|                 if (source.getItemViewType() != target.getItemViewType() || | ||||
|                         instanceListAdapter == null) { | ||||
|                     return false; | ||||
|                 } | ||||
|  | ||||
|                 final int sourceIndex = source.getAdapterPosition(); | ||||
|                 final int targetIndex = target.getAdapterPosition(); | ||||
|                 instanceListAdapter.swapItems(sourceIndex, targetIndex); | ||||
|                 return true; | ||||
|             } | ||||
|  | ||||
|             @Override | ||||
|             public boolean isLongPressDragEnabled() { | ||||
|                 return false; | ||||
|             } | ||||
|  | ||||
|             @Override | ||||
|             public boolean isItemViewSwipeEnabled() { | ||||
|                 return true; | ||||
|             } | ||||
|  | ||||
|             @Override | ||||
|             public void onSwiped(RecyclerView.ViewHolder viewHolder, int swipeDir) { | ||||
|                 int position = viewHolder.getAdapterPosition(); | ||||
|                 // do not allow swiping the selected instance | ||||
|                 if(instanceList.get(position).getUrl().equals(selectedInstance.getUrl())) { | ||||
|                     instanceListAdapter.notifyItemChanged(position); | ||||
|                     return; | ||||
|                 } | ||||
|                 instanceList.remove(position); | ||||
|                 instanceListAdapter.notifyItemRemoved(position); | ||||
|  | ||||
|                 if (instanceList.isEmpty()) { | ||||
|                     instanceList.add(selectedInstance); | ||||
|                     instanceListAdapter.notifyItemInserted(0); | ||||
|                 } | ||||
|             } | ||||
|         }; | ||||
|     } | ||||
| } | ||||
| @@ -31,6 +31,12 @@ public class KioskTranslator { | ||||
|                 return c.getString(R.string.top_50); | ||||
|             case "New & hot": | ||||
|                 return c.getString(R.string.new_and_hot); | ||||
|             case "Local": | ||||
|                 return c.getString(R.string.local); | ||||
|             case "Recently added": | ||||
|                 return c.getString(R.string.recently_added); | ||||
|             case "Most liked": | ||||
|                 return c.getString(R.string.most_liked); | ||||
|             case "conferences": | ||||
|                 return c.getString(R.string.conferences); | ||||
|             default: | ||||
| @@ -46,6 +52,12 @@ public class KioskTranslator { | ||||
|                 return ThemeHelper.resolveResourceIdFromAttr(c, R.attr.ic_hot); | ||||
|             case "New & hot": | ||||
|                 return ThemeHelper.resolveResourceIdFromAttr(c, R.attr.ic_hot); | ||||
|             case "Local": | ||||
|                 return ThemeHelper.resolveResourceIdFromAttr(c, R.attr.ic_kiosk_local); | ||||
|             case "Recently added": | ||||
|                 return ThemeHelper.resolveResourceIdFromAttr(c, R.attr.ic_kiosk_recent); | ||||
|             case "Most liked": | ||||
|                 return ThemeHelper.resolveResourceIdFromAttr(c, R.attr.thumbs_up); | ||||
|             case "conferences": | ||||
|                 return ThemeHelper.resolveResourceIdFromAttr(c, R.attr.ic_hot); | ||||
|             default: | ||||
|   | ||||
| @@ -0,0 +1,65 @@ | ||||
| package org.schabi.newpipe.util; | ||||
|  | ||||
| import android.content.Context; | ||||
| import android.content.SharedPreferences; | ||||
| import android.preference.PreferenceManager; | ||||
|  | ||||
| import com.grack.nanojson.JsonArray; | ||||
| import com.grack.nanojson.JsonObject; | ||||
| import com.grack.nanojson.JsonParser; | ||||
| import com.grack.nanojson.JsonParserException; | ||||
| import com.grack.nanojson.JsonStringWriter; | ||||
| import com.grack.nanojson.JsonWriter; | ||||
|  | ||||
| import org.schabi.newpipe.R; | ||||
| import org.schabi.newpipe.extractor.ServiceList; | ||||
| import org.schabi.newpipe.extractor.services.peertube.PeertubeInstance; | ||||
|  | ||||
| import java.util.ArrayList; | ||||
| import java.util.Collections; | ||||
| import java.util.List; | ||||
|  | ||||
| public class PeertubeHelper { | ||||
|  | ||||
|     public static List<PeertubeInstance> getInstanceList(Context context) { | ||||
|         SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); | ||||
|         String savedInstanceListKey = context.getString(R.string.peertube_instance_list_key); | ||||
|         final String savedJson = sharedPreferences.getString(savedInstanceListKey, null); | ||||
|         if (null == savedJson) { | ||||
|             return Collections.singletonList(getCurrentInstance()); | ||||
|         } | ||||
|  | ||||
|         try { | ||||
|             JsonArray array = JsonParser.object().from(savedJson).getArray("instances"); | ||||
|             List<PeertubeInstance> result = new ArrayList<>(); | ||||
|             for (Object o : array) { | ||||
|                 if (o instanceof JsonObject) { | ||||
|                     JsonObject instance = (JsonObject) o; | ||||
|                     String name = instance.getString("name"); | ||||
|                     String url = instance.getString("url"); | ||||
|                     result.add(new PeertubeInstance(url, name)); | ||||
|                 } | ||||
|             } | ||||
|             return result; | ||||
|         } catch (JsonParserException e) { | ||||
|             return Collections.singletonList(getCurrentInstance()); | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     public static PeertubeInstance selectInstance(PeertubeInstance instance, Context context) { | ||||
|         SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); | ||||
|         String selectedInstanceKey = context.getString(R.string.peertube_selected_instance_key); | ||||
|         JsonStringWriter jsonWriter = JsonWriter.string().object(); | ||||
|         jsonWriter.value("name", instance.getName()); | ||||
|         jsonWriter.value("url", instance.getUrl()); | ||||
|         String jsonToSave = jsonWriter.end().done(); | ||||
|         sharedPreferences.edit().putString(selectedInstanceKey, jsonToSave).apply(); | ||||
|         ServiceList.PeerTube.setInstance(instance); | ||||
|         return instance; | ||||
|     } | ||||
|  | ||||
|     public static PeertubeInstance getCurrentInstance(){ | ||||
|         return ServiceList.PeerTube.getInstance(); | ||||
|     } | ||||
| } | ||||
| @@ -1,15 +1,22 @@ | ||||
| package org.schabi.newpipe.util; | ||||
|  | ||||
| import android.content.Context; | ||||
| import android.content.SharedPreferences; | ||||
| import android.preference.PreferenceManager; | ||||
|  | ||||
| import androidx.annotation.DrawableRes; | ||||
| import androidx.annotation.StringRes; | ||||
|  | ||||
| import com.grack.nanojson.JsonObject; | ||||
| import com.grack.nanojson.JsonParser; | ||||
| import com.grack.nanojson.JsonParserException; | ||||
|  | ||||
| import org.schabi.newpipe.R; | ||||
| import org.schabi.newpipe.extractor.NewPipe; | ||||
| import org.schabi.newpipe.extractor.ServiceList; | ||||
| import org.schabi.newpipe.extractor.StreamingService; | ||||
| import org.schabi.newpipe.extractor.exceptions.ExtractionException; | ||||
| import org.schabi.newpipe.extractor.services.peertube.PeertubeInstance; | ||||
|  | ||||
| import java.util.concurrent.TimeUnit; | ||||
|  | ||||
| @@ -27,13 +34,15 @@ public class ServiceHelper { | ||||
|                 return R.drawable.place_holder_cloud; | ||||
|             case 2: | ||||
|                 return R.drawable.place_holder_gadse; | ||||
|             case 3: | ||||
|                 return R.drawable.place_holder_peertube; | ||||
|             default: | ||||
|                 return R.drawable.place_holder_circle; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public static String getTranslatedFilterString(String filter, Context c) { | ||||
|         switch(filter) { | ||||
|         switch (filter) { | ||||
|             case "all": return c.getString(R.string.all); | ||||
|             case "videos": return c.getString(R.string.videos); | ||||
|             case "channels": return c.getString(R.string.channels); | ||||
| @@ -126,9 +135,36 @@ public class ServiceHelper { | ||||
|     } | ||||
|  | ||||
|     public static boolean isBeta(final StreamingService s) { | ||||
|         switch(s.getServiceInfo().getName()) { | ||||
|         switch (s.getServiceInfo().getName()) { | ||||
|             case "YouTube": return false; | ||||
|             default: return true; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public static void initService(Context context, int serviceId) { | ||||
|         if (serviceId == ServiceList.PeerTube.getServiceId()) { | ||||
|             SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); | ||||
|             String json = sharedPreferences.getString(context.getString(R.string.peertube_selected_instance_key), null); | ||||
|             if (null == json) { | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             JsonObject jsonObject = null; | ||||
|             try { | ||||
|                 jsonObject = JsonParser.object().from(json); | ||||
|             } catch (JsonParserException e) { | ||||
|                 return; | ||||
|             } | ||||
|             String name = jsonObject.getString("name"); | ||||
|             String url = jsonObject.getString("url"); | ||||
|             PeertubeInstance instance = new PeertubeInstance(url, name); | ||||
|             ServiceList.PeerTube.setInstance(instance); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public static void initServices(Context context) { | ||||
|         for (StreamingService s : ServiceList.all()) { | ||||
|             initService(context, s.getServiceId()); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/drawable-hdpi/ic_kiosk_local_black_24dp.png
									
									
									
									
									
										Executable file
									
								
							
							
						
						| After Width: | Height: | Size: 208 B | 
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/drawable-hdpi/ic_kiosk_local_white_24dp.png
									
									
									
									
									
										Executable file
									
								
							
							
						
						| After Width: | Height: | Size: 206 B | 
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/drawable-hdpi/ic_kiosk_recent_black_24dp.png
									
									
									
									
									
										Executable file
									
								
							
							
						
						| After Width: | Height: | Size: 475 B | 
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/drawable-hdpi/ic_kiosk_recent_white_24dp.png
									
									
									
									
									
										Executable file
									
								
							
							
						
						| After Width: | Height: | Size: 460 B | 
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/drawable-mdpi/ic_kiosk_local_black_24dp.png
									
									
									
									
									
										Executable file
									
								
							
							
						
						| After Width: | Height: | Size: 166 B | 
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/drawable-mdpi/ic_kiosk_local_white_24dp.png
									
									
									
									
									
										Executable file
									
								
							
							
						
						| After Width: | Height: | Size: 166 B | 
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/drawable-mdpi/ic_kiosk_recent_black_24dp.png
									
									
									
									
									
										Executable file
									
								
							
							
						
						| After Width: | Height: | Size: 311 B | 
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/drawable-mdpi/ic_kiosk_recent_white_24dp.png
									
									
									
									
									
										Executable file
									
								
							
							
						
						| After Width: | Height: | Size: 312 B | 
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/drawable-nodpi/place_holder_peertube.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 15 KiB | 
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/drawable-xhdpi/ic_kiosk_local_black_24dp.png
									
									
									
									
									
										Executable file
									
								
							
							
						
						| After Width: | Height: | Size: 235 B | 
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/drawable-xhdpi/ic_kiosk_local_white_24dp.png
									
									
									
									
									
										Executable file
									
								
							
							
						
						| After Width: | Height: | Size: 226 B | 
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/drawable-xhdpi/ic_kiosk_recent_black_24dp.png
									
									
									
									
									
										Executable file
									
								
							
							
						
						| After Width: | Height: | Size: 597 B | 
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/drawable-xhdpi/ic_kiosk_recent_white_24dp.png
									
									
									
									
									
										Executable file
									
								
							
							
						
						| After Width: | Height: | Size: 588 B | 
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/drawable-xxhdpi/ic_kiosk_local_black_24dp.png
									
									
									
									
									
										Executable file
									
								
							
							
						
						| After Width: | Height: | Size: 291 B | 
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/drawable-xxhdpi/ic_kiosk_local_white_24dp.png
									
									
									
									
									
										Executable file
									
								
							
							
						
						| After Width: | Height: | Size: 284 B | 
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/drawable-xxhdpi/ic_kiosk_recent_black_24dp.png
									
									
									
									
									
										Executable file
									
								
							
							
						
						| After Width: | Height: | Size: 856 B | 
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/drawable-xxhdpi/ic_kiosk_recent_white_24dp.png
									
									
									
									
									
										Executable file
									
								
							
							
						
						| After Width: | Height: | Size: 835 B | 
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/drawable-xxxhdpi/ic_kiosk_local_black_24dp.png
									
									
									
									
									
										Executable file
									
								
							
							
						
						| After Width: | Height: | Size: 344 B | 
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/drawable-xxxhdpi/ic_kiosk_local_white_24dp.png
									
									
									
									
									
										Executable file
									
								
							
							
						
						| After Width: | Height: | Size: 345 B | 
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/drawable-xxxhdpi/ic_kiosk_recent_black_24dp.png
									
									
									
									
									
										Executable file
									
								
							
							
						
						| After Width: | Height: | Size: 1.2 KiB | 
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/drawable-xxxhdpi/ic_kiosk_recent_white_24dp.png
									
									
									
									
									
										Executable file
									
								
							
							
						
						| After Width: | Height: | Size: 1.1 KiB | 
| @@ -47,15 +47,22 @@ | ||||
|  | ||||
|     <TextView | ||||
|         android:id="@+id/drawer_header_service_view" | ||||
|         android:layout_width="100dp" | ||||
|         android:layout_width="wrap_content" | ||||
|         android:layout_height="100dp" | ||||
|         android:layout_alignLeft="@id/drawer_header_np_text_view" | ||||
|         android:layout_alignStart="@id/drawer_header_np_text_view" | ||||
|         android:layout_below="@id/drawer_header_np_text_view" | ||||
|         android:layout_toLeftOf="@id/drawer_arrow" | ||||
|         android:layout_marginRight="5dp" | ||||
|         android:text="YouTube" | ||||
|         android:textSize="18sp" | ||||
|         android:textColor="@color/drawer_header_font_color" | ||||
|         android:textStyle="italic" /> | ||||
|         android:textStyle="italic" | ||||
|         android:ellipsize="marquee" | ||||
|         android:fadingEdge="horizontal" | ||||
|         android:marqueeRepeatLimit="marquee_forever" | ||||
|         android:scrollHorizontally="true" | ||||
|         android:singleLine="true" /> | ||||
|  | ||||
|     <ImageView | ||||
|         android:id="@+id/drawer_arrow" | ||||
|   | ||||
| @@ -46,15 +46,22 @@ android:focusable="true"> | ||||
|  | ||||
|     <TextView | ||||
|         android:id="@+id/drawer_header_service_view" | ||||
|         android:layout_width="100dp" | ||||
|         android:layout_width="wrap_content" | ||||
|         android:layout_height="100dp" | ||||
|         android:text="YouTube" | ||||
|         android:layout_below="@id/drawer_header_np_text_view" | ||||
|         android:layout_alignLeft="@id/drawer_header_np_text_view" | ||||
|         android:layout_alignStart="@id/drawer_header_np_text_view" | ||||
|         android:layout_toLeftOf="@id/drawer_arrow" | ||||
|         android:layout_marginRight="5dp" | ||||
|         android:textSize="18sp" | ||||
|         android:textColor="@color/drawer_header_font_color" | ||||
|         android:textStyle="italic"/> | ||||
|         android:textStyle="italic" | ||||
|         android:ellipsize="marquee" | ||||
|         android:fadingEdge="horizontal" | ||||
|         android:marqueeRepeatLimit="marquee_forever" | ||||
|         android:scrollHorizontally="true" | ||||
|         android:singleLine="true" /> | ||||
|  | ||||
|     <ImageView | ||||
|         android:id="@+id/drawer_arrow" | ||||
|   | ||||
| @@ -16,7 +16,8 @@ | ||||
|         android:layout_height="wrap_content" | ||||
|         app:elevation="0dp" | ||||
|         android:background="?attr/android:windowBackground" | ||||
|         app:headerLayout="@layout/drawer_header"/> | ||||
|         app:headerLayout="@layout/drawer_header" | ||||
|         android:theme="@style/NavViewTextStyle"/> | ||||
|         <!-- app:menu="@menu/drawer_items" --> | ||||
|  | ||||
|     <LinearLayout | ||||
|   | ||||
							
								
								
									
										51
									
								
								app/src/main/res/layout/fragment_instance_list.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,51 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     xmlns:app="http://schemas.android.com/apk/res-auto" | ||||
|     xmlns:tools="http://schemas.android.com/tools" | ||||
|     android:layout_width="match_parent" | ||||
|     android:layout_height="match_parent" | ||||
|     android:orientation="vertical"> | ||||
|  | ||||
|     <TextView | ||||
|         android:id="@+id/instanceHelpTV" | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:layout_margin="15dp" | ||||
|         android:autoLink="web" | ||||
|         android:text="@string/peertube_instance_url_help"/> | ||||
|  | ||||
|     <androidx.recyclerview.widget.RecyclerView | ||||
|         android:id="@+id/instances" | ||||
|         android:layout_below="@id/instanceHelpTV" | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="match_parent" | ||||
|         tools:listitem="@layout/item_instance" /> | ||||
|  | ||||
|     <!-- LOADING INDICATOR--> | ||||
|     <ProgressBar | ||||
|         android:id="@+id/loading_progress_bar" | ||||
|         style="@style/Widget.AppCompat.ProgressBar" | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:layout_centerInParent="true" | ||||
|         android:indeterminate="true" | ||||
|         android:visibility="gone" | ||||
|         tools:visibility="visible" /> | ||||
|  | ||||
|     <com.google.android.material.floatingactionbutton.FloatingActionButton | ||||
|         android:id="@+id/addInstanceButton" | ||||
|         android:layout_width="wrap_content" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:layout_alignParentEnd="true" | ||||
|         android:layout_alignParentRight="true" | ||||
|         android:layout_alignParentBottom="true" | ||||
|         android:layout_marginEnd="16dp" | ||||
|         android:layout_marginRight="16dp" | ||||
|         android:layout_marginBottom="16dp" | ||||
|         android:clickable="true" | ||||
|         android:focusable="true" | ||||
|         app:backgroundTint="?attr/colorPrimary" | ||||
|         app:fabSize="auto" | ||||
|         app:srcCompat="?attr/ic_add" /> | ||||
|  | ||||
| </RelativeLayout> | ||||
							
								
								
									
										6
									
								
								app/src/main/res/layout/instance_spinner_item.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,6 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <TextView xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     android:id="@android:id/text1" | ||||
|     android:layout_width="wrap_content" | ||||
|     android:layout_height="match_parent" | ||||
|     android:maxLength="0" /> | ||||
							
								
								
									
										9
									
								
								app/src/main/res/layout/instance_spinner_layout.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,9 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <Spinner xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     xmlns:tools="http://schemas.android.com/tools" | ||||
|     android:id="@+id/spinner" | ||||
|     tools:listitem="@layout/instance_spinner_item" | ||||
|     android:layout_width="wrap_content" | ||||
|     android:layout_height="wrap_content" | ||||
|     android:gravity="end" | ||||
|     android:prompt="@string/choose_instance_prompt" /> | ||||
							
								
								
									
										83
									
								
								app/src/main/res/layout/item_instance.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,83 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <androidx.cardview.widget.CardView | ||||
|     xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     xmlns:app="http://schemas.android.com/apk/res-auto" | ||||
|     xmlns:tools="http://schemas.android.com/tools" | ||||
|     android:id="@+id/layoutCard" | ||||
|     android:layout_width="match_parent" | ||||
|     android:layout_height="wrap_content" | ||||
|     android:layout_marginBottom="3dp" | ||||
|     android:layout_marginLeft="5dp" | ||||
|     android:layout_marginRight="5dp" | ||||
|     android:layout_marginTop="3dp" | ||||
|     android:minHeight="?listPreferredItemHeightSmall" | ||||
|     android:orientation="horizontal" | ||||
|     app:cardCornerRadius="5dp" | ||||
|     app:cardElevation="4dp"> | ||||
|  | ||||
|     <RelativeLayout | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="match_parent" | ||||
|         android:layout_gravity="center_vertical"> | ||||
|  | ||||
|         <androidx.appcompat.widget.AppCompatImageView | ||||
|             android:id="@+id/instanceIcon" | ||||
|             android:layout_width="24dp" | ||||
|             android:layout_height="24dp" | ||||
|             android:layout_centerVertical="true" | ||||
|             android:layout_alignParentLeft="true" | ||||
|             android:layout_marginLeft="10dp" | ||||
|             tools:ignore="ContentDescription,RtlHardcoded" | ||||
|             tools:src="@drawable/place_holder_peertube"/> | ||||
|  | ||||
|         <TextView | ||||
|             android:id="@+id/instanceName" | ||||
|             android:layout_width="match_parent" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:layout_marginLeft="10dp" | ||||
|             android:layout_marginTop="6dp" | ||||
|             android:layout_toRightOf="@+id/instanceIcon" | ||||
|             android:layout_toLeftOf="@id/selectInstanceRB" | ||||
|             android:singleLine="true" | ||||
|             android:ellipsize="marquee" | ||||
|             android:textAppearance="?textAppearanceListItem" | ||||
|             tools:ignore="RtlHardcoded" | ||||
|             tools:text="Framatube"/> | ||||
|  | ||||
|         <TextView | ||||
|             android:id="@+id/instanceUrl" | ||||
|             android:autoLink="web" | ||||
|             android:layout_width="match_parent" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:layout_marginBottom="6dp" | ||||
|             android:layout_marginLeft="10dp" | ||||
|             android:layout_toRightOf="@id/instanceIcon" | ||||
|             android:layout_toLeftOf="@id/selectInstanceRB" | ||||
|             android:layout_below="@id/instanceName" | ||||
|             android:singleLine="true" | ||||
|             android:ellipsize="marquee" | ||||
|             android:textAppearance="?textAppearanceListItemSecondary" | ||||
|             tools:ignore="RtlHardcoded" | ||||
|             tools:text="https://framatube.org"/> | ||||
|  | ||||
|         <RadioButton | ||||
|             android:id="@+id/selectInstanceRB" | ||||
|             android:layout_width="wrap_content" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:layout_toLeftOf="@id/handle" | ||||
|             android:layout_centerVertical="true"/> | ||||
|  | ||||
|         <androidx.appcompat.widget.AppCompatImageView | ||||
|             android:id="@+id/handle" | ||||
|             android:layout_width="wrap_content" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:layout_alignParentRight="true" | ||||
|             android:layout_centerVertical="true" | ||||
|             android:paddingBottom="12dp" | ||||
|             android:paddingLeft="16dp" | ||||
|             android:paddingRight="10dp" | ||||
|             android:paddingTop="12dp" | ||||
|             android:src="?attr/drag_handle" | ||||
|             tools:ignore="ContentDescription,RtlHardcoded"/> | ||||
|     </RelativeLayout> | ||||
| </androidx.cardview.widget.CardView> | ||||
| @@ -31,6 +31,25 @@ | ||||
|         <item name="colorAccent">@color/dark_soundcloud_accent_color</item> | ||||
|     </style> | ||||
|  | ||||
|     <!-- PeerTube --> | ||||
|     <style name="LightTheme.PeerTube" parent="LightTheme.Switchable"> | ||||
|         <item name="colorPrimary">@color/light_peertube_primary_color</item> | ||||
|         <item name="colorPrimaryDark">@color/light_peertube_dark_color</item> | ||||
|         <item name="colorAccent">@color/light_peertube_accent_color</item> | ||||
|     </style> | ||||
|  | ||||
|     <style name="DarkTheme.PeerTube" parent="DarkTheme.Switchable"> | ||||
|         <item name="colorPrimary">@color/dark_peertube_primary_color</item> | ||||
|         <item name="colorPrimaryDark">@color/dark_peertube_dark_color</item> | ||||
|         <item name="colorAccent">@color/dark_peertube_accent_color</item> | ||||
|     </style> | ||||
|  | ||||
|     <style name="BlackTheme.PeerTube" parent="BlackTheme.Switchable"> | ||||
|         <item name="colorPrimary">@color/dark_peertube_primary_color</item> | ||||
|         <item name="colorPrimaryDark">@color/dark_peertube_dark_color</item> | ||||
|         <item name="colorAccent">@color/dark_peertube_accent_color</item> | ||||
|     </style> | ||||
|  | ||||
|     <!-- Media.ccc --> | ||||
|     <style name="LightTheme.MediaCCC" parent="LightTheme.Switchable"> | ||||
|         <item name="colorPrimary">@color/light_media_ccc_primary_color</item> | ||||
| @@ -49,4 +68,5 @@ | ||||
|         <item name="colorPrimaryDark">@color/dark_media_ccc_statusbar_color</item> | ||||
|         <item name="colorAccent">@color/dark_media_ccc_accent_color</item> | ||||
|     </style> | ||||
|  | ||||
| </resources> | ||||
| @@ -29,6 +29,8 @@ | ||||
|     <attr name="bug" format="reference"/> | ||||
|     <attr name="settings" format="reference"/> | ||||
|     <attr name="ic_hot" format="reference"/> | ||||
|     <attr name="ic_kiosk_local" format="reference"/> | ||||
|     <attr name="ic_kiosk_recent" format="reference"/> | ||||
|     <attr name="ic_channel" format="reference"/> | ||||
|     <attr name="ic_bookmark" format="reference"/> | ||||
|     <attr name="ic_playlist_add" format="reference"/> | ||||
|   | ||||
| @@ -22,6 +22,17 @@ | ||||
|     <color name="dark_soundcloud_accent_color">#FFFFFF</color> | ||||
|     <color name="dark_soundcloud_statusbar_color">#ff9100</color> | ||||
|  | ||||
|     <!-- PeerTube --> | ||||
|     <color name="light_peertube_primary_color">#ff6f00</color> | ||||
|     <color name="light_peertube_dark_color">#c43e00</color> | ||||
|     <color name="light_peertube_accent_color">#000000</color> | ||||
|     <color name="light_peertube_statusbar_color">#ff833a</color> | ||||
|  | ||||
|     <color name="dark_peertube_primary_color">#ff6f00</color> | ||||
|     <color name="dark_peertube_dark_color">#c43e00</color> | ||||
|     <color name="dark_peertube_accent_color">#FFFFFF</color> | ||||
|     <color name="dark_peertube_statusbar_color">#ff833a</color> | ||||
|  | ||||
|     <!-- Media.CCC --> | ||||
|     <color name="light_media_ccc_primary_color">#9e9e9e</color> | ||||
|     <color name="light_media_ccc_dark_color">#616161</color> | ||||
|   | ||||
| @@ -145,6 +145,9 @@ | ||||
|     <string name="default_language_value">en</string> | ||||
|     <string name="default_country_value">GB</string> | ||||
|     <string name="content_language_key" translatable="false">content_language</string> | ||||
|     <string name="peertube_instance_setup_key" translatable="false">peertube_instance_setup</string> | ||||
|     <string name="peertube_selected_instance_key" translatable="false">peertube_selected_instance</string> | ||||
|     <string name="peertube_instance_list_key" translatable="false">peertube_instance_list</string> | ||||
|     <string name="content_country_key" translatable="false">content_country</string> | ||||
|     <string name="show_age_restricted_content" translatable="false">show_age_restricted_content</string> | ||||
|     <string name="use_tor_key" translatable="false">use_tor</string> | ||||
|   | ||||
| @@ -109,6 +109,14 @@ | ||||
|     <string name="default_content_country_title">Default content country</string> | ||||
|     <string name="service_title">Service</string> | ||||
|     <string name="content_language_title">Default content language</string> | ||||
|     <string name="peertube_instance_url_title">PeerTube instances</string> | ||||
|     <string name="peertube_instance_url_summary">Set your favorite peertube instances</string> | ||||
|     <string name="peertube_instance_url_help">Find the instances that best suit you on https://joinpeertube.org/instances#instances-list</string> | ||||
|     <string name="peertube_instance_add_title">Add instance</string> | ||||
|     <string name="peertube_instance_add_help">Enter instance url</string> | ||||
|     <string name="peertube_instance_add_fail">Failed to validate instance</string> | ||||
|     <string name="peertube_instance_add_https_only">Only https urls are supported</string> | ||||
|     <string name="peertube_instance_add_exists">Instance already exists</string> | ||||
|     <string name="settings_category_player_title">Player</string> | ||||
|     <string name="settings_category_player_behavior_title">Behavior</string> | ||||
|     <string name="settings_category_video_audio_title">Video & audio</string> | ||||
| @@ -400,6 +408,9 @@ | ||||
|     <string name="trending">Trending</string> | ||||
|     <string name="top_50">Top 50</string> | ||||
|     <string name="new_and_hot">New & hot</string> | ||||
|     <string name="local">Local</string> | ||||
|     <string name="recently_added">Recently added</string> | ||||
|     <string name="most_liked">Most liked</string> | ||||
|     <string name="conferences">Conferences</string> | ||||
|     <string name="service_kiosk_string" translatable="false">%1$s/%2$s</string> | ||||
|     <!-- Play Queue --> | ||||
| @@ -577,4 +588,6 @@ | ||||
|     <string name="downloads_storage_ask_summary_kitkat">You will be asked where to save each download.\nChoose SAF if you want to download to an external SD card</string> | ||||
|     <string name="downloads_storage_use_saf_title">Use SAF</string> | ||||
|     <string name="downloads_storage_use_saf_summary">The Storage Access Framework allows downloads to an external SD card.\nNote: some devices are not compatible</string> | ||||
|     <string name="choose_instance_prompt">Choose an instance</string> | ||||
|  | ||||
| </resources> | ||||
|   | ||||
| @@ -44,6 +44,8 @@ | ||||
|         <item name="pause">@drawable/ic_pause_black_24dp</item> | ||||
|         <item name="settings">@drawable/ic_settings_black_24dp</item> | ||||
|         <item name="ic_hot">@drawable/ic_whatshot_black_24dp</item> | ||||
|         <item name="ic_kiosk_local">@drawable/ic_kiosk_local_black_24dp</item> | ||||
|         <item name="ic_kiosk_recent">@drawable/ic_kiosk_recent_black_24dp</item> | ||||
|         <item name="ic_channel">@drawable/ic_channel_black_24dp</item> | ||||
|         <item name="ic_bookmark">@drawable/ic_bookmark_black_24dp</item> | ||||
|         <item name="ic_playlist_add">@drawable/ic_playlist_add_black_24dp</item> | ||||
| @@ -108,6 +110,8 @@ | ||||
|         <item name="play">@drawable/ic_play_arrow_white_24dp</item> | ||||
|         <item name="settings">@drawable/ic_settings_white_24dp</item> | ||||
|         <item name="ic_hot">@drawable/ic_whatshot_white_24dp</item> | ||||
|         <item name="ic_kiosk_local">@drawable/ic_kiosk_local_white_24dp</item> | ||||
|         <item name="ic_kiosk_recent">@drawable/ic_kiosk_recent_white_24dp</item> | ||||
|         <item name="ic_channel">@drawable/ic_channel_white_24dp</item> | ||||
|         <item name="ic_bookmark">@drawable/ic_bookmark_white_24dp</item> | ||||
|         <item name="ic_playlist_add">@drawable/ic_playlist_add_white_24dp</item> | ||||
| @@ -233,4 +237,8 @@ | ||||
|         <item name="android:windowIsTranslucent">true</item> | ||||
|         <item name="android:windowAnimationStyle">@null</item> | ||||
|     </style> | ||||
|  | ||||
|     <style name="NavViewTextStyle"> | ||||
|         <item name="android:ellipsize">end</item> | ||||
|     </style> | ||||
| </resources> | ||||
|   | ||||
| @@ -32,6 +32,25 @@ | ||||
|         <item name="progress_horizontal_drawable">@drawable/progress_soundcloud_horizontal_dark</item> | ||||
|     </style> | ||||
|  | ||||
|     <!-- PeerTube --> | ||||
|     <style name="LightTheme.PeerTube" parent="LightTheme.Switchable"> | ||||
|         <item name="colorPrimary">@color/light_peertube_primary_color</item> | ||||
|         <item name="colorPrimaryDark">@color/light_peertube_dark_color</item> | ||||
|         <item name="colorAccent">@color/light_peertube_accent_color</item> | ||||
|     </style> | ||||
|  | ||||
|     <style name="DarkTheme.PeerTube" parent="DarkTheme.Switchable"> | ||||
|         <item name="colorPrimary">@color/dark_peertube_primary_color</item> | ||||
|         <item name="colorPrimaryDark">@color/dark_peertube_dark_color</item> | ||||
|         <item name="colorAccent">@color/dark_peertube_accent_color</item> | ||||
|     </style> | ||||
|  | ||||
|     <style name="BlackTheme.PeerTube" parent="BlackTheme.Switchable"> | ||||
|         <item name="colorPrimary">@color/dark_peertube_primary_color</item> | ||||
|         <item name="colorPrimaryDark">@color/dark_peertube_dark_color</item> | ||||
|         <item name="colorAccent">@color/dark_peertube_accent_color</item> | ||||
|     </style> | ||||
|  | ||||
|     <!-- Media.ccc --> | ||||
|     <style name="LightTheme.MediaCCC" parent="LightTheme.Switchable"> | ||||
|         <item name="colorPrimary">@color/light_media_ccc_primary_color</item> | ||||
|   | ||||
| @@ -21,6 +21,13 @@ | ||||
|         android:summary="%s" | ||||
|         android:title="@string/default_content_country_title"/> | ||||
|  | ||||
|     <PreferenceScreen | ||||
|         app:iconSpaceReserved="false" | ||||
|         android:fragment="org.schabi.newpipe.settings.PeertubeInstanceListFragment" | ||||
|         android:key="@string/peertube_instance_setup_key" | ||||
|         android:title="@string/peertube_instance_url_title" | ||||
|         android:summary="@string/peertube_instance_url_summary"/> | ||||
|  | ||||
|     <SwitchPreference | ||||
|         app:iconSpaceReserved="false" | ||||
|         android:defaultValue="false" | ||||
|   | ||||
 Peter Hindes
					Peter Hindes