Skip to content

Commit

Permalink
feat(twitch): integrations code for patches (#216)
Browse files Browse the repository at this point in the history
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
  • Loading branch information
Tim Schneeberger and oSumAtrIX committed Nov 21, 2022
1 parent 24367ce commit d4c3b74
Show file tree
Hide file tree
Showing 12 changed files with 582 additions and 0 deletions.
1 change: 1 addition & 0 deletions app/build.gradle.kts
Expand Up @@ -45,4 +45,5 @@ android {
dependencies {
compileOnly(project(mapOf("path" to ":dummy")))
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 changes: 149 additions & 0 deletions app/src/main/java/app/revanced/twitch/settings/SettingsEnum.java
@@ -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();
}
}
110 changes: 110 additions & 0 deletions app/src/main/java/app/revanced/twitch/settingsmenu/SettingsHooks.java
@@ -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 comments on commit d4c3b74

Please sign in to comment.