Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added option for creating custom resolutions #1349

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
package com.limelight.preferences;

import android.content.Context;
import android.content.SharedPreferences;
import android.preference.DialogPreference;
import android.text.Editable;
import android.text.InputType;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.widget.*;
import com.limelight.R;

import java.util.*;
Comment on lines +15 to +18
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment

import java.util.regex.Matcher;
import java.util.regex.Pattern;

class CustomResolutionsConsts {
public static final String CUSTOM_RESOLUTIONS_FILE = "custom_resolutions";
public static final String CUSTOM_RESOLUTIONS_KEY = "custom_resolutions";
}
class Validate {
static public boolean isValidResolution(String res){
String regex = "^\\d{3,5}x\\d{3,5}$";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(res);
return matcher.matches();
}
}

public class CustomResolutionsPreference extends DialogPreference {
private final Context context;
private final CustomResolutionsAdapter adapter;

public CustomResolutionsPreference(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
this.adapter = new CustomResolutionsAdapter(context);
adapter.setOnDataChangedListener(new UpdateStorageEventListener());
}

void onSubmitResolution(EditText field){
field.setError(null);

Editable editable = field.getText();
String content = editable.toString();


if(!Validate.isValidResolution(content)) {
field.setError("Invalid resolution.");
return;
}
if(adapter.exists(content)) {
field.setError("Item already exists.");
return;
}

adapter.addItem(content);
}

@Override
protected void onBindDialogView(View view) {
SharedPreferences prefs = context.getSharedPreferences(CustomResolutionsConsts.CUSTOM_RESOLUTIONS_FILE, Context.MODE_PRIVATE);
Set<String> stored = prefs.getStringSet(CustomResolutionsConsts.CUSTOM_RESOLUTIONS_KEY, null);

if(stored == null) return;

ArrayList<String> list = new ArrayList<>(stored);
Comparator<String> lengthComparator = new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
String[] s1Size = s1.split("x");
String[] s2Size = s2.split("x");

int w1 = Integer.parseInt(s1Size[0]);
int w2 = Integer.parseInt(s2Size[0]);

int h1 = Integer.parseInt(s1Size[1]);
int h2 = Integer.parseInt(s2Size[1]);

if(w1 == w2) {
return Integer.compare(h1, h2);
}
return Integer.compare(w1, w2);
}
};
Collections.sort(list, lengthComparator);

adapter.addAll(list);
super.onBindDialogView(view);
}

@Override
protected View onCreateDialogView() {
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, AbsListView.LayoutParams.WRAP_CONTENT);

LinearLayout body = new LinearLayout(context);
LayoutInflater inflater = LayoutInflater.from(context);
ListView list = new ListView(context);

body.setLayoutParams(layoutParams);
list.setLayoutParams(layoutParams);

View inputRow = inflater.inflate(R.layout.custom_resolutions_form, null);
EditText textEditingField = inputRow.findViewById(R.id.custom_resolution_field);
ImageButton doneEditingButton = inputRow.findViewById(R.id.done_editing);

body.addView(list);
body.addView(inputRow);
body.setOrientation(LinearLayout.VERTICAL);

inputRow.setLayoutParams(layoutParams);

textEditingField.setHint("2400x1080");
list.setAdapter(adapter);
doneEditingButton.setOnClickListener(view -> onSubmitResolution(textEditingField));

return body;
}

@Override
protected void onDialogClosed(boolean positiveResult) {
StreamSettings settingsActivity = (StreamSettings) getContext();
settingsActivity.reloadSettings();
}

private class UpdateStorageEventListener implements EventListener {
@Override
public void onTrigger() {
SharedPreferences prefs = context.getSharedPreferences(CustomResolutionsConsts.CUSTOM_RESOLUTIONS_FILE, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();
Set<String> set = new HashSet<>(adapter.getAll());
editor.putStringSet(CustomResolutionsConsts.CUSTOM_RESOLUTIONS_KEY, set).apply();
}
}
}

interface EventListener {
void onTrigger();
}

class CustomResolutionsAdapter extends BaseAdapter {
ArrayList<String> resolutions = new ArrayList<String>();
private final Context context;
private EventListener listener;

public CustomResolutionsAdapter(Context context) {
this.context = context;
}

public void setOnDataChangedListener(EventListener listener) {
this.listener = listener;
}

@Override
public void notifyDataSetChanged() {
if (listener != null) {
listener.onTrigger();
}
super.notifyDataSetChanged();
}

@Override
public View getView(int i, View view, ViewGroup viewGroup) {
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);

LinearLayout row = new LinearLayout(context);
TextView listItemText = new TextView(context);
ImageButton deleteButton = new ImageButton(context);

row.setLayoutParams(layoutParams);
row.setPadding(dpToPx(8), dpToPx(4), dpToPx(8), dpToPx(4));
row.setOrientation(LinearLayout.HORIZONTAL);

row.addView(listItemText);
row.addView(deleteButton);

layoutParams.gravity = Gravity.CENTER;
layoutParams.weight = 1;

listItemText.setLayoutParams(layoutParams);
listItemText.setText(resolutions.get(i));

LinearLayout.LayoutParams buttonParams = new LinearLayout.LayoutParams(dpToPx(64), dpToPx(64));

deleteButton.setLayoutParams(buttonParams);
deleteButton.setImageResource(R.drawable.ic_delete);

deleteButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
resolutions.remove(i);
notifyDataSetChanged();
}
});

view = row;
return view;
}

@Override
public int getCount() {
return resolutions.size();
}

@Override
public Object getItem(int i) {
return resolutions.get(i);
}

public void addItem(String value) {
if (resolutions.contains(value)) {
return;
}
resolutions.add(value);
notifyDataSetChanged();
}

public ArrayList<String> getAll() {
return resolutions;
}
public void addAll(ArrayList<String> list) {
this.resolutions.addAll(list);
notifyDataSetChanged();
}

public boolean exists(String item){
return resolutions.contains(item);
}

@Override
public long getItemId(int i) {
return 0;
}

private int dpToPx(int value) {
float density = this.context.getResources().getDisplayMetrics().density;
return (int) (value * density + 0.5f);
}
}

58 changes: 57 additions & 1 deletion app/src/main/java/com/limelight/preferences/StreamSettings.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import android.preference.PreferenceManager;
import android.preference.PreferenceScreen;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.Range;
import android.view.Display;
import android.view.DisplayCutout;
Expand All @@ -31,11 +32,12 @@
import com.limelight.PcView;
import com.limelight.R;
import com.limelight.binding.video.MediaCodecHelper;
import com.limelight.utils.AspectRatioConverter;
import com.limelight.utils.Dialog;
import com.limelight.utils.UiHelper;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.*;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know wildcard imports are used in a few other places, but I'm not sure it's a good practice.


public class StreamSettings extends Activity {
private PreferenceConfiguration previousPrefs;
Expand Down Expand Up @@ -188,6 +190,58 @@ private void addNativeResolutionEntries(int nativeWidth, int nativeHeight, boole
}
addNativeResolutionEntry(nativeWidth, nativeHeight, insetsRemoved, false);
}
private void addCustomResolutionsEntries() {
SharedPreferences storage = this.getActivity().getSharedPreferences(CustomResolutionsConsts.CUSTOM_RESOLUTIONS_FILE, Context.MODE_PRIVATE);
Set<String> stored = storage.getStringSet(CustomResolutionsConsts.CUSTOM_RESOLUTIONS_KEY, null);
ListPreference pref = (ListPreference) findPreference(PreferenceConfiguration.RESOLUTION_PREF_STRING);

List<CharSequence> preferencesList = Arrays.asList(pref.getEntryValues());

if(stored == null) {
return;
};

Comparator<String> lengthComparator = new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
String[] s1Size = s1.split("x");
String[] s2Size = s2.split("x");

int w1 = Integer.parseInt(s1Size[0]);
int w2 = Integer.parseInt(s2Size[0]);

int h1 = Integer.parseInt(s1Size[1]);
int h2 = Integer.parseInt(s2Size[1]);

if(w1 == w2) {
return Integer.compare(h1, h2);
}
return Integer.compare(w1, w2);
}
};

ArrayList<String> list = new ArrayList<>(stored);
Collections.sort(list, lengthComparator);

for (String storedResolution : list) {
if(preferencesList.contains(storedResolution)){
continue;
}
String[] resolution = storedResolution.split("x");
int width = Integer.parseInt(resolution[0]);
int height = Integer.parseInt(resolution[1]);
String aspectRatio = AspectRatioConverter.getAspectRatio(width,height);
String displayText = "Custom ";

if(aspectRatio != null){
displayText+=aspectRatio+" ";
}

displayText+="("+storedResolution+")";

appendPreferenceEntry(pref, displayText, storedResolution);
}
}

private void addNativeFrameRateEntry(float framerate) {
int frameRateRounded = Math.round(framerate);
Expand Down Expand Up @@ -667,6 +721,8 @@ public boolean onPreferenceChange(Preference preference, Object newValue) {
return true;
}
});

addCustomResolutionsEntries();
}
}
}
24 changes: 24 additions & 0 deletions app/src/main/java/com/limelight/utils/AspectRatioConverter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.limelight.utils;

import android.util.Log;

public class AspectRatioConverter {
public static String getAspectRatio(int width, int height) {
float ratio = (float) width / height;
float truncatedValue = (float) (Math.floor(ratio * 100) / 100);

if (truncatedValue == 1.25f) return "5:4";
if (truncatedValue == 1.33f) return "4:3";
if (truncatedValue == 1.50f) return "3:2";
if (truncatedValue == 1.60f) return "16:10";
if (truncatedValue == 1.77f) return "16:9";
if (truncatedValue == 1.85f) return "1.85:1";
if (truncatedValue == 2.22f) return "20:9";
if (truncatedValue >= 2.37f && truncatedValue <= 2.44f) return "21:9";
if (truncatedValue == 2.76f) return "2.76:1";
if (truncatedValue == 3.20f) return "32:10";
if (truncatedValue == 3.55f) return "32:9";

return null;
}
}
10 changes: 10 additions & 0 deletions app/src/main/res/drawable/ic_delete.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32dp"
android:height="32dp"
android:viewportWidth="20"
android:viewportHeight="20">
<path
android:fillColor="#FFFFFF"
android:pathData="M17.114,3.923h-4.589V2.427c0-0.252-0.207-0.459-0.46-0.459H7.935c-0.252,0-0.459,0.207-0.459,0.459v1.496h-4.59c-0.252,0-0.459,0.205-0.459,0.459c0,0.252,0.207,0.459,0.459,0.459h1.51v12.732c0,0.252,0.207,0.459,0.459,0.459h10.29c0.254,0,0.459-0.207,0.459-0.459V4.841h1.511c0.252,0,0.459-0.207,0.459-0.459C17.573,4.127,17.366,3.923,17.114,3.923M8.394,2.886h3.214v0.918H8.394V2.886z M14.686,17.114H5.314V4.841h9.372V17.114z M12.525,7.306v7.344c0,0.252-0.207,0.459-0.46,0.459s-0.458-0.207-0.458-0.459V7.306c0-0.254,0.205-0.459,0.458-0.459S12.525,7.051,12.525,7.306M8.394,7.306v7.344c0,0.252-0.207,0.459-0.459,0.459s-0.459-0.207-0.459-0.459V7.306c0-0.254,0.207-0.459,0.459-0.459S8.394,7.051,8.394,7.306"/>
</vector>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
</vector>
</vector>

Probably a good practice to have an empty blank line at the end of each file.

11 changes: 11 additions & 0 deletions app/src/main/res/drawable/ic_done.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32dp"
android:height="32dp"
android:viewportWidth="20"
android:viewportHeight="20">
<path
android:fillColor="#FFFFFF"
android:pathData="M7.197,16.963H7.195c-0.204,0-0.399-0.083-0.544-0.227l-6.039-6.082c-0.3-0.302-0.297-0.788,0.003-1.087
C0.919,9.266,1.404,9.269,1.702,9.57l5.495,5.536L18.221,4.083c0.301-0.301,0.787-0.301,1.087,0c0.301,0.3,0.301,0.787,0,1.087
L7.741,16.738C7.596,16.882,7.401,16.963,7.197,16.963z"/>
</vector>
Loading