-
Notifications
You must be signed in to change notification settings - Fork 686
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
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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.*; | ||
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); | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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; | ||
|
@@ -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.*; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
|
@@ -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); | ||
|
@@ -667,6 +721,8 @@ public boolean onPreferenceChange(Preference preference, Object newValue) { | |
return true; | ||
} | ||
}); | ||
|
||
addCustomResolutionsEntries(); | ||
} | ||
} | ||
} |
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; | ||
} | ||
} |
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> | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Probably a good practice to have an empty blank line at the end of each file. |
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> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same comment