easily switch between multiple peertube instances

This commit is contained in:
yausername 2019-11-24 21:08:06 +05:30
parent afef8d8d0b
commit 527c38adf9
14 changed files with 724 additions and 64 deletions

View file

@ -62,7 +62,7 @@ dependencies {
exclude module: 'support-annotations' exclude module: 'support-annotations'
}) })
implementation 'com.github.yausername:NewPipeExtractor:bc75c66' implementation 'com.github.yausername:NewPipeExtractor:6a7680c'
testImplementation 'junit:junit:4.12' testImplementation 'junit:junit:4.12'
testImplementation 'org.mockito:mockito-core:2.23.0' testImplementation 'org.mockito:mockito-core:2.23.0'

View file

@ -29,14 +29,18 @@ import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.util.Log; import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.Window; import android.view.Window;
import android.view.WindowManager; import android.view.WindowManager;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button; import android.widget.Button;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.Spinner;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
@ -47,12 +51,15 @@ import androidx.appcompat.widget.Toolbar;
import androidx.core.view.GravityCompat; import androidx.core.view.GravityCompat;
import androidx.drawerlayout.widget.DrawerLayout; import androidx.drawerlayout.widget.DrawerLayout;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import com.google.android.material.navigation.NavigationView; import com.google.android.material.navigation.NavigationView;
import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.ServiceList;
import org.schabi.newpipe.extractor.StreamingService; import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.exceptions.ExtractionException; 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.BackPressable;
import org.schabi.newpipe.fragments.MainFragment; import org.schabi.newpipe.fragments.MainFragment;
import org.schabi.newpipe.fragments.detail.VideoDetailFragment; 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.Constants;
import org.schabi.newpipe.util.KioskTranslator; import org.schabi.newpipe.util.KioskTranslator;
import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.PeertubeHelper;
import org.schabi.newpipe.util.PermissionHelper; import org.schabi.newpipe.util.PermissionHelper;
import org.schabi.newpipe.util.ServiceHelper; import org.schabi.newpipe.util.ServiceHelper;
import org.schabi.newpipe.util.StateSaver; import org.schabi.newpipe.util.StateSaver;
import org.schabi.newpipe.util.ThemeHelper; import org.schabi.newpipe.util.ThemeHelper;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity { public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity"; private static final String TAG = "MainActivity";
public static final boolean DEBUG = !BuildConfig.BUILD_TYPE.equals("release"); 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() + final String title = s.getServiceInfo().getName() +
(ServiceHelper.isBeta(s) ? " (beta)" : ""); (ServiceHelper.isBeta(s) ? " (beta)" : "");
drawerItems.getMenu() MenuItem menuItem = drawerItems.getMenu()
.add(R.id.menu_services_group, s.getServiceId(), ORDER, title) .add(R.id.menu_services_group, s.getServiceId(), ORDER, title)
.setIcon(ServiceHelper.getIcon(s.getServiceId())); .setIcon(ServiceHelper.getIcon(s.getServiceId()));
// peertube specifics
if(s.getServiceId() == 3){
enhancePeertubeMenu(s, menuItem);
}
} }
drawerItems.getMenu().getItem(ServiceHelper.getSelectedServiceId(this)).setChecked(true); 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 { private void showTabs() throws ExtractionException {
serviceArrow.setImageResource(R.drawable.ic_arrow_down_white); serviceArrow.setImageResource(R.drawable.ic_arrow_down_white);

View file

@ -12,23 +12,18 @@ import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.preference.EditTextPreference;
import androidx.preference.Preference; import androidx.preference.Preference;
import com.nononsenseapps.filepicker.Utils; import com.nononsenseapps.filepicker.Utils;
import com.nostra13.universalimageloader.core.ImageLoader; import com.nostra13.universalimageloader.core.ImageLoader;
import org.schabi.newpipe.App;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.ServiceList;
import org.schabi.newpipe.extractor.localization.ContentCountry; import org.schabi.newpipe.extractor.localization.ContentCountry;
import org.schabi.newpipe.extractor.localization.Localization; import org.schabi.newpipe.extractor.localization.Localization;
import org.schabi.newpipe.extractor.services.peertube.PeertubeInstance;
import org.schabi.newpipe.report.ErrorActivity; import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.FilePickerActivityHelper; import org.schabi.newpipe.util.FilePickerActivityHelper;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.ZipHelper; import org.schabi.newpipe.util.ZipHelper;
import java.io.BufferedOutputStream; import java.io.BufferedOutputStream;
@ -46,11 +41,6 @@ import java.util.Map;
import java.util.zip.ZipFile; import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream; import java.util.zip.ZipOutputStream;
import io.reactivex.Completable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
public class ContentSettingsFragment extends BasePreferenceFragment { public class ContentSettingsFragment extends BasePreferenceFragment {
private static final int REQUEST_IMPORT_PATH = 8945; private static final int REQUEST_IMPORT_PATH = 8945;
@ -127,40 +117,6 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
return true; return true;
}); });
Preference peerTubeInstance = findPreference(getString(R.string.peertube_instance_url_key));
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getContext());
peerTubeInstance.setDefaultValue(sharedPreferences.getString(getString(R.string.peertube_instance_url_key), ServiceList.PeerTube.getBaseUrl()));
peerTubeInstance.setSummary(sharedPreferences.getString(getString(R.string.peertube_instance_url_key), ServiceList.PeerTube.getBaseUrl()));
peerTubeInstance.setOnPreferenceChangeListener((Preference p, Object newInstance) -> {
EditTextPreference pEt = (EditTextPreference) p;
String url = (String) newInstance;
if (!url.startsWith("https://")) {
Toast.makeText(getActivity(), "instance url should start with https://",
Toast.LENGTH_SHORT).show();
} else {
pEt.setSummary("fetching instance details..");
Disposable disposable = Completable.fromAction(() -> {
PeertubeInstance instance = new PeertubeInstance(url);
instance.fetchInstanceMetaData();
ServiceList.PeerTube.setInstance(instance);
}).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(() -> {
pEt.setSummary(url);
pEt.setText(url);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString(App.getApp().getString(R.string.peertube_instance_name_key), ServiceList.PeerTube.getServiceInfo().getName()).apply();
editor.putString(App.getApp().getString(R.string.current_service_key), ServiceList.PeerTube.getServiceInfo().getName()).apply();
NavigationHelper.openMainActivity(App.getApp());
}, error -> {
pEt.setSummary(ServiceList.PeerTube.getBaseUrl());
Toast.makeText(getActivity(), "unable to update instance",
Toast.LENGTH_SHORT).show();
});
}
return false;
});
} }
@Override @Override

View file

@ -0,0 +1,418 @@
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.ServiceList;
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 = verifyUrl(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(), "failed to validate instance", Toast.LENGTH_SHORT).show();
});
disposables.add(disposable);
}
@Nullable
private String verifyUrl(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(), "instance url should start with https://", Toast.LENGTH_SHORT).show();
return null;
}
// only allow if not already exists
for (PeertubeInstance instance : instanceList) {
if (instance.getUrl().equals(url)) {
Toast.makeText(getActivity(), "instance already 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);
}
}
};
}
}

View file

@ -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();
}
}

View file

@ -3,9 +3,14 @@ package org.schabi.newpipe.util;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import androidx.annotation.DrawableRes; import androidx.annotation.DrawableRes;
import androidx.annotation.StringRes; 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.R;
import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.ServiceList; import org.schabi.newpipe.extractor.ServiceList;
@ -139,9 +144,20 @@ public class ServiceHelper {
public static void initService(Context context, int serviceId) { public static void initService(Context context, int serviceId) {
if (serviceId == ServiceList.PeerTube.getServiceId()) { if (serviceId == ServiceList.PeerTube.getServiceId()) {
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
String peerTubeInstanceUrl = sharedPreferences.getString(context.getString(R.string.peertube_instance_url_key), ServiceList.PeerTube.getBaseUrl()); String json = sharedPreferences.getString(context.getString(R.string.peertube_selected_instance_key), null);
String peerTubeInstanceName = sharedPreferences.getString(context.getString(R.string.peertube_instance_name_key), ServiceList.PeerTube.getServiceInfo().getName()); if (null == json) {
PeertubeInstance instance = new PeertubeInstance(peerTubeInstanceUrl, peerTubeInstanceName); 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); ServiceList.PeerTube.setInstance(instance);
} }
} }

View file

@ -137,12 +137,7 @@ public class ThemeHelper {
else if (selectedTheme.equals(blackTheme)) themeName = "BlackTheme"; else if (selectedTheme.equals(blackTheme)) themeName = "BlackTheme";
else if (selectedTheme.equals(darkTheme)) themeName = "DarkTheme"; else if (selectedTheme.equals(darkTheme)) themeName = "DarkTheme";
if(serviceId == ServiceList.PeerTube.getServiceId()){
//service name for peertube depends on the instance
themeName += ".PeerTube";
}else{
themeName += "." + service.getServiceInfo().getName(); themeName += "." + service.getServiceInfo().getName();
}
int resourceId = context int resourceId = context
.getResources() .getResources()

View 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>

View 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" />

View 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" />

View 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>

View file

@ -144,8 +144,9 @@
<string name="default_language_value">en</string> <string name="default_language_value">en</string>
<string name="default_country_value">GB</string> <string name="default_country_value">GB</string>
<string name="content_language_key" translatable="false">content_language</string> <string name="content_language_key" translatable="false">content_language</string>
<string name="peertube_instance_url_key" translatable="false">peertube_instance_url</string> <string name="peertube_instance_setup_key" translatable="false">peertube_instance_setup</string>
<string name="peertube_instance_name_key" translatable="false">peertube_instance_name</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="content_country_key" translatable="false">content_country</string>
<string name="show_age_restricted_content" translatable="false">show_age_restricted_content</string> <string name="show_age_restricted_content" translatable="false">show_age_restricted_content</string>
<string name="use_tor_key" translatable="false">use_tor</string> <string name="use_tor_key" translatable="false">use_tor</string>

View file

@ -109,8 +109,11 @@
<string name="default_content_country_title">Default content country</string> <string name="default_content_country_title">Default content country</string>
<string name="service_title">Service</string> <string name="service_title">Service</string>
<string name="content_language_title">Default content language</string> <string name="content_language_title">Default content language</string>
<string name="peertube_instance_url_title">PeerTube instance</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 instance that best suits you on https://instances.joinpeertube.org</string> <string name="peertube_instance_url_help">Find the instance that best suits you on https://instances.joinpeertube.org</string>
<string name="peertube_instance_add_title">Add instance</string>
<string name="peertube_instance_add_help">enter instance url</string>
<string name="settings_category_player_title">Player</string> <string name="settings_category_player_title">Player</string>
<string name="settings_category_player_behavior_title">Behavior</string> <string name="settings_category_player_behavior_title">Behavior</string>
<string name="settings_category_video_audio_title">Video &amp; audio</string> <string name="settings_category_video_audio_title">Video &amp; audio</string>
@ -578,4 +581,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_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_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="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> </resources>

View file

@ -12,12 +12,12 @@
android:summary="%s" android:summary="%s"
android:title="@string/content_language_title"/> android:title="@string/content_language_title"/>
<EditTextPreference <PreferenceScreen
app:iconSpaceReserved="false" app:iconSpaceReserved="false"
android:dialogMessage="@string/peertube_instance_url_help" android:fragment="org.schabi.newpipe.settings.PeertubeInstanceListFragment"
android:key="@string/peertube_instance_url_key" android:key="@string/peertube_instance_setup_key"
android:title="@string/peertube_instance_url_title" android:title="@string/peertube_instance_url_title"
android:inputType="textUri" /> android:summary="@string/peertube_instance_url_summary"/>
<ListPreference <ListPreference
app:iconSpaceReserved="false" app:iconSpaceReserved="false"