0
0
mirror of https://github.com/TrianguloY/UrlChecker.git synced 2024-09-19 20:02:16 +02:00

Merge pull request #216 from PabloOQ/flags-update

Flags module update
This commit is contained in:
TrianguloY 2023-04-29 23:29:27 +02:00 committed by GitHub
commit c45b48c8f4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 1042 additions and 389 deletions

View File

@ -29,6 +29,7 @@
android:name=".dialogs.MainDialog"
android:exported="true"
android:noHistory="true"
android:windowSoftInputMode="adjustResize|stateHidden"
android:theme="@android:style/Theme.DeviceDefault.Dialog">
<intent-filter>
<action android:name="android.intent.action.VIEW" />

View File

@ -3,6 +3,7 @@ package com.trianguloy.urlchecker.dialogs;
import android.app.Activity;
import android.app.AlertDialog;
import android.view.View;
import android.view.WindowManager;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
@ -57,6 +58,8 @@ public class JsonEditor {
.setCancelable(false)
.show();
dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
// prepare more dialog
// these are configured here to disable automatic auto-closing when they are pressed
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(v -> {

View File

@ -4,8 +4,8 @@ import android.content.Context;
import android.os.Build;
import com.trianguloy.urlchecker.R;
import com.trianguloy.urlchecker.utilities.Enums;
import com.trianguloy.urlchecker.utilities.GenericPref;
import com.trianguloy.urlchecker.utilities.TranslatableEnum;
/**
* Static elements related to CTabs
@ -35,7 +35,7 @@ public class CTabs {
/**
* CTabs configuration
*/
public enum Config implements TranslatableEnum {
public enum Config implements Enums.IdEnum, Enums.StringEnum {
AUTO(0, R.string.auto),
ON(1, R.string.mOpen_ctabsOn),
OFF(2, R.string.mOpen_ctabsOff),

View File

@ -5,6 +5,7 @@ import android.app.AlertDialog;
import android.util.Log;
import android.util.Pair;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
@ -223,6 +224,8 @@ public class ClearUrlCatalog {
.setCancelable(true)
.show();
dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
// prepare more dialog
// these are configured here to allow auto-closing the dialog when they are pressed
Button updateNow = dialog.getButton(AlertDialog.BUTTON_POSITIVE);

View File

@ -0,0 +1,269 @@
package com.trianguloy.urlchecker.modules.companions;
import static com.trianguloy.urlchecker.utilities.JavaUtils.valueOrDefault;
import android.app.Activity;
import android.content.Intent;
import com.trianguloy.urlchecker.modules.AModuleDialog;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
/**
* Represents intent flags
* Stores all the flags and it states, can be retrieved as list or as int
* Non-compatible flags are ignored
*/
public class Flags {
// ------------------- static -------------------
// https://github.com/MuntashirAkon/AppManager/blob/19782da4c8556c817ba5795554a1cc21f38af13a/app/src/main/java/io/github/muntashirakon/AppManager/intercept/ActivityInterceptor.java#L92
private static final Set<String> ALL_FLAGS = Set.of(
// all have a 'FLAG_' prefix
"GRANT_READ_URI_PERMISSION",
"GRANT_WRITE_URI_PERMISSION",
"FROM_BACKGROUND",
"DEBUG_LOG_RESOLUTION",
"EXCLUDE_STOPPED_PACKAGES",
"INCLUDE_STOPPED_PACKAGES",
"GRANT_PERSISTABLE_URI_PERMISSION",
"GRANT_PREFIX_URI_PERMISSION",
"DIRECT_BOOT_AUTO",
"IGNORE_EPHEMERAL",
"ACTIVITY_NO_HISTORY",
"ACTIVITY_SINGLE_TOP",
"ACTIVITY_NEW_TASK",
"ACTIVITY_MULTIPLE_TASK",
"ACTIVITY_CLEAR_TOP",
"ACTIVITY_FORWARD_RESULT",
"ACTIVITY_PREVIOUS_IS_TOP",
"ACTIVITY_EXCLUDE_FROM_RECENTS",
"ACTIVITY_BROUGHT_TO_FRONT",
"ACTIVITY_RESET_TASK_IF_NEEDED",
"ACTIVITY_LAUNCHED_FROM_HISTORY",
"ACTIVITY_NEW_DOCUMENT",
"ACTIVITY_NO_USER_ACTION",
"ACTIVITY_REORDER_TO_FRONT",
"ACTIVITY_NO_ANIMATION",
"ACTIVITY_CLEAR_TASK",
"ACTIVITY_TASK_ON_HOME",
"ACTIVITY_RETAIN_IN_RECENTS",
"ACTIVITY_LAUNCH_ADJACENT",
"ACTIVITY_MATCH_EXTERNAL",
"ACTIVITY_REQUIRE_NON_BROWSER",
"ACTIVITY_REQUIRE_DEFAULT"
);
private static final Map<String, Integer> compatibleFlags = new TreeMap<>(); // TreeMap to have the entries sorted by key
static {
// Only get flags that are present in the current Android version
for (var flag : ALL_FLAGS) {
try {
compatibleFlags.put(flag, (Integer) Intent.class.getField("FLAG_" + flag).get(null));
} catch (NoSuchFieldException ignored) {
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
public static Map<String, Integer> getCompatibleFlags() {
return new TreeMap<>(compatibleFlags);
}
// ------------------- CRUD -------------------
private Set<String> flags;
// TODO store non-compatible flags? as String "0x001000", int 0x001000
public Flags() {
this(0x00000000);
}
public Flags(int hex) {
setFlags(hex);
}
public boolean isSet(String flag) {
return flags.contains(flag);
}
public int getFlagsAsInt() {
return flagsSetToHex(flags);
}
public Set<String> getFlagsAsSet() {
return new TreeSet<>(flags); // TreeSet to have the entries sorted
}
/**
* Replaces the stored flags with the received hex
*/
public void setFlags(int hex) {
this.flags = hexFlagsToSet(hex);
}
/**
* Replaces the stored flags with the received list
*/
public void setFlags(Set<String> names) {
Set<String> res = new HashSet<>(names);
res.retainAll(compatibleFlags.keySet());
this.flags = res;
}
/**
* Sets the flag to the received boolean
*/
public boolean setFlag(String flag, boolean bool) {
if (compatibleFlags.containsKey(flag)) {
if (bool) {
return flags.add(flag);
} else {
return flags.remove(flag);
}
} else {
return false;
}
}
/**
* Add flags by applying a mask
*/
public void addFlags(int hex) {
flags.addAll(hexFlagsToSet(hex));
}
/**
* Add a list of flags based on its name
*/
public void addFlags(Set<String> flags) {
this.flags.addAll(flags);
}
/**
* Add a flag based on its name
*/
public boolean addFlag(String name) {
if (compatibleFlags.containsKey(name)) {
return flags.add(name);
} else {
return false;
}
}
/**
* Remove flags by applying a mask
*/
public boolean removeFlags(int hex) {
return flags.removeAll(hexFlagsToSet(hex));
}
/**
* Remove a list of flags based on its name
*/
public boolean removeFlags(Set<String> flags) {
return this.flags.removeAll(flags);
}
/**
* Remove a flag based on its name
*/
public boolean removeFlag(String name) {
return flags.remove(name);
}
// ------------------- utils -------------------
/**
* Decode an int as set
*/
private static Set<String> hexFlagsToSet(int hex) {
var foundFlags = new HashSet<String>();
for (var flag : compatibleFlags.entrySet()) {
// check if flag is present
if ((hex & flag.getValue()) != 0) {
foundFlags.add(flag.getKey());
}
}
return foundFlags;
}
/**
* Decode a set as hex
*/
private static int flagsSetToHex(Set<String> set) {
int hex = 0x00000000;
for (var flag : set) {
if (compatibleFlags.containsKey(flag)) {
hex = hex | compatibleFlags.get(flag);
}
}
return hex;
}
// ------------------- store/load flags -------------------
// this handles the store and load of the flags, if something wants to get the flags
// it should always use these methods.
private static final String DATA_FLAGS = "flagsEditor.flags";
private static final String REGEX = "(0x)?[a-fA-F\\d]{1,8}";
private static final int BASE = 16;
/**
* parses a text as an hexadecimal flags string.
* Returns null if invalid
*/
public static Integer toInteger(String text) {
if (text != null && text.matches(REGEX)) {
return Integer.parseInt(text.replaceAll("^0x", ""), BASE);
} else {
return null;
}
}
/**
* Converts an int flags to string
*/
public static String toHexString(int flags) {
return "0x" + Integer.toString(flags, BASE);
}
/**
* Retrieves the flags from GlobalData, if it is not defined it will return null
*/
public static Integer getGlobalFlagsNullable(AModuleDialog instance) {
return toInteger(instance.getData(DATA_FLAGS));
}
/**
* Loads the flags from GlobalData, if none were found it gets the flags from the intent that
* started this activity
*/
public static int getGlobalFlagsNonNull(AModuleDialog instance, Activity cntx) {
return getGlobalFlagsOrDefault(instance, cntx.getIntent().getFlags());
}
/**
* Loads the flags from GlobalData, if none were found it gets the flags from default
* Can be used by other modules
*/
public static int getGlobalFlagsOrDefault(AModuleDialog instance, int defaultFlags) {
return valueOrDefault(toInteger(instance.getData(DATA_FLAGS)), defaultFlags);
}
/**
* Stores the flags in GlobalData
*/
public static void setGlobalFlags(Flags flags, AModuleDialog instance) {
instance.putData(DATA_FLAGS, flags == null ? null : toHexString(flags.getFlagsAsInt()));
}
}

View File

@ -1,14 +1,16 @@
package com.trianguloy.urlchecker.modules.list;
import static com.trianguloy.urlchecker.utilities.JavaUtils.valueOrDefault;
import android.app.AlertDialog;
import android.content.Context;
import android.content.Intent;
import android.text.Editable;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import android.widget.Button;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
@ -18,15 +20,27 @@ import com.trianguloy.urlchecker.dialogs.MainDialog;
import com.trianguloy.urlchecker.modules.AModuleConfig;
import com.trianguloy.urlchecker.modules.AModuleData;
import com.trianguloy.urlchecker.modules.AModuleDialog;
import com.trianguloy.urlchecker.modules.companions.Flags;
import com.trianguloy.urlchecker.url.UrlData;
import com.trianguloy.urlchecker.utilities.AndroidUtils;
import com.trianguloy.urlchecker.utilities.DefaultTextWatcher;
import com.trianguloy.urlchecker.utilities.Enums;
import com.trianguloy.urlchecker.utilities.GenericPref;
import com.trianguloy.urlchecker.utilities.Inflater;
import com.trianguloy.urlchecker.utilities.InternalFile;
import com.trianguloy.urlchecker.utilities.JavaUtils;
import com.trianguloy.urlchecker.views.CycleImageButton;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.Set;
import java.util.TreeSet;
/**
* This module allows flag edition
@ -37,6 +51,8 @@ public class FlagsModule extends AModuleData {
return new GenericPref.Str("flagsEditor_defaultFlags", null, cntx);
}
public static final String DEFAULT_GROUP = "default";
@Override
public String getId() {
return "flagsEditor";
@ -65,279 +81,398 @@ public class FlagsModule extends AModuleData {
class FlagsDialog extends AModuleDialog {
public static final String DATA_FLAGS = "flagsEditor.flags";
private final Flags defaultFlags;
private final Flags currentFlags;
// https://github.com/MuntashirAkon/AppManager/blob/19782da4c8556c817ba5795554a1cc21f38af13a/app/src/main/java/io/github/muntashirakon/AppManager/intercept/ActivityInterceptor.java#L92
private static final List<String> ALL_FLAGS = List.of(
"FLAG_GRANT_READ_URI_PERMISSION",
"FLAG_GRANT_WRITE_URI_PERMISSION",
"FLAG_FROM_BACKGROUND",
"FLAG_DEBUG_LOG_RESOLUTION",
"FLAG_EXCLUDE_STOPPED_PACKAGES",
"FLAG_INCLUDE_STOPPED_PACKAGES",
"FLAG_GRANT_PERSISTABLE_URI_PERMISSION",
"FLAG_GRANT_PREFIX_URI_PERMISSION",
"FLAG_DIRECT_BOOT_AUTO",
"FLAG_IGNORE_EPHEMERAL",
"FLAG_ACTIVITY_NO_HISTORY",
"FLAG_ACTIVITY_SINGLE_TOP",
"FLAG_ACTIVITY_NEW_TASK",
"FLAG_ACTIVITY_MULTIPLE_TASK",
"FLAG_ACTIVITY_CLEAR_TOP",
"FLAG_ACTIVITY_FORWARD_RESULT",
"FLAG_ACTIVITY_PREVIOUS_IS_TOP",
"FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS",
"FLAG_ACTIVITY_BROUGHT_TO_FRONT",
"FLAG_ACTIVITY_RESET_TASK_IF_NEEDED",
"FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY",
"FLAG_ACTIVITY_NEW_DOCUMENT",
"FLAG_ACTIVITY_NO_USER_ACTION",
"FLAG_ACTIVITY_REORDER_TO_FRONT",
"FLAG_ACTIVITY_NO_ANIMATION",
"FLAG_ACTIVITY_CLEAR_TASK",
"FLAG_ACTIVITY_TASK_ON_HOME",
"FLAG_ACTIVITY_RETAIN_IN_RECENTS",
"FLAG_ACTIVITY_LAUNCH_ADJACENT",
"FLAG_ACTIVITY_MATCH_EXTERNAL",
"FLAG_ACTIVITY_REQUIRE_NON_BROWSER",
"FLAG_ACTIVITY_REQUIRE_DEFAULT"
);
private final Map<String, Integer> flagMap = new TreeMap<>(); // TreeMap to have the entries sorted by key
private Map<String, FlagsConfig.FlagState> flagsStatePref;
private final GenericPref.Str defaultFlagsPref;
private ViewGroup shownFlagsVG;
private EditText searchInput;
private ViewGroup hiddenFlagsVG;
private ImageView overflowButton;
private EditText flagsHexText;
private AutoCompleteTextView flagNameText;
private ImageButton more;
private LinearLayout box;
private JSONObject groups;
public FlagsDialog(MainDialog dialog) {
super(dialog);
defaultFlagsPref = FlagsModule.DEFAULTFLAGS_PREF(dialog);
try {
// Only get flags that are present in the current Android version
for (var field : Intent.class.getFields()) {
if (ALL_FLAGS.contains(field.getName())) {
flagMap.put(field.getName(), (Integer) field.get(null));
}
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
defaultFlags = new Flags(getActivity().getIntent().getFlags());
currentFlags = new Flags();
}
@Override
public int getLayoutId() {
return R.layout.dialog_editflags;
return R.layout.dialog_flags;
}
@Override
public void onInitialize(View views) {
box = views.findViewById(R.id.box);
flagsHexText = views.findViewById(R.id.flagsHexText);
initGroups();
// set the flags to the adapter of the input text
flagNameText = views.findViewById(R.id.flagText);
flagNameText.setAdapter(new ArrayAdapter<>(getActivity(), android.R.layout.simple_spinner_dropdown_item, new ArrayList<>(
flagMap.keySet()
)));
// so the dropdown gets the maximum width possible
flagNameText.setDropDownAnchor(R.id.addFlagLayout);
// FIXME better search, currently it is an autofill, not a search
// FIXME sometimes its hidden behind keyboard
shownFlagsVG = views.findViewById(R.id.shownFlags);
searchInput = views.findViewById(R.id.search);
hiddenFlagsVG = views.findViewById(R.id.hiddenFlags);
// get initial flags
var defaultFlagsStr = defaultFlagsPref.get();
if (defaultFlagsStr != null) {
setFlags(toInteger(defaultFlagsStr));
}
// Button to open the `box` with the hidden flags (more indicator)
overflowButton = views.findViewById(R.id.overflowButton);
// press add to add a flag
views.<Button>findViewById(R.id.add).setOnClickListener(v -> {
var flag = flagMap.get(flagNameText.getText().toString());
if (flag != null) {
setFlags(getFlagsNonNull() | flag);
} else {
Toast.makeText(getActivity(), R.string.mFlags_invalid, Toast.LENGTH_LONG).show();
}
// Update views
updateLayout();
});
// press 'more' to expand/collapse the box
more = views.findViewById(R.id.more);
more.setOnClickListener(v -> {
box.setVisibility(box.getVisibility() == View.GONE ? View.VISIBLE : View.GONE);
// Update views
updateLayout();
});
box.setVisibility(View.GONE); // start collapsed
// press edit to start editing, press again to save
var edit = views.<Button>findViewById(R.id.edit);
edit.setOnClickListener(v -> {
if (flagsHexText.isEnabled()) {
// requested to save
var flags = toInteger(flagsHexText.getText().toString());
if (flags != null) {
// Extract flags
setFlags(flags);
}
}
flagsHexText.setEnabled(!flagsHexText.isEnabled());
// Update views
updateLayout();
});
// long press to reset
edit.setOnLongClickListener(v -> {
// Resets the flags
setFlags(null);
// Update views
updateLayout();
return true;
});
}
@Override
public void onDisplayUrl(UrlData urlData) {
updateLayout();
}
private void updateLayout() {
// set text
var flags = getFlagsNonNull();
flagsHexText.setText(toHexString(flags));
// set current flags list
var decodedFlags = decodeFlags(flags);
box.removeAllViews();
if (decodedFlags.size() == 0) {
// no flags, disable
AndroidUtils.setEnabled(more, false);
more.setImageResource(R.drawable.arrow_right);
} else {
// flags, enable
AndroidUtils.setEnabled(more, true);
more.setImageResource(box.getVisibility() == View.VISIBLE ? R.drawable.arrow_down : R.drawable.arrow_right);
// For each flag, create a button+text
for (var flag : decodedFlags) {
var button_text = Inflater.inflate(R.layout.button_text, box, getActivity());
// Button that removes the flag
var button = button_text.<Button>findViewById(R.id.button);
button.setText(R.string.remove);
// Apply mask to remove flag
button.setOnClickListener(v -> {
setFlags(getFlagsNonNull() & ~flagMap.get(flag));
// Update views
updateLayout();
// Hide hidden flags
hiddenFlagsVG.setVisibility(View.GONE);
AndroidUtils.toggleableListener(overflowButton,
v -> hiddenFlagsVG.setVisibility(hiddenFlagsVG.getVisibility() == View.GONE ? View.VISIBLE : View.GONE),
v -> {
searchInput.setVisibility(hiddenFlagsVG.getVisibility());
updateMoreIndicator();
});
var text = button_text.<TextView>findViewById(R.id.text);
text.setText(flag);
// SEARCH
// Set up search text
searchInput.addTextChangedListener(new DefaultTextWatcher() {
@Override
public void afterTextChanged(Editable text) {
for (int i = 0; i < hiddenFlagsVG.getChildCount(); i++) {
var checkbox_text = hiddenFlagsVG.getChildAt(i);
String flag = ((TextView) checkbox_text.findViewById(R.id.text)).getText().toString();
String search = text.toString();
// Set visibility based on search text
checkbox_text.setVisibility(JavaUtils.containsWords(flag, search) ? View.VISIBLE : View.GONE);
}
}
});
// TODO spinner with groups
loadGroup(FlagsModule.DEFAULT_GROUP);
}
private void initGroups() {
String fileString = new InternalFile(FlagsConfig.CONF_FILE, getActivity()).get();
groups = new JSONObject();
if (fileString != null) {
try {
groups = new JSONObject(fileString).getJSONObject("groups");
} catch (JSONException ignore) {
}
}
}
// ------------------- utils -------------------
/**
* Decode an int as flags
*/
private List<String> decodeFlags(int hex) {
var foundFlags = new ArrayList<String>();
for (var flag : flagMap.entrySet()) {
// check if flag is present
if ((hex & flag.getValue()) != 0) {
foundFlags.add(flag.getKey());
// To get all the groups names
private List<String> getGroups() {
List<String> res = new ArrayList<>();
// Always add FlagsModule.DEFAULT_GROUP first, even if it doesn't exist
res.add(FlagsModule.DEFAULT_GROUP);
for (Iterator<String> it = groups.keys(); it.hasNext(); ) {
String group = it.next();
if (!group.equals(FlagsModule.DEFAULT_GROUP)) {
res.add(group);
}
}
return foundFlags;
return res;
}
// ------------------- store/load flags -------------------
// this handles the store and load of the flags, if something wants to get the flags
// it should always use these methods.
void loadGroup(String group) {
currentFlags.setFlags(0);
private static final int BASE = 16;
protected static final String REGEX = "(0x)?[a-fA-F\\d]{1,8}";
// Load json
JSONObject groupPref = null;
try {
groupPref = groups.getJSONObject(group);
} catch (JSONException ignore) {
}
/**
* parses a text as an hexadecimal flags string.
* Returns null if invalid
*/
public static Integer toInteger(String text) {
if (text != null && text.matches(REGEX)) {
return Integer.parseInt(text.replaceAll("^0x", ""), BASE);
// Put shown flags
Set<String> shownFlagsSet = new TreeSet<>();
// Get state preference of flag from json and then store it in a map
flagsStatePref = new HashMap<>();
if (groupPref != null) {
for (Iterator<String> it = groupPref.keys(); it.hasNext(); ) {
String flag = it.next();
try {
flagsStatePref.put(flag, Enums.toEnum(FlagsConfig.FlagState.class,
groupPref.getJSONObject(flag).getInt("state")));
if (groupPref.getJSONObject(flag).getBoolean("show")) {
shownFlagsSet.add(flag);
}
} catch (JSONException ignored) {
}
}
}
// If it is not in shownFlags it must be in hiddenFlags
Set<String> hiddenFlagsSet = new TreeSet<>(Flags.getCompatibleFlags().keySet());
hiddenFlagsSet.removeAll(shownFlagsSet);
// Fill boxes with flags, load flags into currentFlags too
fillWithFlags(shownFlagsSet, shownFlagsVG);
fillWithFlags(hiddenFlagsSet, hiddenFlagsVG);
// Update global
Flags.setGlobalFlags(currentFlags, this);
updateMoreIndicator();
}
void updateMoreIndicator() {
if (hiddenFlagsVG.getChildCount() == 0) {
overflowButton.setImageDrawable(null);
} else {
return null;
overflowButton.setImageResource(hiddenFlagsVG.getVisibility() == View.VISIBLE ? R.drawable.arrow_down : R.drawable.arrow_right);
}
}
/**
* Converts an int flags to string
* Sets up a ViewGroup with the flags received. The state of the flag is read from flagsStatePref.
* The default state of the flag is read from defaultFlags. Also sets currentFlags.
*
* @param flags flags to add
* @param vg ViewGroup to fill with flags
*/
public static String toHexString(int flags) {
return "0x" + Integer.toString(flags, BASE);
private void fillWithFlags(Set<String> flags, ViewGroup vg) {
vg.removeAllViews();
for (String flag : flags) {
var checkbox_text = Inflater.inflate(R.layout.dialog_flags_entry, vg, getActivity());
// Checkbox
var checkBox = checkbox_text.<ImageView>findViewById(R.id.state);
boolean bool;
switch (valueOrDefault(flagsStatePref.get(flag), FlagsConfig.FlagState.AUTO)) {
case ON:
bool = true;
break;
case OFF:
bool = false;
break;
case AUTO:
default:
bool = defaultFlags.isSet(flag);
}
currentFlags.setFlag(flag, bool);
checkBox.setTag(R.id.text, flag);
AndroidUtils.toggleableListener(checkBox,
v -> {
currentFlags.setFlag(flag, !currentFlags.isSet(flag));
// Update global
Flags.setGlobalFlags(currentFlags, this);
// To update debug module view of GlobalData
setUrl(new UrlData(getUrl()).dontTriggerOwn().asMinorUpdate());
},
v -> checkBox.setImageResource(currentFlags.isSet(flag) ? R.drawable.flag_on : R.drawable.flag_off)
);
// Text
((TextView) checkbox_text.findViewById(R.id.text)).setText(flag);
// Color indicators
var defaultIndicator = checkbox_text.findViewById(R.id.defaultIndicator);
var preferenceIndicator = checkbox_text.findViewById(R.id.preferenceIndicator);
checkBox.setTag(R.id.defaultIndicator, defaultIndicator);
checkBox.setTag(R.id.preferenceIndicator, preferenceIndicator);
setColors(flag, defaultIndicator, preferenceIndicator);
}
}
/**
* Retrieves the flags from GlobalData, if it is not defined it will return null
* Intended for use in other modules
*/
public static Integer getFlagsNullable(AModuleDialog instance) {
return toInteger(instance.getData(DATA_FLAGS));
}
void setColors(String flag, View defaultIndicator, View preferenceIndicator) {
AndroidUtils.setRoundedColor(defaultFlags.isSet(flag) ? R.color.good : R.color.bad, defaultIndicator);
/**
* Loads the flags from GlobalData, if none were found it gets the flags from the intent that
* started this activity
*/
private int getFlagsNonNull() {
return getFlagsOrDefault(this, getActivity().getIntent().getFlags());
}
int color;
switch (valueOrDefault(flagsStatePref.get(flag), FlagsConfig.FlagState.AUTO)) {
case ON:
color = R.color.good;
break;
case OFF:
color = R.color.bad;
break;
case AUTO:
default:
color = R.color.grey;
}
/**
* Loads the flags from GlobalData, if none were found it gets the flags from default
* Can be used by other modules
*/
public static int getFlagsOrDefault(AModuleDialog instance, int defaultFlags) {
var flags = toInteger(instance.getData(DATA_FLAGS));
return flags == null ? defaultFlags : flags;
}
/**
* Stores the flags in GlobalData
*/
private void setFlags(Integer flags) {
putData(DATA_FLAGS, flags == null ? null : toHexString(flags));
AndroidUtils.setRoundedColor(color, preferenceIndicator);
}
}
class FlagsConfig extends AModuleConfig {
private final GenericPref.Str defaultFlagsPref;
protected static final String CONF_FILE = "flags_editor_settings";
public FlagsConfig(ModulesActivity activity) {
super(activity);
defaultFlagsPref = FlagsModule.DEFAULTFLAGS_PREF(activity);
}
@Override
public int getLayoutId() {
return R.layout.config_flagseditor;
return R.layout.config_flags;
}
@Override
public void onInitialize(View views) {
defaultFlagsPref.attachToEditText(
views.findViewById(R.id.flags),
str -> str,
str -> str.matches(FlagsDialog.REGEX) ? str : defaultFlagsPref.defaultValue
);
views.findViewById(R.id.button).setOnClickListener(showDialog -> {
View flagsDialogLayout = getActivity().getLayoutInflater().inflate(R.layout.flags_editor, null);
ViewGroup box = flagsDialogLayout.findViewById(R.id.box);
InternalFile file = new InternalFile(CONF_FILE, getActivity());
// Get all flags
fillBoxViewGroup(box, file, FlagsModule.DEFAULT_GROUP);
AlertDialog alertDialog = new AlertDialog.Builder(getActivity())
.setView(flagsDialogLayout)
.setPositiveButton(views.getContext().getText(R.string.save), (dialog, which) -> {
// Save the settings
storePreferences(box, file, FlagsModule.DEFAULT_GROUP);
})
.setNegativeButton(views.getContext().getText(android.R.string.cancel), null)
.setNeutralButton(views.getContext().getText(R.string.reset), null)
.show();
alertDialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
alertDialog.getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener(listener -> {
// Reset current group flags (does not save)
resetFlags(box);
});
// Search
((EditText) flagsDialogLayout.findViewById(R.id.search)).addTextChangedListener(new DefaultTextWatcher() {
@Override
public void afterTextChanged(Editable text) {
for (int i = 0; i < box.getChildCount(); i++) {
var entryView = box.getChildAt(i);
String flag = ((TextView) entryView.findViewById(R.id.text)).getText().toString();
String search = text.toString();
// Set visibility based on search text
entryView.setVisibility(JavaUtils.containsWords(flag, search) ? View.VISIBLE : View.GONE);
}
}
});
// TODO add dialog button to set all to on/off/auto
});
}
private void fillBoxViewGroup(ViewGroup vg, InternalFile file, String group) {
String prefString = file.get();
JSONObject oldPref = null; // Null if there is no file or fails to parse
try {
oldPref = prefString == null ? null : new JSONObject(file.get()).getJSONObject("groups").getJSONObject(group);
} catch (JSONException ignored) {
}
// Fill the box
for (String flag : Flags.getCompatibleFlags().keySet()) {
var entryView = Inflater.inflate(R.layout.flags_editor_entry, vg, getActivity());
TextView textView = entryView.findViewById(R.id.text);
textView.setText(flag);
var flagState = entryView.<CycleImageButton<FlagState>>findViewById(R.id.state);
flagState.setStates(List.of(FlagState.values()));
// Load preferences from settings
Boolean showValue = null;
Integer stateValue = null;
// Get current preferences
if (oldPref != null) {
JSONObject flagPref;
try {
flagPref = oldPref.getJSONObject(flag);
showValue = flagPref.getBoolean("show");
stateValue = flagPref.getInt("state");
} catch (JSONException ignored) {
}
}
flagState.setCurrentState(Enums.toEnum(FlagState.class, valueOrDefault(stateValue, FlagState.AUTO.id)));
var show = entryView.<ImageButton>findViewById(R.id.show);
show.setTag(valueOrDefault(showValue, false));
AndroidUtils.toggleableListener(show,
v -> v.setTag(v.getTag() == Boolean.FALSE),
v -> v.setImageResource(v.getTag() == Boolean.TRUE ? R.drawable.show : R.drawable.hide)
);
}
}
private void storePreferences(ViewGroup vg, InternalFile file, String group) {
// Retrieve previous config, to keep other groups
JSONObject oldSettings = null;
String content = file.get();
// It's ok if there is no file yet
if (content != null) {
try {
oldSettings = new JSONObject(content);
} catch (JSONException ignore) {
// If the json fails to parse then we will create a new file
}
}
try {
// Collect all the settings of the vg
JSONObject newSettings = new JSONObject();
for (int i = 0; i < vg.getChildCount(); i++) {
View v = vg.getChildAt(i);
FlagState state = v.<CycleImageButton<FlagState>>findViewById(R.id.state).getCurrentState();
boolean show = v.findViewById(R.id.show).getTag() == Boolean.TRUE;
newSettings.put(((TextView) v.findViewById(R.id.text)).getText().toString(),
new JSONObject()
.put("state", state.getId())
.put("show", show));
}
// If there are no old settings, create a new one
// Replace the old settings from group with the new ones
newSettings = oldSettings == null ?
new JSONObject().put("groups", new JSONObject().put(group, newSettings)) :
oldSettings.put("groups", oldSettings.getJSONObject("groups").put(group, newSettings));
// TODO should groups be sorted?
file.set(newSettings.toString());
} catch (JSONException e) {
e.printStackTrace();
Toast.makeText(getActivity(), R.string.toast_invalid, Toast.LENGTH_SHORT).show();
}
}
private void resetFlags(ViewGroup vg) {
// Set everything to default values
for (int i = 0; i < vg.getChildCount(); i++) {
View v = vg.getChildAt(i);
v.<CycleImageButton<FlagState>>findViewById(R.id.state).setCurrentState(FlagState.AUTO);
var visible = v.<ImageButton>findViewById(R.id.show);
visible.setImageResource(R.drawable.hide);
visible.setTag(Boolean.FALSE);
}
}
public enum FlagState implements Enums.IdEnum, Enums.ImageEnum {
AUTO(0, R.drawable.flag_auto),
ON(1, R.drawable.flag_on),
OFF(2, R.drawable.flag_off),
;
// -----
private final int id;
private final int imageResource;
FlagState(int id, int imageResource) {
this.id = id;
this.imageResource = imageResource;
}
@Override
public int getId() {
return id;
}
@Override
public int getImageResource() {
return imageResource;
}
}
}

View File

@ -4,6 +4,7 @@ import android.app.AlertDialog;
import android.content.Context;
import android.text.util.Linkify;
import android.view.View;
import android.view.WindowManager;
import android.widget.EditText;
import android.widget.ScrollView;
import android.widget.TextView;
@ -138,6 +139,8 @@ class LogConfig extends AModuleConfig {
// show
AlertDialog dialog = builder.show();
dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
// prepare more dialog
// these are configured here to disable automatic auto-closing when they are pressed
if (editable) {

View File

@ -17,6 +17,7 @@ import com.trianguloy.urlchecker.modules.AModuleConfig;
import com.trianguloy.urlchecker.modules.AModuleData;
import com.trianguloy.urlchecker.modules.AModuleDialog;
import com.trianguloy.urlchecker.modules.companions.CTabs;
import com.trianguloy.urlchecker.modules.companions.Flags;
import com.trianguloy.urlchecker.modules.companions.LastOpened;
import com.trianguloy.urlchecker.url.UrlData;
import com.trianguloy.urlchecker.utilities.AndroidUtils;
@ -251,7 +252,7 @@ class OpenDialog extends AModuleDialog {
}
// Get flags from global data (probably set by flags module, if active)
Integer flags = FlagsDialog.getFlagsNullable(this);
Integer flags = Flags.getGlobalFlagsNullable(this);
if (flags != null) {
intent.setFlags(flags);
}

View File

@ -1,7 +1,6 @@
package com.trianguloy.urlchecker.modules.list;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.View;
import android.widget.EditText;
@ -13,6 +12,7 @@ import com.trianguloy.urlchecker.modules.AModuleData;
import com.trianguloy.urlchecker.modules.AModuleDialog;
import com.trianguloy.urlchecker.modules.DescriptionConfig;
import com.trianguloy.urlchecker.url.UrlData;
import com.trianguloy.urlchecker.utilities.DefaultTextWatcher;
import com.trianguloy.urlchecker.utilities.DoubleEvent;
/**
@ -41,9 +41,10 @@ public class TextInputModule extends AModuleData {
}
}
class TextInputDialog extends AModuleDialog implements TextWatcher {
class TextInputDialog extends AModuleDialog {
private final DoubleEvent doubleEdit = new DoubleEvent(1000); // if two updates happens in less than this milliseconds, they are considered as the same
private boolean skipUpdate = false;
private EditText edtxt_url;
@ -59,39 +60,31 @@ class TextInputDialog extends AModuleDialog implements TextWatcher {
@Override
public void onInitialize(View views) {
edtxt_url = views.findViewById(R.id.url);
edtxt_url.addTextChangedListener(this);
edtxt_url.addTextChangedListener(new DefaultTextWatcher() {
@Override
public void afterTextChanged(Editable s) {
if (skipUpdate) return;
// new url by the user
var newUrlData = new UrlData(s.toString())
.dontTriggerOwn()
.disableUpdates();
// mark as minor if too quick
if (doubleEdit.checkAndTrigger()) newUrlData.asMinorUpdate();
// set
setUrl(newUrlData);
}
});
}
@Override
public void onDisplayUrl(UrlData urlData) {
// setText fires the afterTextChanged listener, so we need to remove it
edtxt_url.removeTextChangedListener(this);
// setText fires the afterTextChanged listener, so we need to skip it
skipUpdate = true;
edtxt_url.setText(urlData.url);
edtxt_url.addTextChangedListener(this);
skipUpdate = false;
doubleEdit.reset(); // next user update, even if immediately after, will be considered new
}
// ------------------- TextWatcher -------------------
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
// new url by the user
var newUrlData = new UrlData(s.toString())
.dontTriggerOwn()
.disableUpdates();
// mark as minor if too quick
if (doubleEdit.checkAndTrigger()) newUrlData.asMinorUpdate();
// set
setUrl(newUrlData);
}
}

View File

@ -4,10 +4,8 @@ import android.app.AlertDialog;
import android.content.Context;
import android.graphics.Color;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import com.trianguloy.urlchecker.R;
@ -19,6 +17,7 @@ import com.trianguloy.urlchecker.modules.AModuleDialog;
import com.trianguloy.urlchecker.modules.companions.VirusTotalUtility;
import com.trianguloy.urlchecker.url.UrlData;
import com.trianguloy.urlchecker.utilities.AndroidUtils;
import com.trianguloy.urlchecker.utilities.DefaultTextWatcher;
import com.trianguloy.urlchecker.utilities.GenericPref;
import com.trianguloy.urlchecker.utilities.UrlUtils;
@ -57,7 +56,7 @@ public class VirusTotalModule extends AModuleData {
}
}
class VirusTotalConfig extends AModuleConfig implements TextWatcher {
class VirusTotalConfig extends AModuleConfig {
final GenericPref.Str api_key;
@ -79,23 +78,15 @@ class VirusTotalConfig extends AModuleConfig implements TextWatcher {
@Override
public void onInitialize(View views) {
final EditText edit_key = (EditText) views.findViewById(R.id.api_key);
var edit_key = views.<TextView>findViewById(R.id.api_key);
edit_key.setText(api_key.get());
edit_key.addTextChangedListener(this);
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
api_key.set(s.toString());
if (!canBeEnabled()) disable();
edit_key.addTextChangedListener(new DefaultTextWatcher() {
@Override
public void afterTextChanged(Editable s) {
api_key.set(s.toString());
if (!canBeEnabled()) disable();
}
});
}
}

View File

@ -128,7 +128,7 @@ public interface AndroidSettings {
/**
* The theme setting
*/
enum Theme implements TranslatableEnum {
enum Theme implements Enums.IdEnum, Enums.StringEnum {
DEFAULT(0, R.string.deviceDefault),
DARK(1, R.string.spin_darkTheme),
LIGHT(2, R.string.spin_lightTheme),

View File

@ -0,0 +1,21 @@
package com.trianguloy.urlchecker.utilities;
import android.text.Editable;
import android.text.TextWatcher;
/***
* TextWatcher empty implementation, so you can override only what you need
*/
public class DefaultTextWatcher implements TextWatcher {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
}
}

View File

@ -0,0 +1,50 @@
package com.trianguloy.urlchecker.utilities;
import java.util.HashMap;
import java.util.Map;
public interface Enums {
interface StringEnum {
/**
* This must return the string resourced associated with this enum value
*/
int getStringResource();
}
interface IdEnum {
/**
* The id of the saved preference. Must never change
*/
int getId();
}
interface ImageEnum {
int getImageResource();
}
/**
* Get an enum from an id
*/
static <TE extends IdEnum> TE toEnum(Class<TE> te, int id) {
TE[] enumConstants = te.getEnumConstants();
for (TE constant : enumConstants) {
if (constant.getId() == id) {
return constant;
}
}
return null;
}
/**
* Get a map of id and enum
*/
static <TE extends IdEnum> Map<Integer, TE> toEnumMap(Class<TE> te) {
Map<Integer, TE> res = new HashMap<>();
TE[] enumConstants = te.getEnumConstants();
for (TE constant : enumConstants) {
res.put(constant.getId(), constant);
}
return res;
}
}

View File

@ -3,7 +3,6 @@ package com.trianguloy.urlchecker.utilities;
import android.content.Context;
import android.content.SharedPreferences;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
@ -198,15 +197,7 @@ public abstract class GenericPref<T> {
*/
public void attachToEditText(EditText editText, JavaUtils.UnaryOperator<String> loadMod, JavaUtils.UnaryOperator<String> storeMod) {
editText.setText(loadMod.apply(get()));
editText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
editText.addTextChangedListener(new DefaultTextWatcher() {
@Override
public void afterTextChanged(Editable s) {
set(storeMod.apply(s.toString()));
@ -257,7 +248,7 @@ public abstract class GenericPref<T> {
/**
* A list of options (enumeration) preference
*/
static public class Enumeration<T extends Enum<T> & TranslatableEnum> extends GenericPref<T> {
static public class Enumeration<T extends Enum<T> & Enums.IdEnum & Enums.StringEnum> extends GenericPref<T> {
private final Class<T> type;
public Enumeration(String prefName, T defaultValue, Class<T> type, Context cntx) {

View File

@ -45,6 +45,33 @@ public interface JavaUtils {
: clamp(max, value, min);
}
/**
* Applies a filter to both strings to check if all words of keywords are in body.
* The order does not matter.
*/
static boolean containsWords(String body, String keywords){
JavaUtils.Function<String, String> filter = s -> s.toUpperCase().replaceAll("[\\s-_]+", " ");
// Match all words
String[] words = filter.apply(keywords).split(" ");
body = filter.apply(body);
boolean match = true;
for (String str : words) {
if (!body.contains(str)){
match = false;
break;
}
}
return match;
}
/**
* Returns the object, or default if null
* java.util.Optional requires api 24
*/
static <T> T valueOrDefault(T value, T defaultValue){
return value == null ? defaultValue : value;
}
/**
* java.util.function.Consumer requires api 24
*/

View File

@ -1,17 +0,0 @@
package com.trianguloy.urlchecker.utilities;
/**
* On java, an enum can't be extended :(
* This is the best I can do...unless I discover how to create annotations!
*/
public interface TranslatableEnum {
/**
* The id of the saved preference. Must never change
*/
int getId();
/**
* This must return the string resourced associated with this enum value
*/
int getStringResource();
}

View File

@ -0,0 +1,61 @@
package com.trianguloy.urlchecker.views;
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.widget.ImageButton;
import com.trianguloy.urlchecker.utilities.Enums;
import java.util.List;
public class CycleImageButton<T extends Enums.ImageEnum> extends ImageButton {
private List<T> states;
private int currentState;
public CycleImageButton(Context context) {
super(context);
}
public CycleImageButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CycleImageButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public void setStates(List<T> states) {
this.states = states;
updateImageResource(0);
}
public void setCurrentState(T currentState) {
updateImageResource(states.indexOf(currentState));
}
public T getCurrentState() {
return states == null || states.isEmpty() ? null : states.get(currentState);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
}
@Override
public boolean performClick() {
updateImageResource(currentState + 1);
return super.performClick();
}
private void updateImageResource(int newState) {
if (states == null || states.isEmpty()) {
setImageDrawable(null);
} else {
currentState = newState >= 0 ? newState % states.size() : 0;
setImageResource(states.get(currentState).getImageResource());
}
}
}

View File

@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:autoMirrored="true"
android:viewportWidth="24"
android:viewportHeight="24">
<!-- <path-->
<!-- android:fillColor="@color/app"-->
<!-- android:pathData="M5 4 5 6 7 6 18 15 18 17 20 17 20 6 14 6 13 4M5 7 5 9 7 9 7 7M5 13 5 15 7 15 7 13M5 16 5 18 7 18 7 16M5 19 5 21 7 21 7 19M8 13 8 15 10 15 10 13M11 13 11 15 12 15 13 17 14 17 14 15 13 13M15 15 15 17 17 17 17 15M5 10 5 12 7 12 7 10" />-->
<path
android:fillColor="@color/app"
android:pathData="M5 4 5 15 7 15 7 13 18 8 20 8 20 6 14 6 13 4M5 16 5 18 7 18 7 16M5 19 5 21 7 21 7 19M8 13 8 15 10 15 10 13M11 13 11 15 12 15 13 17 14 17 14 15 13 13M15 15 15 17 17 17 17 15M18 15 18 17 20 17 20 15M18 12 18 14 20 14 20 12M18 9 18 11 20 11 20 9" />
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:autoMirrored="true"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@color/app"
android:pathData="M5 4 5 6 7 6 7 4M5 7 5 9 7 9 7 7M5 13 5 15 7 15 7 13M5 16 5 18 7 18 7 16M5 19 5 21 7 21 7 19M8 13 8 15 10 15 10 13M11 13 11 15 12 15 13 17 14 17 14 15 13 13M15 15 15 17 17 17 17 15M18 15 18 17 20 17 20 15M18 12 18 14 20 14 20 12M18 6 18 8 20 8 20 6M15 6 15 8 17 8 17 6M11 4 11 6 12 6 13 8 14 8 14 6 13 4M8 4 8 6 10 6 10 4M5 10 5 12 7 12 7 10M18 9 18 11 20 11 20 9" />
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:autoMirrored="true"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@color/app"
android:pathData="M14 6 13 4H5v17h2v-7h5l1 2h7V6z" />
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:autoMirrored="true"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@color/app"
android:pathData="M12 17C6 17 1 8 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c0-4-5 5-11 5z" />
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:autoMirrored="true"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@color/app"
android:pathData="M12,4.5C7,4.5 2.73,7.61 1,12c1.73,4.39 6,7.5 11,7.5s9.27,-3.11 11,-7.5c-1.73,-4.39 -6,-7.5 -11,-7.5zM12,17c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5zM12,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3 3,-1.34 3,-3 -1.34,-3 -3,-3z" />
</vector>

View File

@ -10,11 +10,11 @@
android:layout_height="wrap_content"
android:text="@string/mFlag_desc" />
<EditText
android:id="@+id/flags"
<Button
android:id="@+id/button"
style="?android:attr/buttonBarButtonStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:hint="0xFFFFFFFF"
android:inputType="textPersonName" />
android:text="@string/mFlag_editFlags" />
</LinearLayout>

View File

@ -1,76 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center_vertical"
android:orientation="vertical">
<LinearLayout
android:id="@+id/flagHexLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/edit"
style="?android:attr/buttonBarButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="0"
android:text="@string/edit" />
<EditText
android:id="@+id/flagsHexText"
style="@android:style/Widget.TextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:enabled="false"
android:inputType="textPersonName"
android:textSize="20sp"
android:textStyle="bold" />
</LinearLayout>
<LinearLayout
android:id="@+id/addFlagLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/add"
style="?android:attr/buttonBarButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="0"
android:text="@string/add" />
<AutoCompleteTextView
android:id="@+id/flagText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:ems="10"
android:hint="@string/mFlag_flagHint" />
<ImageButton
android:id="@+id/more"
style="?android:attr/buttonBarButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="0"
android:src="@drawable/arrow_down"
android:tint="?attr/colorAccent" />
</LinearLayout>
<LinearLayout
android:id="@+id/box"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
</LinearLayout>
</LinearLayout>

View File

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:orientation="vertical">
<LinearLayout
android:id="@+id/shownFlags"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="right|end|center_vertical"
android:orientation="horizontal">
<EditText
android:id="@+id/search"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:hint="@android:string/search_go" />
<ImageView
android:id="@+id/overflowButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/smallPadding"
android:src="@drawable/arrow_down"
android:tint="?attr/colorAccent" />
</LinearLayout>
<LinearLayout
android:id="@+id/hiddenFlags"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
</LinearLayout>

View File

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center">
<ImageButton
android:id="@+id/state"
style="?android:attr/buttonBarButtonStyle"
android:layout_width="@dimen/square_button"
android:layout_height="@dimen/square_button"
android:layout_weight="0"
android:src="@drawable/flag_auto" />
<TextView
android:id="@+id/defaultIndicator"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_margin="3dp"
android:layout_weight="0" />
<TextView
android:id="@+id/preferenceIndicator"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_margin="3dp"
android:layout_weight="0" />
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center_vertical" />
</LinearLayout>

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<EditText
android:id="@+id/search"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:hint="@android:string/search_go"
android:inputType="textPersonName" />
<LinearLayout
android:id="@+id/box"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"></LinearLayout>
</LinearLayout>
</ScrollView>

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical">
<view
android:id="@+id/state"
style="?android:attr/buttonBarButtonStyle"
class="com.trianguloy.urlchecker.views.CycleImageButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minWidth="0dp"
android:minHeight="0dp"
android:src="@drawable/flag_auto" />
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="left|center_vertical" />
<ImageButton
android:id="@+id/show"
style="?android:attr/buttonBarButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minWidth="0dp"
android:minHeight="0dp"
android:src="@drawable/show" />
</LinearLayout>

View File

@ -2,4 +2,5 @@
<resources>
<dimen name="padding">25dp</dimen>
<dimen name="smallPadding">10dp</dimen>
<dimen name="square_button">35dp</dimen>
</resources>

View File

@ -100,6 +100,9 @@ Hope you find the app useful! And don't hesitate to suggest features, report bug
<string name="remove">Remove</string>
<string name="edit">Edit</string>
<string name="add">Add</string>
<string name="on">Enabled</string>
<string name="off">Disabled</string>
<!--
json
-->
@ -304,8 +307,10 @@ Their api is rate limited to 10 requests per hour for new checks. The module res
<string name="mFlags_name">Flags editor</string>
<string name="mFlags_invalid">Invalid flag</string>
<string name="mFlag_desc">"[Beta feature] This is an advanced module, it allows you to edit the intent flags when opening another app.
In the field below you can put flags that will overwrite the default ones, those are taken from the intent that was used to open this app.
While using the module you can hold the edit button to set the flags to the default flags. You can add flags by writing their name in the flags field"</string>
\nThis module will show a list of flags, you can change if the flag is set or not by taping the icon. There is also an overflow menu to hide flags in there, it is just visual, flags will still use the assigned state.
\nThe flags will have two color indicators next to the checkbox. The left one shows the value received from the intent, the right one indicates the state set by the module itself. Red means the flag is not set and green means the flag is set. Additionally the preference indicator can be grey, this means that the state was set to auto.
\nWith the button below you can edit the default state and the visibility of the flags."</string>
<string name="mFlag_flagHint">Flag name</string>
<string name="mFlag_editFlags">Edit default flag values</string>
</resources>