Use sheets to edit attribute values in UI Designer

This commit is contained in:
Akash Yadav
2022-03-07 14:14:10 +05:30
parent 7b8207683a
commit ffb0fd629f
26 changed files with 989 additions and 240 deletions
+5
View File
@@ -41,5 +41,10 @@
<option name="name" value="MavenLocal" />
<option name="url" value="file:/$USER_HOME$/.m2/repository/" />
</remote-repository>
<remote-repository>
<option name="id" value="MavenLocal" />
<option name="name" value="MavenLocal" />
<option name="url" value="file:/$USER_HOME$/.m2/repository" />
</remote-repository>
</component>
</project>
+1
View File
@@ -49,6 +49,7 @@
<entry key="..\:/Projects/AndroidStudioProjects/AndroidIDE/app/src/main/res/layout/layout_attr_editor_sheet.xml" value="0.25" />
<entry key="..\:/Projects/AndroidStudioProjects/AndroidIDE/app/src/main/res/layout/layout_attr_editor_sheet_item.xml" value="0.25" />
<entry key="..\:/Projects/AndroidStudioProjects/AndroidIDE/app/src/main/res/layout/layout_attr_value_editor.xml" value="0.25" />
<entry key="..\:/Projects/AndroidStudioProjects/AndroidIDE/app/src/main/res/layout/layout_attr_value_editor_enum.xml" value="0.25" />
<entry key="..\:/Projects/AndroidStudioProjects/AndroidIDE/app/src/main/res/layout/layout_code_editor.xml" value="0.20185185185185187" />
<entry key="..\:/Projects/AndroidStudioProjects/AndroidIDE/app/src/main/res/layout/layout_completion_item.xml" value="0.23854166666666668" />
<entry key="..\:/Projects/AndroidStudioProjects/AndroidIDE/app/src/main/res/layout/layout_crash_report.xml" value="0.25" />
+5
View File
@@ -1,4 +1,5 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'com.google.gms.google-services'
apply plugin: 'com.google.firebase.crashlytics'
@@ -112,6 +113,10 @@ dependencies {
implementation 'com.google.firebase:firebase-analytics'
implementation 'com.google.firebase:firebase-crashlytics'
implementation 'com.google.firebase:firebase-messaging'
// Kotlin
implementation "androidx.core:core-ktx:1.7.0"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
// Local projects here
implementation project(':treeview')
Binary file not shown.
@@ -0,0 +1,128 @@
/*
* 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.adapters;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentStatePagerAdapter;
import com.itsaky.androidide.fragments.attr.BaseValueEditorFragment;
import com.itsaky.androidide.models.XMLAttribute;
import com.itsaky.androidide.utils.Logger;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* Adapter for value format tabs of attribute editor in UI Designer.
*
* @author Akash Yadav
*/
@SuppressWarnings("deprecation")
public class AttrValueFormatTabAdapter extends FragmentStatePagerAdapter {
private static final Logger LOG = Logger.instance ("AttrValueEditorTabAdapter");
private final List<Fragment> mFragments = new ArrayList<> ();
private final List<String> mTitles = new ArrayList<> ();
private final XMLAttribute attribute;
private final BaseValueEditorFragment.OnValueChangeListener changeListener;
public AttrValueFormatTabAdapter (@NonNull FragmentManager manager, XMLAttribute attribute, BaseValueEditorFragment.OnValueChangeListener changeListener) {
super (manager);
Objects.requireNonNull (attribute);
this.changeListener = changeListener;
this.attribute = attribute;
}
public void addFragment (final Class<? extends BaseValueEditorFragment> fragment, String title) {
Objects.requireNonNull (fragment);
Objects.requireNonNull (title);
if (TextUtils.isEmpty (title.trim ())) {
throw new IllegalArgumentException ("Invalid tab title");
}
mTitles.add (capitalize (title));
mFragments.add (createFragment (fragment, title));
}
@NonNull
private String capitalize (String title) {
title = title.trim ();
final var sb = new StringBuilder ();
for (int i = 0; i < title.length (); i++) {
var ch = title.charAt (i);
if (i == 0) {
ch = Character.toUpperCase (ch);
} else {
ch = Character.toLowerCase (ch);
}
sb.append (ch);
}
return sb.toString ();
}
public String getTitle (int position) {
return mTitles.get (position);
}
@NonNull
public Fragment createFragment (Class<? extends BaseValueEditorFragment> clazz, @NonNull final String name) {
try {
final var frag = clazz.newInstance ();
frag.setAttribute (attribute);
frag.setName (name);
frag.setOnValueChangeListener (this.changeListener);
return frag;
} catch (Throwable th) {
LOG.error ("Unable to create value editor fragment", th);
throw new RuntimeException (th);
}
}
public void removeAll () {
mFragments.clear ();
mTitles.clear ();
}
@NonNull
@Override
public Fragment getItem (int position) {
return mFragments.get (position);
}
@Override
public int getCount () {
return mFragments.size ();
}
@Nullable
@Override
public CharSequence getPageTitle (int position) {
return mTitles.get (position);
}
}
@@ -0,0 +1,71 @@
/*
* 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 androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import com.itsaky.androidide.models.XMLAttribute;
import com.itsaky.inflater.IAttribute;
import java.util.Objects;
/**
* Base class for value editor fragment.
*
* @author Akash Yadav
*/
public class BaseValueEditorFragment extends Fragment {
protected OnValueChangeListener mValueChangeListener;
protected XMLAttribute attribute;
protected String name;
public void setAttribute (XMLAttribute attribute) {
this.attribute = attribute;
}
public void setName (String name) {
this.name = name;
}
public void setOnValueChangeListener (OnValueChangeListener listener) {
this.mValueChangeListener = listener;
}
protected void notifyValueChanged (@NonNull String newValue) {
Objects.requireNonNull (newValue);
if (mValueChangeListener != null) {
mValueChangeListener.onValueChanged (this.attribute, newValue);
}
}
/**
* Listener for listening for value change event.
*/
public interface OnValueChangeListener {
/**
* Called when the value was changed.
*
* @param attribute The attribute whose value was changed.
* @param newValue The new value for the editor.
*/
void onValueChanged (IAttribute attribute, String newValue);
}
}
@@ -0,0 +1,24 @@
/*
* 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;
/**
* @author Akash Yadav
*/
public class BooleanEditor extends BaseValueEditorFragment {
}
@@ -0,0 +1,24 @@
/*
* 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;
/**
* @author Akash Yadav
*/
public class ColorEditor extends BaseValueEditorFragment {
}
@@ -0,0 +1,24 @@
/*
* 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;
/**
* @author Akash Yadav
*/
public class DimensionEditor extends BaseValueEditorFragment {
}
@@ -0,0 +1,60 @@
/*
* 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 android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.material.chip.Chip;
import java.util.Objects;
/**
* @author Akash Yadav
*/
public class EnumEditor extends FixedValueEditor {
@Override
public void onViewCreated (@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated (view, savedInstanceState);
Objects.requireNonNull (this.attribute);
Objects.requireNonNull (this.chipGroup);
this.chipGroup.setSingleSelection (true);
final var attr = this.attribute.getAttr ();
for (var value : attr.possibleValues) {
if (TextUtils.isEmpty (value)) {
continue;
}
this.chipGroup.addView (newChip (value, value.equals (this.attribute.getValue ())));
}
this.chipGroup.setOnCheckedChangeListener ((group, checkedId) -> {
final var chip = (Chip) group.findViewById (checkedId);
final var val = chip.getText ().toString ().trim ();
notifyValueChanged (val);
});
}
}
@@ -0,0 +1,64 @@
/*
* 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 android.content.res.ColorStateList;
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 androidx.core.content.ContextCompat;
import com.google.android.material.chip.Chip;
import com.google.android.material.chip.ChipGroup;
import com.itsaky.androidide.R;
/**
* @author Akash Yadav
*/
public abstract class FixedValueEditor extends BaseValueEditorFragment {
protected ChipGroup chipGroup;
@Nullable
@Override
public View onCreateView (@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
this.chipGroup = new ChipGroup (inflater.getContext ());
this.chipGroup.setLayoutParams (new ViewGroup.LayoutParams (ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
this.chipGroup.setSelectionRequired (true);
this.chipGroup.setSingleSelection (false);
return this.chipGroup;
}
protected Chip newChip (String title, boolean checked) {
final var chip = new Chip (requireContext ());
chip.setText (title);
chip.setCheckedIcon (ContextCompat.getDrawable (requireContext (), R.drawable.ic_ok));
chip.setChecked (checked);
chip.setChipBackgroundColor (ContextCompat.getColorStateList (requireContext (), R.color.bg_enum_chips));
chip.setTextColor (ContextCompat.getColor (requireContext (), R.color.primaryTextColor));
chip.setLayoutParams (new ChipGroup.LayoutParams (ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
chip.setChipStrokeWidth (1f);
chip.setChipStrokeColor (ColorStateList.valueOf (ContextCompat.getColor (requireContext (), R.color.primaryDarkColor)));
return chip;
}
}
@@ -0,0 +1,24 @@
/*
* 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;
/**
* @author Akash Yadav
*/
public class FlagEditor extends BaseValueEditorFragment {
}
@@ -0,0 +1,24 @@
/*
* 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;
/**
* @author Akash Yadav
*/
public class FloatEditor extends BaseValueEditorFragment {
}
@@ -0,0 +1,24 @@
/*
* 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;
/**
* @author Akash Yadav
*/
public class FractionEditor extends BaseValueEditorFragment {
}
@@ -0,0 +1,24 @@
/*
* 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;
/**
* @author Akash Yadav
*/
public class IntegerEditor extends BaseValueEditorFragment {
}
@@ -0,0 +1,24 @@
/*
* 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;
/**
* @author Akash Yadav
*/
public class ReferenceEditor extends BaseValueEditorFragment {
}
@@ -0,0 +1,24 @@
/*
* 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;
/**
* @author Akash Yadav
*/
public class StringEditor extends BaseValueEditorFragment {
}
@@ -75,6 +75,7 @@ public class AttrEditorSheet extends BottomSheetDialogFragment
private static final List<IconTextListItem> VIEW_ACTIONS = new ArrayList<> ();
private static final Logger LOG = Logger.instance ("AttrBottomSheet");
private AttributeListSheet mAttrListSheet;
private AttrValueEditorSheet mValueEditorSheet;
private IView selectedView;
private File layout;
private LayoutAttrEditorSheetBinding binding;
@@ -108,8 +109,6 @@ public class AttrEditorSheet extends BottomSheetDialogFragment
R.drawable.ic_view_select_parent));
}
binding.valueEditorLayout.closeButton.setOnClickListener (v -> hideValueEditorLayout ());
setupViewData ();
AttributeDialogs.init (requireActivity ());
@@ -171,12 +170,11 @@ public class AttrEditorSheet extends BottomSheetDialogFragment
return;
}
showEditorDialog (attribute);
showValueEditorSheet (attribute);
}
private void hideValueEditorLayout () {
TransitionManager.beginDelayedTransition (binding.getRoot ());
binding.valueEditorLayout.getRoot ().setVisibility (View.GONE);
private void showValueEditorSheet (@NonNull XMLAttribute attribute) {
getValueEditorSheet (attribute).show (getChildFragmentManager (), "attr_value_editor_sheet");
}
private void showEditorDialog (@NonNull XMLAttribute attribute) {
@@ -371,10 +369,36 @@ public class AttrEditorSheet extends BottomSheetDialogFragment
return mAttrListSheet;
}
@NonNull
public AttrValueEditorSheet getValueEditorSheet (@NonNull XMLAttribute attribute) {
if (mValueEditorSheet == null) {
mValueEditorSheet = AttrValueEditorSheet.newInstance (attribute);
mValueEditorSheet.setOnValueChangeListener (this::onValueChanged);
}
mValueEditorSheet.setAttribute (attribute);
return mValueEditorSheet;
}
public void onValueChanged (@NonNull IAttribute attribute, String newValue) {
Objects.requireNonNull (this.selectedView);
final var attributeName = attribute.getAttributeName ();
final var namespace = attribute.getNamespace ();
if (this.selectedView.hasAttribute (namespace, attributeName)) {
this.selectedView.updateAttribute (namespace, attributeName, newValue);
} else {
final var attr = new UiAttribute (namespace, attributeName, newValue);
this.selectedView.addAttribute (attr);
}
}
private void addNewAttribute (@NonNull Attr attr) {
final XMLAttribute attribute = new XMLAttribute (attr.namespace, attr.name, "", false);
attribute.setAttr (attr);
showEditorDialog (attribute);
showValueEditorSheet (attribute);
}
/**
@@ -0,0 +1,177 @@
/*
* 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.sheets;
import static com.itsaky.attrinfo.models.Attr.FORMAT_BOOLEAN;
import static com.itsaky.attrinfo.models.Attr.FORMAT_COLOR;
import static com.itsaky.attrinfo.models.Attr.FORMAT_DIMENSION;
import static com.itsaky.attrinfo.models.Attr.FORMAT_ENUM;
import static com.itsaky.attrinfo.models.Attr.FORMAT_FLAG;
import static com.itsaky.attrinfo.models.Attr.FORMAT_FLOAT;
import static com.itsaky.attrinfo.models.Attr.FORMAT_FRACTION;
import static com.itsaky.attrinfo.models.Attr.FORMAT_INTEGER;
import static com.itsaky.attrinfo.models.Attr.FORMAT_REFERENCE;
import static com.itsaky.attrinfo.models.Attr.FORMAT_STRING;
import android.annotation.SuppressLint;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import com.itsaky.androidide.adapters.AttrValueFormatTabAdapter;
import com.itsaky.androidide.databinding.LayoutAttrValueEditorBinding;
import com.itsaky.androidide.fragments.attr.BaseValueEditorFragment;
import com.itsaky.androidide.fragments.attr.BooleanEditor;
import com.itsaky.androidide.fragments.attr.ColorEditor;
import com.itsaky.androidide.fragments.attr.DimensionEditor;
import com.itsaky.androidide.fragments.attr.EnumEditor;
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.ReferenceEditor;
import com.itsaky.androidide.fragments.attr.StringEditor;
import com.itsaky.androidide.models.XMLAttribute;
import com.itsaky.attrinfo.models.Attr;
import org.jetbrains.annotations.Contract;
import java.util.Objects;
/**
* Sheet for editing value of an attribute.
*
* @author Akash Yadav
*/
public class AttrValueEditorSheet extends BottomSheetDialogFragment {
private static final String KEY_ATTRIBUTE = "attrEditorSheet_attribute";
private LayoutAttrValueEditorBinding binding;
private AttrValueFormatTabAdapter adapter;
private XMLAttribute attribute;
private BaseValueEditorFragment.OnValueChangeListener changeListener;
public void setOnValueChangeListener (BaseValueEditorFragment.OnValueChangeListener changeListener) {
this.changeListener = changeListener;
}
@NonNull
public static AttrValueEditorSheet newInstance (final XMLAttribute attribute) {
Objects.requireNonNull (attribute);
final var args = new Bundle ();
args.putParcelable (KEY_ATTRIBUTE, attribute);
final var fragment = new AttrValueEditorSheet ();
fragment.setArguments (args);
return fragment;
}
@Nullable
@Override
public View onCreateView (@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
this.binding = LayoutAttrValueEditorBinding.inflate (inflater, container, false);
return binding.getRoot ();
}
@Override
public void onViewCreated (@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated (view, savedInstanceState);
if (attribute == null) {
this.attribute = requireArguments ().getParcelable (KEY_ATTRIBUTE);
}
Objects.requireNonNull (this.attribute);
final var namespace = this.attribute.getNamespace ();
final var formatText = Attr.createFormatText (attribute.getFormat ());
this.binding.attributeName.setText (String.format ("%s:%s", namespace.getName (), attribute.getAttributeName ()));
this.binding.attributeFormat.setText (formatText);
setupTabs (formatText);
}
@SuppressLint("NotifyDataSetChanged")
private void setupTabs (@NonNull String formatText) {
this.binding.tabs.removeAllTabs ();
this.binding.pager.setSaveEnabled (false);
if (this.adapter != null) {
this.adapter.removeAll ();
this.adapter.notifyDataSetChanged ();
}
this.adapter = new AttrValueFormatTabAdapter (getChildFragmentManager (), attribute, changeListener);
final var formats = formatText.split ("\\|");
for (var formatName : formats) {
if (TextUtils.isEmpty (formatName) || formatName.length () < 2) {
continue;
}
final var frag = getFragmentClass (formatName);
if (frag != null) {
this.adapter.addFragment (frag, formatName);
}
}
binding.pager.setAdapter (this.adapter);
binding.tabs.setupWithViewPager (binding.pager);
}
@Nullable
@Contract(pure = true)
private Class<? extends BaseValueEditorFragment> getFragmentClass (@NonNull String name) {
switch (name) {
case FORMAT_REFERENCE:
return ReferenceEditor.class;
case FORMAT_COLOR:
return ColorEditor.class;
case FORMAT_BOOLEAN:
return BooleanEditor.class;
case FORMAT_DIMENSION:
return DimensionEditor.class;
case FORMAT_FLOAT:
return FloatEditor.class;
case FORMAT_INTEGER:
return IntegerEditor.class;
case FORMAT_FRACTION:
return FractionEditor.class;
case FORMAT_STRING:
return StringEditor.class;
case FORMAT_ENUM:
return EnumEditor.class;
case FORMAT_FLAG:
return FlagEditor.class;
}
return null;
}
public void setAttribute (XMLAttribute attribute) {
Objects.requireNonNull (attribute);
this.attribute = attribute;
}
}
@@ -0,0 +1,37 @@
package com.itsaky.androidide.views;
import android.content.Context
import android.util.AttributeSet
import android.view.ViewGroup
import com.google.android.material.tabs.TabLayout
// https://stackoverflow.com/a/65605542
class ScalableTabLayout : TabLayout {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr
)
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val tabLayout = getChildAt(0) as ViewGroup
val childCount = tabLayout.childCount
if (childCount > 0) {
val widthPixels = MeasureSpec.getSize(widthMeasureSpec)
val tabMinWidth = widthPixels / childCount
var remainderPixels = widthPixels % childCount
for (i in 0 until childCount) {
val it = tabLayout.getChildAt(i)
if (remainderPixels > 0) {
it.minimumWidth = tabMinWidth + 1
remainderPixels--
} else {
it.minimumWidth = tabMinWidth
}
}
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
}
}
@@ -1,84 +0,0 @@
/*
* 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.views;
import android.annotation.SuppressLint;
import android.content.Context;
import android.text.Editable;
import android.text.TextWatcher;
import android.widget.ArrayAdapter;
import androidx.annotation.NonNull;
import com.google.android.material.textfield.MaterialAutoCompleteTextView;
import com.itsaky.androidide.ui.edit.IValueSuggestionProvider;
import com.itsaky.androidide.utils.TextWatcherAdapter;
import com.itsaky.inflater.IAttribute;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
/**
* {@link MaterialAutoCompleteTextView} implementation used for providing suggestions while editing
* attribute values.
*
* @author Akash Yadav
*/
@SuppressLint("ViewConstructor")
public class ValueEditorTextView extends MaterialAutoCompleteTextView {
private final IAttribute attribute;
private final int format;
private final Set<IValueSuggestionProvider> mSuggestionProviders = new HashSet<>();
private final TextWatcher watcher =
new TextWatcherAdapter() {
@Override
public void afterTextChanged(@NonNull Editable s) {
final var prefix = s.toString();
final var items = new ArrayList<String>();
for (IValueSuggestionProvider provider : mSuggestionProviders) {
if (provider.checkFormat(format)) {
items.addAll(provider.suggest(attribute, prefix));
}
}
setThreshold(1);
setAdapter(
new ArrayAdapter<>(
getContext(), android.R.layout.simple_list_item_1, items));
}
};
public ValueEditorTextView(@NonNull Context context, IAttribute attribute, int format) {
super(context);
this.attribute = attribute;
this.format = format;
addTextChangedListener(this.watcher);
}
public void registerSuggestionProvider(IValueSuggestionProvider provider) {
Objects.requireNonNull(provider);
mSuggestionProviders.add(provider);
}
}
+20
View File
@@ -0,0 +1,20 @@
<?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/>.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@color/secondaryLightColor_transparent" android:state_checked="true" android:state_selected="true" />
<item android:color="@color/content_background" />
</selector>
@@ -19,18 +19,16 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/content_background"
android:paddingStart="16dp"
android:paddingTop="16dp"
android:paddingEnd="16dp"
android:paddingBottom="8dp">
android:background="@color/content_background">
<TextView
android:id="@+id/widget_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="@color/primaryDarkColor"
android:ellipsize="middle"
android:maxLines="1"
android:padding="16dp"
android:textColor="@color/primaryTextColor"
android:textSize="20sp"
android:textStyle="bold"
@@ -39,55 +37,55 @@
app:layout_constraintTop_toTopOf="parent"
tools:text="Button" />
<TextView
android:id="@+id/actions_header"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="@string/msg_attr_editor_actions"
android:textColor="@color/secondaryTextColor"
app:layout_constraintStart_toStartOf="@id/widget_name"
app:layout_constraintTop_toBottomOf="@id/widget_name" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/actions_list"
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/actions_header" />
<TextView
android:id="@+id/attrs_header"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="@string/msg_applicable_attrs"
android:textColor="@color/secondaryTextColor"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/actions_list" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/attr_list"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/attrs_header" />
<include
android:id="@+id/value_editor_layout"
layout="@layout/layout_attr_value_editor"
android:layout_width="0dp"
android:layout_height="0dp"
android:visibility="gone"
android:padding="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
app:layout_constraintTop_toBottomOf="@id/widget_name">
<TextView
android:id="@+id/actions_header"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/msg_attr_editor_actions"
android:textColor="@color/secondaryTextColor"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/actions_list"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/actions_header" />
<TextView
android:id="@+id/attrs_header"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="@string/msg_applicable_attrs"
android:textColor="@color/secondaryTextColor"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/actions_list" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/attr_list"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/attrs_header" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -19,19 +19,21 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/content_background"
tools:padding="16dp">
android:background="@color/content_background">
<TextView
android:id="@+id/attribute_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:background="@color/primaryDarkColor"
android:maxLines="1"
android:paddingStart="16dp"
android:paddingTop="16dp"
android:paddingEnd="16dp"
android:textColor="@color/primaryTextColor"
android:textSize="20sp"
android:textStyle="bold"
app:layout_constraintEnd_toStartOf="@id/close_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="android:layout_height" />
@@ -40,35 +42,46 @@
android:id="@+id/attribute_format"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:background="@color/primaryDarkColor"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:paddingBottom="8dp"
android:textColor="@color/secondaryTextColor_light"
android:textSize="14sp"
app:layout_constraintEnd_toStartOf="@id/close_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/attribute_name"
tools:text="dimension|reference" />
<ImageView
android:id="@+id/close_button"
android:layout_width="36dp"
android:layout_height="36dp"
android:background="?selectableItemBackgroundBorderless"
android:clickable="true"
android:focusable="true"
android:padding="8dp"
android:src="@drawable/ic_close"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/editor_layout_container"
<com.itsaky.androidide.views.ScalableTabLayout
android:id="@+id/tabs"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="16dp"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_height="wrap_content"
android:background="@color/primaryDarkColor"
android:clipChildren="false"
android:clipToPadding="false"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/attribute_format" />
app:layout_constraintTop_toBottomOf="@id/attribute_format"
app:tabBackground="@android:color/transparent"
app:tabIndicatorAnimationMode="elastic"
app:tabIndicatorColor="@color/secondaryColor"
app:tabIndicatorHeight="3dp"
app:tabMaxWidth="0dp"
app:tabMode="scrollable"
app:tabSelectedTextColor="@color/secondaryColor"
app:tabTextAppearance="@style/AppTheme.TabLayout.Title" />
<androidx.viewpager.widget.ViewPager
android:id="@+id/pager"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:minHeight="100dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tabs" />
</androidx.constraintlayout.widget.ConstraintLayout>
+1
View File
@@ -5,6 +5,7 @@
<color name="primaryDarkColor">#010101</color>
<color name="secondaryColor">#ff5252</color>
<color name="secondaryLightColor">#ff867f</color>
<color name="secondaryLightColor_transparent">#1FFF867F</color>
<color name="secondaryDarkColor">#c50e29</color>
<color name="primaryTextColor">#ffffff</color>
<color name="primaryTextColorLight">#bdbdbd</color>
@@ -39,7 +39,7 @@ import java.util.regex.Pattern;
* @see com.itsaky.attrinfo.AttrInfo
*/
public class Attr {
public static final int REFERENCE = 1;
public static final int COLOR = 1 << 1;
public static final int BOOLEAN = 1 << 2;
@@ -51,122 +51,135 @@ public class Attr {
public static final int ENUM = 1 << 8;
public static final int FLAG = 1 << 9;
public static final int UNKNOWN = 1 << 10;
private static final Logger LOG = Logger.instance("AttrInfo::Attr");
public static final String FORMAT_REFERENCE = "reference";
public static final String FORMAT_COLOR = "color";
public static final String FORMAT_BOOLEAN = "boolean";
public static final String FORMAT_DIMENSION = "dimension";
public static final String FORMAT_FLOAT = "float";
public static final String FORMAT_INTEGER = "integer";
public static final String FORMAT_FRACTION = "fraction";
public static final String FORMAT_STRING = "string";
public static final String FORMAT_ENUM = "enum";
public static final String FORMAT_FLAG = "flag";
private static final Logger LOG = Logger.instance ("AttrInfo::Attr");
public INamespace namespace;
public String name;
public Set<String> possibleValues;
public int format;
public Attr(String name) {
this(name, INamespace.ANDROID);
public Attr (String name) {
this (name, INamespace.ANDROID);
}
public Attr(String name, INamespace namespace) {
public Attr (String name, INamespace namespace) {
this.name = name;
this.namespace = namespace;
this.possibleValues = new TreeSet<>();
this.possibleValues = new TreeSet<> ();
}
public static int formatForName(@NonNull String names) {
public static int formatForName (@NonNull String names) {
var result = 0;
if (names.contains("|")) {
for (var name : names.split(Pattern.quote("|"))) {
result |= formatForSingleName(name);
if (names.contains ("|")) {
for (var name : names.split (Pattern.quote ("|"))) {
result |= formatForSingleName (name);
}
} else {
result = formatForSingleName(names);
result = formatForSingleName (names);
}
return result;
}
@Contract(pure = true)
public static int formatForSingleName(@NonNull String name) {
public static int formatForSingleName (@NonNull String name) {
switch (name) {
case "reference":
case FORMAT_REFERENCE:
return REFERENCE;
case "color":
case FORMAT_COLOR:
return COLOR;
case "boolean":
case FORMAT_BOOLEAN:
return BOOLEAN;
case "dimension":
case FORMAT_DIMENSION:
return DIMENSION;
case "float":
case FORMAT_FLOAT:
return FLOAT;
case "integer":
case FORMAT_INTEGER:
return INTEGER;
case "fraction":
case FORMAT_FRACTION:
return FRACTION;
case "string":
case FORMAT_STRING:
return STRING;
case "enum":
case FORMAT_ENUM:
return ENUM;
case "flag":
case FORMAT_FLAG:
return FLAG;
default:
return UNKNOWN;
}
return UNKNOWN;
}
public static String createFormatText(int format) {
final var formats = new ArrayList<String>();
public static String createFormatText (int format) {
final var formats = new ArrayList<String> ();
if ((format & REFERENCE) != 0) {
formats.add("reference");
formats.add (FORMAT_REFERENCE);
}
if ((format & COLOR) != 0) {
formats.add("color");
formats.add (FORMAT_COLOR);
}
if ((format & BOOLEAN) != 0) {
formats.add("boolean");
formats.add (FORMAT_BOOLEAN);
}
if ((format & DIMENSION) != 0) {
formats.add("dimension");
formats.add (FORMAT_DIMENSION);
}
if ((format & FLOAT) != 0) {
formats.add("float");
formats.add (FORMAT_FLOAT);
}
if ((format & INTEGER) != 0) {
formats.add("integer");
formats.add (FORMAT_INTEGER);
}
if ((format & FRACTION) != 0) {
formats.add("fraction");
formats.add (FORMAT_FRACTION);
}
if ((format & STRING) != 0) {
formats.add("string");
formats.add (FORMAT_STRING);
}
if ((format & ENUM) != 0) {
formats.add("enum");
formats.add (FORMAT_ENUM);
}
if ((format & FLAG) != 0) {
formats.add("flag");
formats.add (FORMAT_FLAG);
}
if (formats.isEmpty()) {
formats.add("unknown");
if (formats.isEmpty ()) {
formats.add ("unknown");
}
return TextUtils.join("|", formats);
return TextUtils.join ("|", formats);
}
public boolean hasPossibleValues() {
return possibleValues != null && possibleValues.size() > 0;
public boolean hasPossibleValues () {
return possibleValues != null && possibleValues.size () > 0;
}
public boolean hasFormat(int format) {
public boolean hasFormat (int format) {
return (this.format & format) != 0;
}
@NonNull
@Override
public String toString() {
public String toString () {
return "Attr ["
+ " name: "
+ name
@@ -175,33 +188,33 @@ public class Attr {
+ namespace
+ "\n"
+ " values: "
+ TextUtils.join(", ", this.possibleValues)
+ TextUtils.join (", ", this.possibleValues)
+ "\n"
+ " format: "
+ format
+ "\n"
+ "]";
}
@Override
public boolean equals(Object o) {
public boolean equals (Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
if (o == null || getClass () != o.getClass ()) {
return false;
}
Attr attr = (Attr) o;
return format == attr.format
&& Objects.equals(namespace, attr.namespace)
&& Objects.equals(name, attr.name)
&& Objects.equals(possibleValues, attr.possibleValues);
&& Objects.equals (namespace, attr.namespace)
&& Objects.equals (name, attr.name)
&& Objects.equals (possibleValues, attr.possibleValues);
}
@Override
public int hashCode() {
return Objects.hash(namespace, name, possibleValues, format);
public int hashCode () {
return Objects.hash (namespace, name, possibleValues, format);
}
}