mirror of
https://github.com/revanced/revanced-integrations.git
synced 2024-11-09 13:47:02 +01:00
feat(twitch): integrations code for patches (#216)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
This commit is contained in:
parent
24367cea3f
commit
d4c3b74a9a
@ -45,4 +45,5 @@ android {
|
|||||||
dependencies {
|
dependencies {
|
||||||
compileOnly(project(mapOf("path" to ":dummy")))
|
compileOnly(project(mapOf("path" to ":dummy")))
|
||||||
compileOnly("androidx.annotation:annotation:1.5.0")
|
compileOnly("androidx.annotation:annotation:1.5.0")
|
||||||
|
compileOnly("androidx.appcompat:appcompat:1.5.1")
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,5 @@
|
|||||||
|
package app.revanced.twitch.settings;
|
||||||
|
|
||||||
|
public enum ReturnType {
|
||||||
|
BOOLEAN, INTEGER, STRING, LONG, FLOAT
|
||||||
|
}
|
149
app/src/main/java/app/revanced/twitch/settings/SettingsEnum.java
Normal file
149
app/src/main/java/app/revanced/twitch/settings/SettingsEnum.java
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
package app.revanced.twitch.settings;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
|
||||||
|
import app.revanced.twitch.utils.LogHelper;
|
||||||
|
import app.revanced.twitch.utils.ReVancedUtils;
|
||||||
|
|
||||||
|
public enum SettingsEnum {
|
||||||
|
/* Ads */
|
||||||
|
BLOCK_VIDEO_ADS("revanced_block_video_ads", true, ReturnType.BOOLEAN),
|
||||||
|
BLOCK_AUDIO_ADS("revanced_block_audio_ads", true, ReturnType.BOOLEAN),
|
||||||
|
|
||||||
|
/* Chat */
|
||||||
|
SHOW_DELETED_MESSAGES("revanced_show_deleted_messages", "cross-out", ReturnType.STRING),
|
||||||
|
|
||||||
|
/* Misc */
|
||||||
|
DEBUG_MODE("revanced_debug_mode", false, ReturnType.BOOLEAN, true);
|
||||||
|
|
||||||
|
public static final String REVANCED_PREFS = "revanced_prefs";
|
||||||
|
|
||||||
|
private final String path;
|
||||||
|
private final Object defaultValue;
|
||||||
|
private final ReturnType returnType;
|
||||||
|
private final boolean rebootApp;
|
||||||
|
|
||||||
|
private Object value = null;
|
||||||
|
|
||||||
|
SettingsEnum(String path, Object defaultValue, ReturnType returnType) {
|
||||||
|
this.path = path;
|
||||||
|
this.defaultValue = defaultValue;
|
||||||
|
this.returnType = returnType;
|
||||||
|
this.rebootApp = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsEnum(String path, Object defaultValue, ReturnType returnType, Boolean rebootApp) {
|
||||||
|
this.path = path;
|
||||||
|
this.defaultValue = defaultValue;
|
||||||
|
this.returnType = returnType;
|
||||||
|
this.rebootApp = rebootApp;
|
||||||
|
}
|
||||||
|
|
||||||
|
static {
|
||||||
|
load();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void load() {
|
||||||
|
ReVancedUtils.ifContextAttached((context -> {
|
||||||
|
try {
|
||||||
|
SharedPreferences prefs = context.getSharedPreferences(REVANCED_PREFS, Context.MODE_PRIVATE);
|
||||||
|
for (SettingsEnum setting : values()) {
|
||||||
|
Object value = setting.getDefaultValue();
|
||||||
|
|
||||||
|
try {
|
||||||
|
switch (setting.getReturnType()) {
|
||||||
|
case BOOLEAN:
|
||||||
|
value = prefs.getBoolean(setting.getPath(), (boolean)setting.getDefaultValue());
|
||||||
|
break;
|
||||||
|
// Numbers are implicitly converted from strings
|
||||||
|
case FLOAT:
|
||||||
|
case LONG:
|
||||||
|
case INTEGER:
|
||||||
|
case STRING:
|
||||||
|
value = prefs.getString(setting.getPath(), setting.getDefaultValue() + "");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LogHelper.error("Setting '%s' does not have a valid type", setting.name());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (ClassCastException ex) {
|
||||||
|
LogHelper.printException("Failed to read value", ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
setting.setValue(value);
|
||||||
|
LogHelper.debug("Loaded setting '%s' with value %s", setting.name(), value);
|
||||||
|
}
|
||||||
|
} catch (Throwable th) {
|
||||||
|
LogHelper.printException("Failed to load settings", th);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setValue(Object newValue) {
|
||||||
|
// Implicitly convert strings to numbers depending on the ResultType
|
||||||
|
switch (returnType) {
|
||||||
|
case FLOAT:
|
||||||
|
value = Float.valueOf(newValue + "");
|
||||||
|
break;
|
||||||
|
case LONG:
|
||||||
|
value = Long.valueOf(newValue + "");
|
||||||
|
break;
|
||||||
|
case INTEGER:
|
||||||
|
value = Integer.valueOf(newValue + "");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
value = newValue;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void saveValue(Object newValue) {
|
||||||
|
ReVancedUtils.ifContextAttached((context) -> {
|
||||||
|
SharedPreferences prefs = context.getSharedPreferences(REVANCED_PREFS, Context.MODE_PRIVATE);
|
||||||
|
if (returnType == ReturnType.BOOLEAN) {
|
||||||
|
prefs.edit().putBoolean(path, (Boolean)newValue).apply();
|
||||||
|
} else {
|
||||||
|
prefs.edit().putString(path, newValue + "").apply();
|
||||||
|
}
|
||||||
|
value = newValue;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getInt() {
|
||||||
|
return (int) value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getString() {
|
||||||
|
return (String) value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getBoolean() {
|
||||||
|
return (Boolean) value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getLong() {
|
||||||
|
return (Long) value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Float getFloat() {
|
||||||
|
return (Float) value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getDefaultValue() {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPath() {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReturnType getReturnType() {
|
||||||
|
return returnType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean shouldRebootOnChange() {
|
||||||
|
return rebootApp;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,125 @@
|
|||||||
|
package app.revanced.twitch.settingsmenu;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.app.AlarmManager;
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.app.PendingIntent;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Process;
|
||||||
|
import android.preference.EditTextPreference;
|
||||||
|
import android.preference.ListPreference;
|
||||||
|
import android.preference.Preference;
|
||||||
|
import android.preference.PreferenceFragment;
|
||||||
|
import android.preference.PreferenceManager;
|
||||||
|
import android.preference.SwitchPreference;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import app.revanced.twitch.settings.SettingsEnum;
|
||||||
|
import app.revanced.twitch.utils.LogHelper;
|
||||||
|
import app.revanced.twitch.utils.ReVancedUtils;
|
||||||
|
|
||||||
|
import tv.twitch.android.app.core.LandingActivity;
|
||||||
|
|
||||||
|
public class ReVancedSettingsFragment extends PreferenceFragment {
|
||||||
|
|
||||||
|
private boolean registered = false;
|
||||||
|
private boolean settingsInitialized = false;
|
||||||
|
|
||||||
|
SharedPreferences.OnSharedPreferenceChangeListener listener = (sharedPreferences, key) -> {
|
||||||
|
LogHelper.debug("Setting '%s' changed", key);
|
||||||
|
syncPreference(key);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sync preference
|
||||||
|
* @param key Preference to load. If key is null, all preferences are updated
|
||||||
|
*/
|
||||||
|
private void syncPreference(@Nullable String key) {
|
||||||
|
for (SettingsEnum setting : SettingsEnum.values()) {
|
||||||
|
if (!setting.getPath().equals(key) && key != null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
Preference pref = this.findPreference(setting.getPath());
|
||||||
|
LogHelper.debug("Syncing setting '%s' with UI", setting.getPath());
|
||||||
|
|
||||||
|
if (pref instanceof SwitchPreference) {
|
||||||
|
setting.setValue(((SwitchPreference) pref).isChecked());
|
||||||
|
}
|
||||||
|
else if (pref instanceof EditTextPreference) {
|
||||||
|
setting.setValue(((EditTextPreference) pref).getText());
|
||||||
|
}
|
||||||
|
else if (pref instanceof ListPreference) {
|
||||||
|
ListPreference listPref = (ListPreference) pref;
|
||||||
|
listPref.setSummary(listPref.getEntry());
|
||||||
|
setting.setValue(listPref.getValue());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
LogHelper.error("Setting '%s' cannot be handled!", pref);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ReVancedUtils.getContext() != null && key != null && settingsInitialized && setting.shouldRebootOnChange()) {
|
||||||
|
rebootDialog(getActivity());
|
||||||
|
}
|
||||||
|
|
||||||
|
// First onChange event is caused by initial state loading
|
||||||
|
this.settingsInitialized = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("ResourceType")
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle bundle) {
|
||||||
|
super.onCreate(bundle);
|
||||||
|
|
||||||
|
try {
|
||||||
|
PreferenceManager mgr = getPreferenceManager();
|
||||||
|
mgr.setSharedPreferencesName(SettingsEnum.REVANCED_PREFS);
|
||||||
|
mgr.getSharedPreferences().registerOnSharedPreferenceChangeListener(this.listener);
|
||||||
|
|
||||||
|
addPreferencesFromResource(
|
||||||
|
getResources().getIdentifier(
|
||||||
|
SettingsEnum.REVANCED_PREFS,
|
||||||
|
"xml",
|
||||||
|
this.getContext().getPackageName()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Sync all preferences with UI
|
||||||
|
syncPreference(null);
|
||||||
|
|
||||||
|
this.registered = true;
|
||||||
|
} catch (Throwable th) {
|
||||||
|
LogHelper.printException("Error during onCreate()", th);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
if (this.registered) {
|
||||||
|
getPreferenceManager().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this.listener);
|
||||||
|
this.registered = false;
|
||||||
|
}
|
||||||
|
super.onDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("MissingPermission")
|
||||||
|
private void reboot(Activity activity) {
|
||||||
|
int flags;
|
||||||
|
flags = PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE;
|
||||||
|
((AlarmManager) activity.getSystemService(Context.ALARM_SERVICE)).setExact(AlarmManager.ELAPSED_REALTIME, 1500L, PendingIntent.getActivity(activity, 0, new Intent(activity, LandingActivity.class), flags));
|
||||||
|
Process.killProcess(Process.myPid());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void rebootDialog(final Activity activity) {
|
||||||
|
new AlertDialog.Builder(activity).
|
||||||
|
setMessage(ReVancedUtils.getString("revanced_reboot_message")).
|
||||||
|
setPositiveButton(ReVancedUtils.getString("revanced_reboot"), (dialog, i) -> reboot(activity))
|
||||||
|
.setNegativeButton(ReVancedUtils.getString("revanced_cancel"), null)
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,110 @@
|
|||||||
|
package app.revanced.twitch.settingsmenu;
|
||||||
|
|
||||||
|
import static app.revanced.twitch.utils.ReVancedUtils.getIdentifier;
|
||||||
|
import static app.revanced.twitch.utils.ReVancedUtils.getStringId;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import androidx.appcompat.app.ActionBar;
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import app.revanced.twitch.utils.ReVancedUtils;
|
||||||
|
import app.revanced.twitch.utils.LogHelper;
|
||||||
|
import tv.twitch.android.feature.settings.menu.SettingsMenuGroup;
|
||||||
|
import tv.twitch.android.settings.SettingsActivity;
|
||||||
|
|
||||||
|
public class SettingsHooks {
|
||||||
|
private static final int REVANCED_SETTINGS_MENU_ITEM_ID = 0x7;
|
||||||
|
private static final String EXTRA_REVANCED_SETTINGS = "app.revanced.twitch.settings";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Launches SettingsActivity and show ReVanced settings
|
||||||
|
*/
|
||||||
|
public static void startSettingsActivity() {
|
||||||
|
LogHelper.debug("Launching ReVanced settings");
|
||||||
|
|
||||||
|
ReVancedUtils.ifContextAttached((c) -> {
|
||||||
|
Intent intent = new Intent(c, SettingsActivity.class);
|
||||||
|
Bundle bundle = new Bundle();
|
||||||
|
bundle.putBoolean(EXTRA_REVANCED_SETTINGS, true);
|
||||||
|
intent.putExtras(bundle);
|
||||||
|
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
c.startActivity(intent);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper for easy access in smali
|
||||||
|
* @return Returns string resource id
|
||||||
|
*/
|
||||||
|
public static int getReVancedSettingsString() {
|
||||||
|
return getStringId("revanced_settings");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Intercepts settings menu group list creation in SettingsMenuPresenter$Event.MenuGroupsUpdated
|
||||||
|
* @return Returns a modified list of menu groups
|
||||||
|
*/
|
||||||
|
public static List<SettingsMenuGroup> handleSettingMenuCreation(List<SettingsMenuGroup> settingGroups, Object revancedEntry) {
|
||||||
|
List<SettingsMenuGroup> groups = new ArrayList<>(settingGroups);
|
||||||
|
|
||||||
|
if(groups.size() < 1) {
|
||||||
|
// Create new menu group if none exist yet
|
||||||
|
List<Object> items = new ArrayList<>();
|
||||||
|
items.add(revancedEntry);
|
||||||
|
groups.add(new SettingsMenuGroup(items));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Add to last menu group
|
||||||
|
int groupIdx = groups.size() - 1;
|
||||||
|
List<Object> items = new ArrayList<>(groups.remove(groupIdx).getSettingsMenuItems());
|
||||||
|
items.add(revancedEntry);
|
||||||
|
groups.add(new SettingsMenuGroup(items));
|
||||||
|
}
|
||||||
|
|
||||||
|
LogHelper.debug("%d menu groups in list", settingGroups.size());
|
||||||
|
return groups;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Intercepts settings menu group onclick events
|
||||||
|
* @return Returns true if handled, otherwise false
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
public static boolean handleSettingMenuOnClick(Enum item) {
|
||||||
|
LogHelper.debug("item %d clicked", item.ordinal());
|
||||||
|
if(item.ordinal() != REVANCED_SETTINGS_MENU_ITEM_ID) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
startSettingsActivity();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Intercepts fragment loading in SettingsActivity.onCreate
|
||||||
|
* @return Returns true if the revanced settings have been requested by the user, otherwise false
|
||||||
|
*/
|
||||||
|
public static boolean handleSettingsCreation(AppCompatActivity base) {
|
||||||
|
if(!base.getIntent().getBooleanExtra(EXTRA_REVANCED_SETTINGS, false)) {
|
||||||
|
LogHelper.debug("Revanced settings not requested");
|
||||||
|
return false; // User wants to enter another settings fragment
|
||||||
|
}
|
||||||
|
LogHelper.debug("ReVanced settings requested");
|
||||||
|
|
||||||
|
ReVancedSettingsFragment fragment = new ReVancedSettingsFragment();
|
||||||
|
ActionBar supportActionBar = base.getSupportActionBar();
|
||||||
|
if(supportActionBar != null)
|
||||||
|
supportActionBar.setTitle(getStringId("revanced_settings"));
|
||||||
|
|
||||||
|
base.getFragmentManager()
|
||||||
|
.beginTransaction()
|
||||||
|
.replace(getIdentifier("fragment_container", "id"), fragment)
|
||||||
|
.commit();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
package app.revanced.twitch.settingsmenu.preference;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.preference.PreferenceCategory;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
public class CustomPreferenceCategory extends PreferenceCategory {
|
||||||
|
public CustomPreferenceCategory(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onBindView(View rootView) {
|
||||||
|
super.onBindView(rootView);
|
||||||
|
|
||||||
|
if(rootView instanceof TextView) {
|
||||||
|
((TextView) rootView).setTextColor(Color.parseColor("#8161b3"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
49
app/src/main/java/app/revanced/twitch/utils/LogHelper.java
Normal file
49
app/src/main/java/app/revanced/twitch/utils/LogHelper.java
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
package app.revanced.twitch.utils;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import app.revanced.twitch.settings.SettingsEnum;
|
||||||
|
|
||||||
|
public class LogHelper {
|
||||||
|
|
||||||
|
public static String getCallOrigin()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
final StackTraceElement elem = new Throwable().getStackTrace()[/* depth */ 2];
|
||||||
|
final String fullName = elem.getClassName();
|
||||||
|
return fullName.substring(fullName.lastIndexOf('.') + 1) + "/" + elem.getMethodName();
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
return "<unknown>";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final String TAG = "revanced";
|
||||||
|
|
||||||
|
public static void debug(String message, Object... args) {
|
||||||
|
Log.d(TAG, getCallOrigin() + ": " + String.format(message, args));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void info(String message, Object... args) {
|
||||||
|
Log.i(TAG, getCallOrigin() + ": " + String.format(message, args));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void error(String message, Object... args) {
|
||||||
|
String msg = getCallOrigin() + ": " + String.format(message, args);
|
||||||
|
showDebugToast(msg);
|
||||||
|
Log.e(TAG, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void printException(String message, Throwable ex) {
|
||||||
|
String msg = getCallOrigin() + ": " + message;
|
||||||
|
showDebugToast(msg + " (" + ex.getClass().getSimpleName() + ")");
|
||||||
|
Log.e(TAG, msg, ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void showDebugToast(String msg) {
|
||||||
|
if(SettingsEnum.DEBUG_MODE.getBoolean()) {
|
||||||
|
ReVancedUtils.ifContextAttached((c) -> Toast.makeText(c, msg, Toast.LENGTH_SHORT).show());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,87 @@
|
|||||||
|
package app.revanced.twitch.utils;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
public class ReVancedUtils {
|
||||||
|
@SuppressLint("StaticFieldLeak")
|
||||||
|
public static Context context;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Regular context getter
|
||||||
|
* @return Returns context or null if not initialized
|
||||||
|
*/
|
||||||
|
public static Context getContext() {
|
||||||
|
if (context != null) {
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
LogHelper.error("Context is null (at %s)", LogHelper.getCallOrigin());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute lambda only if context attached.
|
||||||
|
*/
|
||||||
|
public static void ifContextAttached(SafeContextAccessLambda lambda) {
|
||||||
|
if (context != null) {
|
||||||
|
lambda.run(context);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LogHelper.error("Context is null, lambda not executed (at %s)", LogHelper.getCallOrigin());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute lambda only if context attached.
|
||||||
|
* @return Returns result on success or valueOnError on failure
|
||||||
|
*/
|
||||||
|
public static <T> T ifContextAttached(SafeContextAccessReturnLambda<T> lambda, T valueOnError) {
|
||||||
|
if (context != null) {
|
||||||
|
return lambda.run(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
LogHelper.error("Context is null, lambda not executed (at %s)", LogHelper.getCallOrigin());
|
||||||
|
return valueOnError;
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface SafeContextAccessReturnLambda<T> {
|
||||||
|
T run(Context ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface SafeContextAccessLambda {
|
||||||
|
void run(Context ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get resource id safely
|
||||||
|
* @return May return 0 if resource not found or context not attached
|
||||||
|
*/
|
||||||
|
@SuppressLint("DiscouragedApi")
|
||||||
|
public static int getIdentifier(String name, String defType) {
|
||||||
|
return ifContextAttached(
|
||||||
|
(context) -> {
|
||||||
|
int resId = context.getResources().getIdentifier(name, defType, context.getPackageName());
|
||||||
|
if(resId == 0) {
|
||||||
|
LogHelper.error("Resource '%s' not found (at %s)", name, LogHelper.getCallOrigin());
|
||||||
|
}
|
||||||
|
return resId;
|
||||||
|
},
|
||||||
|
0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Called from SettingsPatch smali */
|
||||||
|
public static int getStringId(String name) {
|
||||||
|
return getIdentifier(name, "string");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Called from SettingsPatch smali */
|
||||||
|
public static int getDrawableId(String name) {
|
||||||
|
return getIdentifier(name, "drawable");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getString(String name) {
|
||||||
|
return ifContextAttached((c) -> c.getString(getStringId(name)), "");
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
package tv.twitch.android.app.core;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
|
||||||
|
public class LandingActivity extends Activity {}
|
@ -0,0 +1,14 @@
|
|||||||
|
package tv.twitch.android.feature.settings.menu;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
// Dummy
|
||||||
|
public final class SettingsMenuGroup {
|
||||||
|
public SettingsMenuGroup(List<Object> settingsMenuItems) {
|
||||||
|
throw new UnsupportedOperationException("Stub");
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Object> getSettingsMenuItems() {
|
||||||
|
throw new UnsupportedOperationException("Stub");
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
package tv.twitch.android.settings;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
|
||||||
|
public class SettingsActivity extends Activity {}
|
@ -0,0 +1,9 @@
|
|||||||
|
package tv.twitch.android.shared.chat.util;
|
||||||
|
|
||||||
|
import android.text.style.ClickableSpan;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
public final class ClickableUsernameSpan extends ClickableSpan {
|
||||||
|
@Override
|
||||||
|
public void onClick(View widget) {}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user