mirror of
https://gitee.com/zhangsken/AndroidIDE.git
synced 2026-10-16 07:49:44 +08:00
Show more items in attribute value completion
Auto-fill already set value in input fields in value editor
This commit is contained in:
Generated
+1
@@ -61,6 +61,7 @@
|
||||
<entry key="..\:/Projects/AndroidStudioProjects/AndroidIDE/app/src/main/res/layout/layout_divider.xml" value="0.25" />
|
||||
<entry key="..\:/Projects/AndroidStudioProjects/AndroidIDE/app/src/main/res/layout/layout_editor_bottom_sheet.xml" value="0.19572649572649573" />
|
||||
<entry key="..\:/Projects/AndroidStudioProjects/AndroidIDE/app/src/main/res/layout/layout_editor_text_actions.xml" value="0.25" />
|
||||
<entry key="..\:/Projects/AndroidStudioProjects/AndroidIDE/app/src/main/res/layout/layout_reference_attr_editor.xml" value="0.25" />
|
||||
<entry key="..\:/Projects/AndroidStudioProjects/AndroidIDE/app/src/main/res/layout/layout_resource_input_field.xml" value="0.25" />
|
||||
<entry key="..\:/Projects/AndroidStudioProjects/AndroidIDE/app/src/main/res/layout/layout_string_attr_editor.xml" value="0.25" />
|
||||
<entry key="..\:/Projects/AndroidStudioProjects/AndroidIDE/app/src/main/res/layout/layout_symbol_item.xml" value="0.20512820512820512" />
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.itsaky.androidide.fragments.attr;
|
||||
|
||||
import static com.itsaky.androidide.utils.Logger.instance;
|
||||
|
||||
import android.text.Editable;
|
||||
import android.text.TextUtils;
|
||||
import android.widget.AutoCompleteTextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.blankj.utilcode.util.ThreadUtils;
|
||||
import com.google.android.material.textfield.MaterialAutoCompleteTextView;
|
||||
import com.itsaky.androidide.adapters.AttrValueCompletionAdapter;
|
||||
import com.itsaky.androidide.utils.Logger;
|
||||
import com.itsaky.androidide.utils.TextWatcherAdapter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
/**
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public abstract class AbstractReferenceEditor extends BaseValueEditorFragment {
|
||||
|
||||
private static final Logger LOG = instance ("ReferenceEditor");
|
||||
public TextWatcherAdapter resInputWatcher;
|
||||
|
||||
@Override
|
||||
protected void notifyValueChanged (@NonNull String newValue) {
|
||||
try {
|
||||
super.notifyValueChanged (newValue);
|
||||
} catch (Throwable e) {
|
||||
LOG.error ("Unable to update resource value to '" + newValue + "'", e);
|
||||
}
|
||||
}
|
||||
|
||||
protected void setupReferenceInput (MaterialAutoCompleteTextView referenceInput) {
|
||||
Objects.requireNonNull (referenceInput);
|
||||
|
||||
referenceInput.setText (attribute.getValue ());
|
||||
|
||||
if (resInputWatcher != null) {
|
||||
referenceInput.removeTextChangedListener (resInputWatcher);
|
||||
}
|
||||
|
||||
resInputWatcher =
|
||||
new TextWatcherAdapter () {
|
||||
@Override
|
||||
public void afterTextChanged (@NonNull Editable s) {
|
||||
final var text = s.toString ().trim ();
|
||||
if (TextUtils.isEmpty (text)) {
|
||||
return;
|
||||
}
|
||||
|
||||
notifyValueChanged (text);
|
||||
}
|
||||
};
|
||||
|
||||
referenceInput.addTextChangedListener (resInputWatcher);
|
||||
|
||||
CompletableFuture.supplyAsync (this::computeReferenceItems)
|
||||
.whenComplete (
|
||||
(list, error) -> handleAutoCompleteResult (list, error, referenceInput));
|
||||
}
|
||||
|
||||
protected void handleAutoCompleteResult (
|
||||
List<String> list, Throwable error, AutoCompleteTextView referenceInput) {
|
||||
if (list == null || list.isEmpty ()) {
|
||||
LOG.error ("No completion items found");
|
||||
LOG.error ("Error was:", error);
|
||||
return;
|
||||
}
|
||||
|
||||
LOG.debug ("Found", list.size (), "resource items for completion");
|
||||
|
||||
ThreadUtils.runOnUiThread (
|
||||
() -> {
|
||||
final var adapter = new AttrValueCompletionAdapter (requireContext (), list);
|
||||
referenceInput.setThreshold (1);
|
||||
referenceInput.setAdapter (adapter);
|
||||
adapter.notifyDataSetChanged ();
|
||||
});
|
||||
}
|
||||
|
||||
protected List<String> computeReferenceItems () {
|
||||
return new ArrayList<> ();
|
||||
}
|
||||
}
|
||||
@@ -35,51 +35,61 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/** @author Akash Yadav */
|
||||
public class BooleanEditor extends ReferenceEditor {
|
||||
|
||||
/**
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public class BooleanEditor extends AbstractReferenceEditor {
|
||||
|
||||
private LayoutBooleanAttrEditorBinding binding;
|
||||
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(
|
||||
public View onCreateView (
|
||||
@NonNull LayoutInflater inflater,
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
this.binding = LayoutBooleanAttrEditorBinding.inflate(inflater, container, false);
|
||||
return this.binding.getRoot();
|
||||
this.binding = LayoutBooleanAttrEditorBinding.inflate (inflater, container, false);
|
||||
return this.binding.getRoot ();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
this.binding.choiceGroup.setOnCheckedChangeListener(
|
||||
public void onViewCreated (@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated (view, savedInstanceState);
|
||||
|
||||
this.binding.choiceGroup.setOnCheckedChangeListener (
|
||||
(group, checkedId) -> {
|
||||
final var checked = (RadioButton) group.findViewById(checkedId);
|
||||
notifyValueChanged(checked.getText().toString().trim());
|
||||
final var checked = (RadioButton) group.findViewById (checkedId);
|
||||
notifyValueChanged (checked.getText ().toString ().trim ());
|
||||
});
|
||||
|
||||
setupReferenceInput(
|
||||
(MaterialAutoCompleteTextView) this.binding.booleanResInput.getEditText());
|
||||
|
||||
switch (attribute.getValue ()) {
|
||||
case "true":
|
||||
this.binding.boolTrue.setChecked (true);
|
||||
break;
|
||||
case "false":
|
||||
this.binding.boolFalse.setChecked (true);
|
||||
}
|
||||
|
||||
setupReferenceInput (
|
||||
(MaterialAutoCompleteTextView) this.binding.booleanResInput.getEditText ());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected List<String> computeReferenceItems() {
|
||||
final var list = new ArrayList<String>();
|
||||
final var tables = ValuesTableFactory.getAllTables();
|
||||
for (var entry : tables.entrySet()) {
|
||||
final var dimens = entry.getValue().getTable("bool");
|
||||
protected List<String> computeReferenceItems () {
|
||||
final var list = new ArrayList<String> ();
|
||||
final var tables = ValuesTableFactory.getAllTables ();
|
||||
for (var entry : tables.entrySet ()) {
|
||||
final var dimens = entry.getValue ().getTable ("bool");
|
||||
if (dimens != null) {
|
||||
list.addAll(
|
||||
dimens.keySet().stream().map("@bool/"::concat).collect(Collectors.toSet()));
|
||||
list.addAll (
|
||||
dimens.keySet ().stream ().map ("@bool/"::concat).collect (Collectors.toSet ()));
|
||||
}
|
||||
}
|
||||
|
||||
list.addAll(
|
||||
FrameworkValues.listBools().stream()
|
||||
.map("@android:bool/"::concat)
|
||||
.collect(Collectors.toList()));
|
||||
|
||||
list.addAll (
|
||||
FrameworkValues.listBools ().stream ()
|
||||
.map ("@android:bool/"::concat)
|
||||
.collect (Collectors.toList ()));
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
package com.itsaky.androidide.fragments.attr;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.graphics.Color;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@@ -27,6 +28,7 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.google.android.material.textfield.MaterialAutoCompleteTextView;
|
||||
import com.itsaky.androidide.app.StudioApp;
|
||||
import com.itsaky.androidide.colorpicker.ColorPickerView;
|
||||
import com.itsaky.androidide.databinding.LayoutColorAttrEditorBinding;
|
||||
import com.itsaky.androidide.utils.DialogUtils;
|
||||
@@ -40,7 +42,7 @@ import java.util.stream.Collectors;
|
||||
/**
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public class ColorEditor extends ReferenceEditor {
|
||||
public class ColorEditor extends AbstractReferenceEditor {
|
||||
|
||||
private LayoutColorAttrEditorBinding binding;
|
||||
private Dialog colorPickerDialog;
|
||||
@@ -58,6 +60,13 @@ public class ColorEditor extends ReferenceEditor {
|
||||
|
||||
this.binding.pickColor.setOnClickListener (v -> getColorPickerDialog ().show ());
|
||||
setupReferenceInput ((MaterialAutoCompleteTextView) this.binding.colorResInput.getEditText ());
|
||||
|
||||
try {
|
||||
final var col = Color.parseColor (attribute.getValue ());
|
||||
this.binding.colorPreview.setCardBackgroundColor (col);
|
||||
} catch (Throwable th) {
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -78,6 +87,10 @@ public class ColorEditor extends ReferenceEditor {
|
||||
FrameworkValues.listColors ().stream ()
|
||||
.map ("@android:color/"::concat)
|
||||
.collect (Collectors.toList ()));
|
||||
|
||||
final var resTable = StudioApp.getInstance ().getResourceTable ();
|
||||
list.addAll (resTable.listResourceNames ("color"));
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,9 +19,11 @@ package com.itsaky.androidide.fragments.attr;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -35,88 +37,105 @@ import com.itsaky.inflater.values.FrameworkValues;
|
||||
import com.itsaky.inflater.values.ValuesTableFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/** @author Akash Yadav */
|
||||
public class DimensionEditor extends ReferenceEditor {
|
||||
|
||||
private static final Logger LOG = Logger.instance("DimensionEditor");
|
||||
/**
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public class DimensionEditor extends AbstractReferenceEditor {
|
||||
|
||||
private static final Logger LOG = Logger.instance ("DimensionEditor");
|
||||
public TextWatcherAdapter dimensionInputWatcher;
|
||||
|
||||
|
||||
private LayoutDimensionAttrEditorBinding binding;
|
||||
private String[] dimensionUnits;
|
||||
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(
|
||||
public View onCreateView (
|
||||
@NonNull LayoutInflater inflater,
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
this.binding = LayoutDimensionAttrEditorBinding.inflate(inflater, container, false);
|
||||
return this.binding.getRoot();
|
||||
this.binding = LayoutDimensionAttrEditorBinding.inflate (inflater, container, false);
|
||||
return this.binding.getRoot ();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
public void onViewCreated (@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated (view, savedInstanceState);
|
||||
|
||||
final var dimensionInput =
|
||||
Objects.requireNonNull(this.binding.dimensionInput.getEditText());
|
||||
Objects.requireNonNull (this.binding.dimensionInput.getEditText ());
|
||||
setDimensionValue (dimensionInput);
|
||||
if (dimensionInputWatcher != null) {
|
||||
dimensionInput.removeTextChangedListener(dimensionInputWatcher);
|
||||
dimensionInput.removeTextChangedListener (dimensionInputWatcher);
|
||||
}
|
||||
|
||||
|
||||
dimensionInputWatcher =
|
||||
new TextWatcherAdapter() {
|
||||
new TextWatcherAdapter () {
|
||||
@Override
|
||||
public void afterTextChanged(@NonNull Editable s) {
|
||||
final var text = s.toString().trim();
|
||||
public void afterTextChanged (@NonNull Editable s) {
|
||||
final var text = s.toString ().trim ();
|
||||
final var unit =
|
||||
getDimensionUnits()[binding.unitSelector.getSelectedItemPosition()];
|
||||
|
||||
getDimensionUnits ()[binding.unitSelector.getSelectedItemPosition ()];
|
||||
|
||||
// This will call CommonParseUtils#parseDimension
|
||||
notifyValueChanged(text.concat(unit));
|
||||
notifyValueChanged (text.concat (unit));
|
||||
}
|
||||
};
|
||||
|
||||
dimensionInput.addTextChangedListener(dimensionInputWatcher);
|
||||
|
||||
|
||||
dimensionInput.addTextChangedListener (dimensionInputWatcher);
|
||||
|
||||
final var dimensionResInput =
|
||||
(MaterialAutoCompleteTextView)
|
||||
Objects.requireNonNull(binding.dimensionResInput.getEditText());
|
||||
setupReferenceInput(dimensionResInput);
|
||||
Objects.requireNonNull (binding.dimensionResInput.getEditText ());
|
||||
setupReferenceInput (dimensionResInput);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private String[] getDimensionUnits() {
|
||||
if (dimensionUnits == null) {
|
||||
dimensionUnits =
|
||||
requireContext().getResources().getStringArray(R.array.dimension_units);
|
||||
}
|
||||
|
||||
return dimensionUnits;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> computeReferenceItems() {
|
||||
final var list = new ArrayList<String>();
|
||||
final var tables = ValuesTableFactory.getAllTables();
|
||||
for (var entry : tables.entrySet()) {
|
||||
final var dimens = entry.getValue().getTable("dimen");
|
||||
if (dimens != null) {
|
||||
list.addAll(
|
||||
dimens.keySet().stream()
|
||||
.map("@dimen/"::concat)
|
||||
.collect(Collectors.toSet()));
|
||||
|
||||
private void setDimensionValue (EditText dimensionInput) {
|
||||
final var val = attribute.getValue ();
|
||||
var dimension = val.substring (0, val.length () - 2);
|
||||
if (TextUtils.isDigitsOnly (dimension)) {
|
||||
dimensionInput.setText (dimension);
|
||||
final var arr = requireContext ().getResources ().getStringArray (R.array.dimension_units);
|
||||
final var index = Arrays.asList (arr).indexOf (val.substring (dimension.length ()));
|
||||
if (index >= 0) {
|
||||
this.binding.unitSelector.setSelection (index);
|
||||
}
|
||||
}
|
||||
|
||||
list.addAll(
|
||||
FrameworkValues.listDimens().stream()
|
||||
.map("@android:dimen/"::concat)
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private String[] getDimensionUnits () {
|
||||
if (dimensionUnits == null) {
|
||||
dimensionUnits =
|
||||
requireContext ().getResources ().getStringArray (R.array.dimension_units);
|
||||
}
|
||||
|
||||
return dimensionUnits;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> computeReferenceItems () {
|
||||
final var list = new ArrayList<String> ();
|
||||
final var tables = ValuesTableFactory.getAllTables ();
|
||||
for (var entry : tables.entrySet ()) {
|
||||
final var dimens = entry.getValue ().getTable ("dimen");
|
||||
if (dimens != null) {
|
||||
list.addAll (
|
||||
dimens.keySet ().stream ()
|
||||
.map ("@dimen/"::concat)
|
||||
.collect (Collectors.toSet ()));
|
||||
}
|
||||
}
|
||||
|
||||
list.addAll (
|
||||
FrameworkValues.listDimens ().stream ()
|
||||
.map ("@android:dimen/"::concat)
|
||||
.collect (Collectors.toList ()));
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,28 +26,39 @@ import com.google.android.material.chip.Chip;
|
||||
import com.google.android.material.chip.ChipGroup;
|
||||
import com.itsaky.androidide.utils.Logger;
|
||||
|
||||
/** @author Akash Yadav */
|
||||
/**
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public class EnumEditor extends FixedValueEditor {
|
||||
|
||||
private static final Logger LOG = Logger.instance("EnumEditor");
|
||||
|
||||
|
||||
private static final Logger LOG = Logger.instance ("EnumEditor");
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
this.chipGroup.setSingleSelection(true);
|
||||
this.chipGroup.setSelectionRequired(true);
|
||||
public void onViewCreated (@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated (view, savedInstanceState);
|
||||
|
||||
this.chipGroup.setSingleSelection (true);
|
||||
this.chipGroup.setSelectionRequired (true);
|
||||
|
||||
final var val = this.attribute.getValue ();
|
||||
for (int i = 0; i < this.chipGroup.getChildCount (); i++) {
|
||||
final var chip = (Chip) this.chipGroup.getChildAt (i);
|
||||
if (val.equals (chip.getText ().toString ().trim ())) {
|
||||
chip.setChecked (true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void onCheckChanged(@NonNull ChipGroup group, int checkedId) {
|
||||
final var chip = (Chip) group.findViewById(group.getCheckedChipId());
|
||||
protected void onCheckChanged (@NonNull ChipGroup group, int checkedId) {
|
||||
final var chip = (Chip) group.findViewById (group.getCheckedChipId ());
|
||||
if (chip == null) {
|
||||
LOG.error("Unable to update enum value. Checked Chip view is null.");
|
||||
LOG.error ("Unable to update enum value. Checked Chip view is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
final var val = chip.getText().toString().trim();
|
||||
notifyValueChanged(val);
|
||||
|
||||
final var val = chip.getText ().toString ().trim ();
|
||||
notifyValueChanged (val);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ import com.google.android.material.chip.ChipGroup;
|
||||
import com.itsaky.androidide.utils.Logger;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* @author Akash Yadav
|
||||
@@ -47,6 +48,14 @@ public class FlagEditor extends FixedValueEditor {
|
||||
this.chipGroup.setSingleSelection(false);
|
||||
this.chipGroup.setSelectionRequired(true);
|
||||
this.chipGroup.setOnCheckedChangeListener(null); // does not work for multiple selections
|
||||
|
||||
final var values = Arrays.asList (this.attribute.getValue ().split ("\\|"));
|
||||
for (int i = 0; i < this.chipGroup.getChildCount (); i++) {
|
||||
final var chip = (Chip) this.chipGroup.getChildAt (i);
|
||||
if (values.contains (chip.getText ().toString ().trim ())) {
|
||||
chip.setChecked (true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -14,91 +14,72 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.fragments.attr;
|
||||
|
||||
import static com.itsaky.androidide.utils.Logger.instance;
|
||||
|
||||
import android.text.Editable;
|
||||
import android.text.TextUtils;
|
||||
import android.widget.AutoCompleteTextView;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.blankj.utilcode.util.ThreadUtils;
|
||||
import com.google.android.material.textfield.MaterialAutoCompleteTextView;
|
||||
import com.itsaky.androidide.adapters.AttrValueCompletionAdapter;
|
||||
import com.itsaky.androidide.utils.Logger;
|
||||
import com.itsaky.androidide.utils.TextWatcherAdapter;
|
||||
import com.itsaky.androidide.app.StudioApp;
|
||||
import com.itsaky.androidide.databinding.LayoutReferenceAttrEditorBinding;
|
||||
import com.itsaky.inflater.values.FrameworkValues;
|
||||
import com.itsaky.inflater.values.ValuesTableFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public class ReferenceEditor extends BaseValueEditorFragment {
|
||||
public class ReferenceEditor extends AbstractReferenceEditor {
|
||||
|
||||
private static final Logger LOG = instance ("ReferenceEditor");
|
||||
public TextWatcherAdapter resInputWatcher;
|
||||
private LayoutReferenceAttrEditorBinding binding;
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView (@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
this.binding = LayoutReferenceAttrEditorBinding.inflate (inflater, container, false);
|
||||
return this.binding.getRoot ();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void notifyValueChanged (@NonNull String newValue) {
|
||||
try {
|
||||
super.notifyValueChanged (newValue);
|
||||
} catch (Throwable e) {
|
||||
LOG.error ("Unable to update resource value to '" + newValue + "'", e);
|
||||
}
|
||||
}
|
||||
|
||||
protected void setupReferenceInput (MaterialAutoCompleteTextView referenceInput) {
|
||||
Objects.requireNonNull (referenceInput);
|
||||
|
||||
if (resInputWatcher != null) {
|
||||
referenceInput.removeTextChangedListener (resInputWatcher);
|
||||
}
|
||||
|
||||
resInputWatcher =
|
||||
new TextWatcherAdapter () {
|
||||
@Override
|
||||
public void afterTextChanged (@NonNull Editable s) {
|
||||
final var text = s.toString ().trim ();
|
||||
if (TextUtils.isEmpty (text)) {
|
||||
return;
|
||||
}
|
||||
|
||||
notifyValueChanged (text);
|
||||
}
|
||||
};
|
||||
|
||||
referenceInput.addTextChangedListener (resInputWatcher);
|
||||
|
||||
CompletableFuture.supplyAsync (this::computeReferenceItems)
|
||||
.whenComplete (
|
||||
(list, error) -> handleAutoCompleteResult (list, error, referenceInput));
|
||||
}
|
||||
|
||||
protected void handleAutoCompleteResult (
|
||||
List<String> list, Throwable error, AutoCompleteTextView referenceInput) {
|
||||
if (list == null || list.isEmpty ()) {
|
||||
LOG.error ("No completion items found");
|
||||
LOG.error ("Error was:", error);
|
||||
return;
|
||||
}
|
||||
|
||||
LOG.debug ("Found", list.size (), "resource items for completion");
|
||||
|
||||
ThreadUtils.runOnUiThread (
|
||||
() -> {
|
||||
final var adapter = new AttrValueCompletionAdapter (requireContext (), list);
|
||||
referenceInput.setThreshold (1);
|
||||
referenceInput.setAdapter (adapter);
|
||||
adapter.notifyDataSetChanged ();
|
||||
});
|
||||
public void onViewCreated (@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated (view, savedInstanceState);
|
||||
|
||||
setupReferenceInput ((MaterialAutoCompleteTextView) this.binding.resInput.getEditText ());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> computeReferenceItems () {
|
||||
return new ArrayList<> ();
|
||||
final var resTable = StudioApp.getInstance ().getResourceTable ();
|
||||
final var list = new ArrayList<> (resTable.listResourceNames (null));
|
||||
|
||||
final var tables = ValuesTableFactory.getAllTables ();
|
||||
for (var entry : tables.entrySet ()) {
|
||||
final var resourceMap = entry.getValue ().getResourceMap ();
|
||||
for (var resourceEntries : resourceMap.entrySet ()) {
|
||||
final var name = resourceEntries.getKey ();
|
||||
list.addAll (
|
||||
resourceEntries
|
||||
.getValue ()
|
||||
.keySet ()
|
||||
.stream ()
|
||||
.map (("@android:" + name + "/")::concat)
|
||||
.collect (Collectors.toList ()
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
list.addAll (FrameworkValues.listAllResources ());
|
||||
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ import java.util.stream.Collectors;
|
||||
/**
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public class StringEditor extends ReferenceEditor {
|
||||
public class StringEditor extends AbstractReferenceEditor {
|
||||
|
||||
private LayoutStringAttrEditorBinding binding;
|
||||
|
||||
@@ -54,13 +54,15 @@ public class StringEditor extends ReferenceEditor {
|
||||
public void onViewCreated (@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated (view, savedInstanceState);
|
||||
|
||||
Objects.requireNonNull (this.binding.stringInput.getEditText ()).addTextChangedListener (new TextWatcherAdapter () {
|
||||
final var stringInput = Objects.requireNonNull (this.binding.stringInput.getEditText ());
|
||||
stringInput.addTextChangedListener (new TextWatcherAdapter () {
|
||||
@Override
|
||||
public void afterTextChanged (@NonNull Editable s) {
|
||||
notifyValueChanged (s.toString ());
|
||||
}
|
||||
});
|
||||
|
||||
stringInput.setText (this.attribute.getValue ());
|
||||
setupReferenceInput ((MaterialAutoCompleteTextView) this.binding.stringResInput.getEditText ());
|
||||
}
|
||||
|
||||
@@ -79,7 +81,7 @@ public class StringEditor extends ReferenceEditor {
|
||||
}
|
||||
|
||||
list.addAll (
|
||||
FrameworkValues.listDimens ().stream ()
|
||||
FrameworkValues.listStrings ().stream ()
|
||||
.map ("@android:string/"::concat)
|
||||
.collect (Collectors.toList ()));
|
||||
return list;
|
||||
|
||||
@@ -49,6 +49,7 @@ import com.itsaky.androidide.fragments.attr.FlagEditor;
|
||||
import com.itsaky.androidide.fragments.attr.FloatEditor;
|
||||
import com.itsaky.androidide.fragments.attr.FractionEditor;
|
||||
import com.itsaky.androidide.fragments.attr.IntegerEditor;
|
||||
import com.itsaky.androidide.fragments.attr.AbstractReferenceEditor;
|
||||
import com.itsaky.androidide.fragments.attr.ReferenceEditor;
|
||||
import com.itsaky.androidide.fragments.attr.StringEditor;
|
||||
import com.itsaky.androidide.models.XMLAttribute;
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
package com.itsaky.androidide.project;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.itsaky.androidide.utils.Logger;
|
||||
import com.itsaky.inflater.IResourceTable;
|
||||
@@ -25,229 +26,287 @@ import com.itsaky.inflater.values.ValuesTableFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileFilter;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Locale;
|
||||
|
||||
public class ProjectResourceTable implements IResourceTable {
|
||||
|
||||
|
||||
private static final Logger LOG = Logger.instance ("ProjectResourceFinder");
|
||||
private File resDir;
|
||||
private File color;
|
||||
private File layout;
|
||||
|
||||
private File[] drawables;
|
||||
private File[] mipmaps;
|
||||
|
||||
private static final Logger LOG = Logger.instance("ProjectResourceFinder");
|
||||
|
||||
|
||||
@Override
|
||||
public File findDrawable(@NonNull String name) {
|
||||
public File findDrawable (@NonNull String name) {
|
||||
for (File drawable : drawables) {
|
||||
final File file = findFileWithName(drawable.listFiles(), name);
|
||||
final File file = findFileWithName (drawable.listFiles (), name);
|
||||
if (file != null) {
|
||||
return file;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public File findLayout(@NonNull String name) {
|
||||
return findFileWithName(this.layout.listFiles(), name);
|
||||
public File findLayout (@NonNull String name) {
|
||||
return findFileWithName (this.layout.listFiles (), name);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String findString(@NonNull String name) {
|
||||
final var table = ValuesTableFactory.getTable(resDir);
|
||||
|
||||
public String findString (@NonNull String name) {
|
||||
final var table = ValuesTableFactory.getTable (resDir);
|
||||
|
||||
if (table == null) {
|
||||
return name;
|
||||
}
|
||||
|
||||
final var resource = table.findString(name);
|
||||
|
||||
final var resource = table.findString (name);
|
||||
if (resource == null) {
|
||||
return name;
|
||||
}
|
||||
|
||||
final var value = resource.getValue();
|
||||
if (value.startsWith("@string/")) {
|
||||
var ref = value.substring("@string/".length());
|
||||
if (name.equals(ref)) {
|
||||
|
||||
final var value = resource.getValue ();
|
||||
if (value.startsWith ("@string/")) {
|
||||
var ref = value.substring ("@string/".length ());
|
||||
if (name.equals (ref)) {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String findBoolean(@NonNull String name) {
|
||||
final var table = ValuesTableFactory.getTable(resDir);
|
||||
|
||||
public String findBoolean (@NonNull String name) {
|
||||
final var table = ValuesTableFactory.getTable (resDir);
|
||||
|
||||
if (table == null) {
|
||||
return name;
|
||||
}
|
||||
|
||||
final var resource = table.findBoolean(name);
|
||||
|
||||
final var resource = table.findBoolean (name);
|
||||
if (resource == null) {
|
||||
return name;
|
||||
}
|
||||
|
||||
final var val = resource.getValue();
|
||||
if (val.startsWith("@bool/")) {
|
||||
final var ref = val.substring("@bool/".length());
|
||||
if (ref.equals(name)) {
|
||||
|
||||
final var val = resource.getValue ();
|
||||
if (val.startsWith ("@bool/")) {
|
||||
final var ref = val.substring ("@bool/".length ());
|
||||
if (ref.equals (name)) {
|
||||
// recursive reference
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String findInteger(@NonNull String name) {
|
||||
final var table = ValuesTableFactory.getTable(resDir);
|
||||
|
||||
public String findInteger (@NonNull String name) {
|
||||
final var table = ValuesTableFactory.getTable (resDir);
|
||||
|
||||
if (table == null) {
|
||||
return name;
|
||||
}
|
||||
|
||||
final var resource = table.findInteger(name);
|
||||
|
||||
final var resource = table.findInteger (name);
|
||||
if (resource == null) {
|
||||
return name;
|
||||
}
|
||||
|
||||
final var val = resource.getValue();
|
||||
if (val.startsWith("@integer/")) {
|
||||
final var ref = val.substring("@integer/".length());
|
||||
if (ref.equals(name)) {
|
||||
|
||||
final var val = resource.getValue ();
|
||||
if (val.startsWith ("@integer/")) {
|
||||
final var ref = val.substring ("@integer/".length ());
|
||||
if (ref.equals (name)) {
|
||||
// recursive reference
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String findColor(@NonNull String name) {
|
||||
final var table = ValuesTableFactory.getTable(resDir);
|
||||
|
||||
public String findColor (@NonNull String name) {
|
||||
final var table = ValuesTableFactory.getTable (resDir);
|
||||
|
||||
if (table == null) {
|
||||
return name;
|
||||
}
|
||||
|
||||
final var resource = table.findColor(name);
|
||||
|
||||
final var resource = table.findColor (name);
|
||||
if (resource == null) {
|
||||
return name;
|
||||
}
|
||||
|
||||
final var value = resource.getValue();
|
||||
if (value.startsWith("@color/")) {
|
||||
var ref = value.substring("@color/".length());
|
||||
if (name.equals(ref)) {
|
||||
|
||||
final var value = resource.getValue ();
|
||||
if (value.startsWith ("@color/")) {
|
||||
var ref = value.substring ("@color/".length ());
|
||||
if (name.equals (ref)) {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String[] findArray(@NonNull String name) {
|
||||
final var table = ValuesTableFactory.getTable(resDir);
|
||||
public String[] findArray (@NonNull String name) {
|
||||
final var table = ValuesTableFactory.getTable (resDir);
|
||||
if (table == null) {
|
||||
return new String[] {name};
|
||||
return new String[]{name};
|
||||
}
|
||||
|
||||
final var resource = table.findArray(name);
|
||||
|
||||
final var resource = table.findArray (name);
|
||||
if (resource == null) {
|
||||
return new String[] {name};
|
||||
return new String[]{name};
|
||||
}
|
||||
|
||||
return resource.getValues();
|
||||
|
||||
return resource.getValues ();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String findDimension(@NonNull String name) {
|
||||
final var table = ValuesTableFactory.getTable(resDir);
|
||||
|
||||
public String findDimension (@NonNull String name) {
|
||||
final var table = ValuesTableFactory.getTable (resDir);
|
||||
|
||||
if (table == null) {
|
||||
return name;
|
||||
}
|
||||
|
||||
final var resource = table.findDimension(name);
|
||||
|
||||
final var resource = table.findDimension (name);
|
||||
if (resource == null) {
|
||||
return name;
|
||||
}
|
||||
|
||||
final var value = resource.getValue();
|
||||
if (value.startsWith("@dimen/")) {
|
||||
var ref = value.substring("@dimen/".length());
|
||||
if (name.equals(ref)) {
|
||||
|
||||
final var value = resource.getValue ();
|
||||
if (value.startsWith ("@dimen/")) {
|
||||
var ref = value.substring ("@dimen/".length ());
|
||||
if (name.equals (ref)) {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public void setInflatingFile(@NonNull File file) {
|
||||
if (file.getParentFile() == null) {
|
||||
throw new IllegalArgumentException("Invalid inflating file");
|
||||
public Collection<String> listResourceNames (@Nullable String type) {
|
||||
if (type == null) {
|
||||
return listAllResourceNames ();
|
||||
} else {
|
||||
final var set = new HashSet<String> ();
|
||||
switch (type) {
|
||||
case "drawable":
|
||||
for (var folder : this.drawables) {
|
||||
listAndAddTo (set, folder, type);
|
||||
}
|
||||
break;
|
||||
case "layout":
|
||||
listAndAddTo (set, this.layout, type);
|
||||
break;
|
||||
case "color":
|
||||
listAndAddTo (set, this.color, type);
|
||||
break;
|
||||
case "mipmap":
|
||||
for (var folder : this.mipmaps) {
|
||||
listAndAddTo (set, folder, type);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return set;
|
||||
}
|
||||
|
||||
setupDirectories(
|
||||
file // layout file
|
||||
.getParentFile() // layout dir
|
||||
.getParentFile()); // res dir
|
||||
}
|
||||
|
||||
private void setupDirectories(File resDir) {
|
||||
|
||||
if (resDir == null) {
|
||||
LOG.error("Null resource directory passed to resource finder. Ignoring.");
|
||||
|
||||
private void listAndAddTo (HashSet<String> set, @NonNull File dir, String type) {
|
||||
final var files = dir.listFiles ();
|
||||
if (files == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
for (var file : files) {
|
||||
var name = file.getName ();
|
||||
if (!name.contains (".")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
name = name.substring (0, name.lastIndexOf ('.'));
|
||||
name = String.format (Locale.ROOT, "@%s/%s", type, name);
|
||||
set.add (name);
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Collection<String> listAllResourceNames () {
|
||||
final var set = new HashSet<> (listResourceNames ("color"));
|
||||
set.addAll (listResourceNames ("drawable"));
|
||||
set.addAll (listResourceNames ("mipmap"));
|
||||
set.addAll (listResourceNames ("layout"));
|
||||
return set;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInflatingFile (@NonNull File file) {
|
||||
if (file.getParentFile () == null) {
|
||||
throw new IllegalArgumentException ("Invalid inflating file");
|
||||
}
|
||||
|
||||
setupDirectories (
|
||||
file // layout file
|
||||
.getParentFile () // layout dir
|
||||
.getParentFile ()); // res dir
|
||||
}
|
||||
|
||||
private void setupDirectories (File resDir) {
|
||||
|
||||
if (resDir == null) {
|
||||
LOG.error ("Null resource directory passed to resource finder. Ignoring.");
|
||||
return;
|
||||
}
|
||||
|
||||
this.resDir = resDir;
|
||||
this.color = new File(resDir, "color");
|
||||
this.layout = new File(resDir, "layout");
|
||||
|
||||
final File[] drawables = resDir.listFiles(new NameStartsWith("drawable"));
|
||||
this.color = new File (resDir, "color");
|
||||
this.layout = new File (resDir, "layout");
|
||||
|
||||
final File[] drawables = resDir.listFiles (new NameStartsWith ("drawable"));
|
||||
this.drawables = drawables == null ? new File[0] : drawables;
|
||||
|
||||
final File[] mipmaps = resDir.listFiles(new NameStartsWith("mipmap"));
|
||||
|
||||
final File[] mipmaps = resDir.listFiles (new NameStartsWith ("mipmap"));
|
||||
this.mipmaps = mipmaps == null ? new File[0] : mipmaps;
|
||||
}
|
||||
|
||||
private File findFileWithName(File[] files, String name) {
|
||||
|
||||
private File findFileWithName (File[] files, String name) {
|
||||
if (files == null || name == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
for (File file : files) {
|
||||
var simpleName = file.getName();
|
||||
|
||||
if (simpleName.startsWith(name)) {
|
||||
var simpleName = file.getName ();
|
||||
|
||||
if (simpleName.startsWith (name)) {
|
||||
return file;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
private static class NameStartsWith implements FileFilter {
|
||||
|
||||
|
||||
private final String name;
|
||||
|
||||
public NameStartsWith(String name) {
|
||||
|
||||
public NameStartsWith (String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean accept(@NonNull File file) {
|
||||
return file.getName().startsWith(name);
|
||||
public boolean accept (@NonNull File file) {
|
||||
return file.getName ().startsWith (name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ This file is part of AndroidIDE.
|
||||
~
|
||||
~ AndroidIDE is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by
|
||||
~ the Free Software Foundation, either version 3 of the License, or
|
||||
~ (at your option) any later version.
|
||||
~
|
||||
~ AndroidIDE is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
~ GNU General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License
|
||||
~ along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
<com.google.android.material.textfield.TextInputLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/res_input"
|
||||
style="@style/AppTheme.TextInputLayout.Outlined"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:hint="@string/hint_resource_reference"
|
||||
app:hintEnabled="true"
|
||||
app:hintTextColor="@color/secondaryColor"
|
||||
tools:layout_margin="16dp">
|
||||
|
||||
<com.google.android.material.textfield.MaterialAutoCompleteTextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:digits="abcdefghijklmnopqrstwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_/@"
|
||||
android:inputType="textNoSuggestions"
|
||||
android:padding="16dp" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
@@ -342,4 +342,5 @@
|
||||
<string name="title_color_picker">Color picker</string>
|
||||
<string name="hint_string_value">String value</string>
|
||||
<string name="hint_string_resource">String resource</string>
|
||||
<string name="hint_resource_reference">Any resource reference</string>
|
||||
</resources>
|
||||
|
||||
@@ -18,12 +18,16 @@
|
||||
package com.itsaky.inflater;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Collection;
|
||||
|
||||
/** Finds values in resources of a project */
|
||||
/**
|
||||
* Finds values in resources of a project
|
||||
*/
|
||||
public interface IResourceTable {
|
||||
|
||||
|
||||
/**
|
||||
* Find the path of the drawable with the provided name
|
||||
*
|
||||
@@ -32,69 +36,79 @@ public interface IResourceTable {
|
||||
* @param name The name of the drawable
|
||||
* @return The drawable resource.
|
||||
*/
|
||||
File findDrawable(@NonNull String name);
|
||||
|
||||
File findDrawable (@NonNull String name);
|
||||
|
||||
/**
|
||||
* Find the layout file with the specified name.
|
||||
*
|
||||
* @param name The name of the layout file
|
||||
* @return The found layout resource file
|
||||
*/
|
||||
File findLayout(@NonNull String name);
|
||||
|
||||
File findLayout (@NonNull String name);
|
||||
|
||||
/**
|
||||
* Find a string resource. Must be a valid string or {@code null}
|
||||
*
|
||||
* @param name The name of the resource
|
||||
* @return Value of the resource
|
||||
*/
|
||||
String findString(@NonNull String name);
|
||||
|
||||
String findString (@NonNull String name);
|
||||
|
||||
/**
|
||||
* Find a color resource. The returned value must be a valid color value or {@code -1}
|
||||
*
|
||||
* @param name The name of the resource
|
||||
* @return Value of the resource
|
||||
*/
|
||||
String findColor(@NonNull String name);
|
||||
|
||||
String findColor (@NonNull String name);
|
||||
|
||||
/**
|
||||
* Find an array resource. Must be a valid array or {@code null}
|
||||
*
|
||||
* @param name The name of the resource
|
||||
* @return Value of the resource
|
||||
*/
|
||||
String[] findArray(@NonNull String name);
|
||||
|
||||
String[] findArray (@NonNull String name);
|
||||
|
||||
/**
|
||||
* Find the dimension value with the given name.
|
||||
*
|
||||
* @param name The name of the dimension resource
|
||||
* @return The String representation of the dimension. It could one of the following format :
|
||||
* 1dp, 12sp, 123px, etc.
|
||||
* 1dp, 12sp, 123px, etc.
|
||||
*/
|
||||
String findDimension(@NonNull String name);
|
||||
|
||||
String findDimension (@NonNull String name);
|
||||
|
||||
/**
|
||||
* Find the boolean value with the given name.
|
||||
*
|
||||
* @param name The name of the boolean resource.
|
||||
* @return The string value of the boolean resource.
|
||||
*/
|
||||
String findBoolean(@NonNull String name);
|
||||
|
||||
String findBoolean (@NonNull String name);
|
||||
|
||||
/**
|
||||
* Find the integer value with the given name.
|
||||
*
|
||||
* @param name The name of the integer resource.
|
||||
* @return The string value of the integer resource.
|
||||
*/
|
||||
String findInteger(@NonNull String name);
|
||||
|
||||
String findInteger (@NonNull String name);
|
||||
|
||||
/**
|
||||
* Set the file which is currently being inflated.
|
||||
*
|
||||
* @param file The file.
|
||||
*/
|
||||
void setInflatingFile(@NonNull File file);
|
||||
void setInflatingFile (@NonNull File file);
|
||||
|
||||
/**
|
||||
* Lists the resources with the given type.
|
||||
*
|
||||
* @param type The type of the resources to list. Might be <code>null</code>.
|
||||
* @return A collection of resource names. If <code>type</code> is <code>null</code>,
|
||||
* all the resources in the resource table must be listed.
|
||||
*/
|
||||
@NonNull
|
||||
Collection<String> listResourceNames (@Nullable String type);
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import androidx.annotation.NonNull;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Provides framework resource values.
|
||||
@@ -32,42 +33,54 @@ import java.util.Objects;
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public class FrameworkValues {
|
||||
|
||||
|
||||
@NonNull
|
||||
public static List<String> listDimens() {
|
||||
return listFields(android.R.dimen.class);
|
||||
public static List<String> listDimens () {
|
||||
return listFields (android.R.dimen.class);
|
||||
}
|
||||
|
||||
|
||||
@NonNull
|
||||
public static List<String> listBools() {
|
||||
return listFields(android.R.bool.class);
|
||||
public static List<String> listBools () {
|
||||
return listFields (android.R.bool.class);
|
||||
}
|
||||
|
||||
|
||||
@NonNull
|
||||
public static List<String> listStrings() {
|
||||
return listFields(android.R.string.class);
|
||||
public static List<String> listStrings () {
|
||||
return listFields (android.R.string.class);
|
||||
}
|
||||
|
||||
|
||||
@NonNull
|
||||
public static List<String> listColors() {
|
||||
return listFields(android.R.color.class);
|
||||
public static List<String> listColors () {
|
||||
return listFields (android.R.color.class);
|
||||
}
|
||||
|
||||
|
||||
@NonNull
|
||||
private static List<String> listFields(final Class<?> klass) {
|
||||
Objects.requireNonNull(klass);
|
||||
|
||||
final var list = new ArrayList<String>();
|
||||
final var fields = klass.getDeclaredFields();
|
||||
private static List<String> listFields (final Class<?> klass) {
|
||||
Objects.requireNonNull (klass);
|
||||
|
||||
final var list = new ArrayList<String> ();
|
||||
final var fields = klass.getDeclaredFields ();
|
||||
for (var field : fields) {
|
||||
final var mods = field.getModifiers();
|
||||
if (!isPublic(mods) || !isStatic(mods)) {
|
||||
final var mods = field.getModifiers ();
|
||||
if (!isPublic (mods) || !isStatic (mods)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
list.add(field.getName());
|
||||
|
||||
list.add (field.getName ());
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static List<String> listAllResources () {
|
||||
final var list = new ArrayList<String> ();
|
||||
for (var klass : android.R.class.getDeclaredClasses ()) {
|
||||
list.addAll (listFields (klass).stream ()
|
||||
.map (("@android:" + klass.getSimpleName () + "/")::concat)
|
||||
.collect (Collectors.toList ())
|
||||
);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,8 +48,8 @@ import java.util.Objects;
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public class ValuesTable {
|
||||
|
||||
private static final Logger LOG = Logger.instance("ValuesTable");
|
||||
|
||||
private static final Logger LOG = Logger.instance ("ValuesTable");
|
||||
// TODO Parse styles and styleables
|
||||
private final Map<String, IResource> arrays;
|
||||
private final Map<String, IResource> strings;
|
||||
@@ -57,185 +57,185 @@ public class ValuesTable {
|
||||
private final Map<String, IResource> integers;
|
||||
private final Map<String, IResource> dimens;
|
||||
private final Map<String, IResource> booleans;
|
||||
|
||||
private final Map<String, Map<String, IResource>> resourceMap = new HashMap<>();
|
||||
|
||||
protected ValuesTable() {
|
||||
this.arrays = new HashMap<>();
|
||||
this.strings = new HashMap<>();
|
||||
this.colors = new HashMap<>();
|
||||
this.integers = new HashMap<>();
|
||||
this.dimens = new HashMap<>();
|
||||
this.booleans = new HashMap<>();
|
||||
|
||||
this.resourceMap.put("array", this.arrays);
|
||||
this.resourceMap.put("string", this.strings);
|
||||
this.resourceMap.put("color", this.colors);
|
||||
this.resourceMap.put("integer", this.integers);
|
||||
this.resourceMap.put("dimen", this.dimens);
|
||||
this.resourceMap.put("bool", this.booleans);
|
||||
|
||||
private final Map<String, Map<String, IResource>> resourceMap = new HashMap<> ();
|
||||
|
||||
protected ValuesTable () {
|
||||
this.arrays = new HashMap<> ();
|
||||
this.strings = new HashMap<> ();
|
||||
this.colors = new HashMap<> ();
|
||||
this.integers = new HashMap<> ();
|
||||
this.dimens = new HashMap<> ();
|
||||
this.booleans = new HashMap<> ();
|
||||
|
||||
this.resourceMap.put ("array", this.arrays);
|
||||
this.resourceMap.put ("string", this.strings);
|
||||
this.resourceMap.put ("color", this.colors);
|
||||
this.resourceMap.put ("integer", this.integers);
|
||||
this.resourceMap.put ("dimen", this.dimens);
|
||||
this.resourceMap.put ("bool", this.booleans);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a new table by reading the files from the given values directory.
|
||||
*
|
||||
* @param valuesDirectory The "res/values" directory.
|
||||
* @return The values table or {@code null} if the directory is invalid or there are no files in
|
||||
* this directory.
|
||||
* this directory.
|
||||
* @throws XmlPullParserException Thrown by {@link XmlPullParserFactory}.
|
||||
* @throws IOException Thrown by {@link XmlPullParser}.
|
||||
* @throws IOException Thrown by {@link XmlPullParser}.
|
||||
*/
|
||||
@Nullable
|
||||
public static ValuesTable forDirectory(File valuesDirectory) throws Exception {
|
||||
public static ValuesTable forDirectory (File valuesDirectory) throws Exception {
|
||||
if (valuesDirectory == null) {
|
||||
LOG.error("Cannot create values table for directory: ", null);
|
||||
LOG.error ("Cannot create values table for directory: ", null);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!valuesDirectory.exists()) {
|
||||
LOG.error(
|
||||
|
||||
if (!valuesDirectory.exists ()) {
|
||||
LOG.error (
|
||||
"Cannot create values table for directory as it does not exists. Directory:",
|
||||
valuesDirectory);
|
||||
return null;
|
||||
}
|
||||
|
||||
final var files = valuesDirectory.listFiles(ValuesTable::isValid);
|
||||
|
||||
final var files = valuesDirectory.listFiles (ValuesTable::isValid);
|
||||
if (files == null) {
|
||||
LOG.error("No files found in the given values directory");
|
||||
LOG.error ("No files found in the given values directory");
|
||||
return null;
|
||||
}
|
||||
|
||||
final var start = System.currentTimeMillis();
|
||||
final var values = new ValuesTable();
|
||||
|
||||
|
||||
final var start = System.currentTimeMillis ();
|
||||
final var values = new ValuesTable ();
|
||||
|
||||
for (var file : files) {
|
||||
final var factory = XmlPullParserFactory.newInstance();
|
||||
final var parser = factory.newPullParser();
|
||||
parser.setInput(new FileInputStream(file), null);
|
||||
|
||||
var event = parser.getEventType();
|
||||
final var factory = XmlPullParserFactory.newInstance ();
|
||||
final var parser = factory.newPullParser ();
|
||||
parser.setInput (new FileInputStream (file), null);
|
||||
|
||||
var event = parser.getEventType ();
|
||||
while (event != XmlPullParser.END_DOCUMENT) {
|
||||
if (event == XmlPullParser.START_TAG) {
|
||||
final var tag = parser.getName();
|
||||
if ("resources".equals(tag)) {
|
||||
event = parser.next();
|
||||
final var tag = parser.getName ();
|
||||
if ("resources".equals (tag)) {
|
||||
event = parser.next ();
|
||||
continue;
|
||||
}
|
||||
|
||||
readTag(tag, parser, values);
|
||||
|
||||
readTag (tag, parser, values);
|
||||
}
|
||||
event = parser.next();
|
||||
event = parser.next ();
|
||||
}
|
||||
}
|
||||
|
||||
final var duration = System.currentTimeMillis() - start;
|
||||
LOG.info(
|
||||
String.format(
|
||||
Locale.getDefault(),
|
||||
|
||||
final var duration = System.currentTimeMillis () - start;
|
||||
LOG.info (
|
||||
String.format (
|
||||
Locale.getDefault (),
|
||||
"Took %d ms to read %d files in directory: %s",
|
||||
duration,
|
||||
files.length,
|
||||
valuesDirectory));
|
||||
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
private static void readTag(String tag, XmlPullParser parser, ValuesTable values)
|
||||
|
||||
private static void readTag (String tag, XmlPullParser parser, ValuesTable values)
|
||||
throws Exception {
|
||||
|
||||
if ("style".equals(tag) || "declare-styleable".equals(tag)) {
|
||||
|
||||
if ("style".equals (tag) || "declare-styleable".equals (tag)) {
|
||||
// These tags are not supported yet
|
||||
// Skip parsing until we reach next tag with same depth as of this tag
|
||||
skip(parser);
|
||||
skip (parser);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// TODO Should we check if these tags are specified within <resources>?
|
||||
final var name = getName(parser);
|
||||
final var name = getName (parser);
|
||||
if (name == null) {
|
||||
LOG.error(String.format("A <%s> resource was found with no 'name' attribute", tag));
|
||||
LOG.error (String.format ("A <%s> resource was found with no 'name' attribute", tag));
|
||||
return;
|
||||
}
|
||||
|
||||
if ("item".equals(tag)) {
|
||||
tag = parser.getAttributeValue(null, "type");
|
||||
|
||||
if ("item".equals (tag)) {
|
||||
tag = parser.getAttributeValue (null, "type");
|
||||
if (tag == null) {
|
||||
LOG.error("<item> resource value found but no 'type' was specified.");
|
||||
LOG.error ("<item> resource value found but no 'type' was specified.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// check for array before reading value
|
||||
// because array resources do not have text values
|
||||
if ("array".equals(tag) || tag.endsWith("-array")) {
|
||||
readArray(parser, values, name);
|
||||
if ("array".equals (tag) || tag.endsWith ("-array")) {
|
||||
readArray (parser, values, name);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Value must be read after all other required values have been read
|
||||
final var value = parser.nextText();
|
||||
final var value = parser.nextText ();
|
||||
IResource resourceValue;
|
||||
|
||||
|
||||
switch (tag) {
|
||||
case "string":
|
||||
resourceValue = new StringResource(name, value);
|
||||
values.strings.put(name, resourceValue);
|
||||
resourceValue = new StringResource (name, value);
|
||||
values.strings.put (name, resourceValue);
|
||||
break;
|
||||
case "color":
|
||||
resourceValue = new ColorResource(name, value);
|
||||
values.colors.put(name, resourceValue);
|
||||
resourceValue = new ColorResource (name, value);
|
||||
values.colors.put (name, resourceValue);
|
||||
break;
|
||||
case "bool":
|
||||
resourceValue = new BooleanResource(name, value);
|
||||
values.booleans.put(name, resourceValue);
|
||||
resourceValue = new BooleanResource (name, value);
|
||||
values.booleans.put (name, resourceValue);
|
||||
break;
|
||||
case "dimen":
|
||||
resourceValue = new DimensionResource(name, value);
|
||||
values.dimens.put(name, resourceValue);
|
||||
resourceValue = new DimensionResource (name, value);
|
||||
values.dimens.put (name, resourceValue);
|
||||
break;
|
||||
case "integer":
|
||||
resourceValue = new IntegerResource(name, value);
|
||||
values.integers.put(name, resourceValue);
|
||||
resourceValue = new IntegerResource (name, value);
|
||||
values.integers.put (name, resourceValue);
|
||||
break;
|
||||
default:
|
||||
LOG.warn("Unknown or unsupported resource value tag:", tag);
|
||||
LOG.warn ("Unknown or unsupported resource value tag:", tag);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static void readArray(@NonNull XmlPullParser parser, ValuesTable values, String name)
|
||||
|
||||
private static void readArray (@NonNull XmlPullParser parser, ValuesTable values, String name)
|
||||
throws XmlPullParserException, IOException {
|
||||
final var minDepth = parser.getDepth();
|
||||
final var items = new ArrayList<String>();
|
||||
|
||||
var event = parser.getEventType();
|
||||
while ((event = parser.next()) != XmlPullParser.END_DOCUMENT
|
||||
&& parser.getDepth() >= minDepth) {
|
||||
final var minDepth = parser.getDepth ();
|
||||
final var items = new ArrayList<String> ();
|
||||
|
||||
var event = parser.getEventType ();
|
||||
while ((event = parser.next ()) != XmlPullParser.END_DOCUMENT
|
||||
&& parser.getDepth () >= minDepth) {
|
||||
if (event == XmlPullParser.START_TAG) {
|
||||
final var child = parser.getName();
|
||||
if ("item".equals(child)) {
|
||||
final var value = parser.nextText();
|
||||
items.add(value);
|
||||
final var child = parser.getName ();
|
||||
if ("item".equals (child)) {
|
||||
final var value = parser.nextText ();
|
||||
items.add (value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final var arr = new String[items.size()];
|
||||
|
||||
final var arr = new String[items.size ()];
|
||||
for (int i = 0; i < arr.length; i++) {
|
||||
arr[i] = items.get(i);
|
||||
arr[i] = items.get (i);
|
||||
}
|
||||
|
||||
values.arrays.put(name, new ArrayResource(name, arr));
|
||||
|
||||
values.arrays.put (name, new ArrayResource (name, arr));
|
||||
}
|
||||
|
||||
private static void skip(@NonNull XmlPullParser parser)
|
||||
|
||||
private static void skip (@NonNull XmlPullParser parser)
|
||||
throws XmlPullParserException, IOException {
|
||||
if (parser.getEventType() != XmlPullParser.START_TAG) {
|
||||
throw new IllegalStateException();
|
||||
if (parser.getEventType () != XmlPullParser.START_TAG) {
|
||||
throw new IllegalStateException ();
|
||||
}
|
||||
int depth = 1;
|
||||
while (depth != 0) {
|
||||
switch (parser.next()) {
|
||||
switch (parser.next ()) {
|
||||
case XmlPullParser.END_TAG:
|
||||
depth--;
|
||||
break;
|
||||
@@ -245,21 +245,26 @@ public class ValuesTable {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String getName(@NonNull XmlPullParser parser) {
|
||||
return parser.getAttributeValue(null, "name");
|
||||
|
||||
private static String getName (@NonNull XmlPullParser parser) {
|
||||
return parser.getAttributeValue (null, "name");
|
||||
}
|
||||
|
||||
private static boolean isValid(File file) {
|
||||
return file != null && file.isFile() && FileUtils.isUtf8(file);
|
||||
|
||||
private static boolean isValid (File file) {
|
||||
return file != null && file.isFile () && FileUtils.isUtf8 (file);
|
||||
}
|
||||
|
||||
|
||||
@Nullable
|
||||
public Map<String, IResource> getTable(final String name) {
|
||||
Objects.requireNonNull(name);
|
||||
return this.resourceMap.get(name);
|
||||
public Map<String, IResource> getTable (final String name) {
|
||||
Objects.requireNonNull (name);
|
||||
return this.resourceMap.get (name);
|
||||
}
|
||||
|
||||
|
||||
@NonNull
|
||||
public Map<String, Map<String, IResource>> getResourceMap () {
|
||||
return resourceMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a resource value of any type.
|
||||
*
|
||||
@@ -267,133 +272,133 @@ public class ValuesTable {
|
||||
* @return The resource value or {@code null} if there is no resource with the given name.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public IResource findResource(final String name) {
|
||||
public IResource findResource (final String name) {
|
||||
IResource value;
|
||||
|
||||
value = findString(name);
|
||||
|
||||
value = findString (name);
|
||||
if (value != null) {
|
||||
return value;
|
||||
}
|
||||
|
||||
value = findColor(name);
|
||||
|
||||
value = findColor (name);
|
||||
if (value != null) {
|
||||
return value;
|
||||
}
|
||||
|
||||
value = findInteger(name);
|
||||
|
||||
value = findInteger (name);
|
||||
if (value != null) {
|
||||
return value;
|
||||
}
|
||||
|
||||
value = findDimension(name);
|
||||
|
||||
value = findDimension (name);
|
||||
if (value != null) {
|
||||
return value;
|
||||
}
|
||||
|
||||
value = findArray(name);
|
||||
|
||||
value = findArray (name);
|
||||
if (value != null) {
|
||||
return value;
|
||||
}
|
||||
|
||||
value = findBoolean(name);
|
||||
|
||||
value = findBoolean (name);
|
||||
//noinspection RedundantIfStatement
|
||||
if (value != null) {
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public ArrayResource findArray(final String name) {
|
||||
return (ArrayResource) arrays.getOrDefault(name, null);
|
||||
|
||||
public ArrayResource findArray (final String name) {
|
||||
return (ArrayResource) arrays.getOrDefault (name, null);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Find a string value with the given name.
|
||||
*
|
||||
* @param name The name of the string resource.
|
||||
* @return The string resource or {@code null} if there is no resource with the given name.
|
||||
*/
|
||||
public StringResource findString(final String name) {
|
||||
return (StringResource) strings.getOrDefault(name, null);
|
||||
public StringResource findString (final String name) {
|
||||
return (StringResource) strings.getOrDefault (name, null);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Find a color value with the given name.
|
||||
*
|
||||
* @param name The name of the color resource.
|
||||
* @return The color resource or {@code null} if there is no resource with the given name.
|
||||
*/
|
||||
public ColorResource findColor(final String name) {
|
||||
return (ColorResource) colors.getOrDefault(name, null);
|
||||
public ColorResource findColor (final String name) {
|
||||
return (ColorResource) colors.getOrDefault (name, null);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Find a integer value with the given name.
|
||||
*
|
||||
* @param name The name of the integer resource.
|
||||
* @return The integer resource or {@code null} if there is no resource with the given name.
|
||||
*/
|
||||
public IntegerResource findInteger(final String name) {
|
||||
return (IntegerResource) integers.getOrDefault(name, null);
|
||||
public IntegerResource findInteger (final String name) {
|
||||
return (IntegerResource) integers.getOrDefault (name, null);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Find a dimension value with the given name.
|
||||
*
|
||||
* @param name The name of the dimension resource.
|
||||
* @return The dimension resource or {@code null} if there is no resource with the given name.
|
||||
*/
|
||||
public DimensionResource findDimension(final String name) {
|
||||
return (DimensionResource) dimens.getOrDefault(name, null);
|
||||
public DimensionResource findDimension (final String name) {
|
||||
return (DimensionResource) dimens.getOrDefault (name, null);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Find a boolean value with the given name.
|
||||
*
|
||||
* @param name The name of the boolean resource.
|
||||
* @return The boolean resource or {@code null} if there is no resource with the given name.
|
||||
*/
|
||||
public BooleanResource findBoolean(final String name) {
|
||||
return (BooleanResource) booleans.getOrDefault(name, null);
|
||||
public BooleanResource findBoolean (final String name) {
|
||||
return (BooleanResource) booleans.getOrDefault (name, null);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reads the contents of the given file and updates this table.
|
||||
*
|
||||
* @param file The file to sync with.
|
||||
*/
|
||||
public void syncWithFile(final File file) throws Exception {
|
||||
final var start = System.currentTimeMillis();
|
||||
final var factory = XmlPullParserFactory.newInstance();
|
||||
final var parser = factory.newPullParser();
|
||||
parser.setInput(new FileInputStream(file), null);
|
||||
|
||||
var event = parser.getEventType();
|
||||
public void syncWithFile (final File file) throws Exception {
|
||||
final var start = System.currentTimeMillis ();
|
||||
final var factory = XmlPullParserFactory.newInstance ();
|
||||
final var parser = factory.newPullParser ();
|
||||
parser.setInput (new FileInputStream (file), null);
|
||||
|
||||
var event = parser.getEventType ();
|
||||
while (event != XmlPullParser.END_DOCUMENT) {
|
||||
if (event == XmlPullParser.START_TAG) {
|
||||
final var tag = parser.getName();
|
||||
if ("resources".equals(tag)) {
|
||||
event = parser.next();
|
||||
final var tag = parser.getName ();
|
||||
if ("resources".equals (tag)) {
|
||||
event = parser.next ();
|
||||
continue;
|
||||
}
|
||||
|
||||
ValuesTable.readTag(tag, parser, this);
|
||||
|
||||
ValuesTable.readTag (tag, parser, this);
|
||||
}
|
||||
event = parser.next();
|
||||
event = parser.next ();
|
||||
}
|
||||
|
||||
LOG.debug(
|
||||
|
||||
LOG.debug (
|
||||
"Syncing with file",
|
||||
file.getName(),
|
||||
file.getName (),
|
||||
"took",
|
||||
(System.currentTimeMillis() - start),
|
||||
(System.currentTimeMillis () - start),
|
||||
"ms");
|
||||
}
|
||||
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
public String toString () {
|
||||
return "ValuesTable{"
|
||||
+ "strings="
|
||||
+ strings
|
||||
|
||||
Reference in New Issue
Block a user