diff --git a/layouteditor/src/main/assets/attributes/attributes.json b/layouteditor/src/main/assets/attributes/attributes.json index b66fac0a0a..f72c8db266 100644 --- a/layouteditor/src/main/assets/attributes/attributes.json +++ b/layouteditor/src/main/assets/attributes/attributes.json @@ -638,5 +638,101 @@ "attributeName": "android:measureAllChildren", "argumentType": "boolean" } + ], + "com.google.android.material.textfield.TextInputEditText": [ + { + "name": "android:hint", + "methodName": "setHint", + "className": "text.TextInputEditTextCaller", + "attributeName": "android:hint", + "argumentType": "text|string" + }, + { + "name": "android:inputType", + "methodName": "setInputType", + "className": "text.TextInputEditTextCaller", + "attributeName": "android:inputType", + "argumentType": "flag", + "arguments": [ + "date", + "datetime", + "none", + "number", + "numberDecimal", + "numberSigned", + "numberPassword", + "phone", + "text", + "textAutoComplete", + "textAutoCorrect", + "textCapCharacters", + "textCapSentences", + "textCapWords", + "textEmailAddress", + "textEmailSubject", + "textEnableTextConversionSuggestions", + "textFilter", + "textImeMultiLine", + "textLongMessage", + "textMultiLine", + "textNoSuggestions", + "textPassword", + "textPersonName", + "textPhonetic", + "textPostalAddress", + "textShortMessage", + "textUri", + "textVisiblePassword", + "textWebEditText", + "textWebEmailAddress", + "textWebPassword", + "time" + ], + "defaultValue": "-1" + }, + { + "name": "android:textColorHint", + "methodName": "setHintTextColor", + "className": "text.TextInputEditTextCaller", + "attributeName": "android:textColorHint", + "argumentType": "color" + }, + { + "name": "android:singleLine", + "methodName": "setSingleLine", + "className": "text.TextInputEditTextCaller", + "attributeName": "android:singleLine", + "argumentType": "boolean" + } + ], + "com.google.android.material.textfield.TextInputLayout": [ + { + "name": "android:hint", + "methodName": "setHint", + "className": "text.TextInputLayoutCaller", + "attributeName": "android:hint", + "argumentType": "text|string" + }, + { + "name": "app:hintEnabled", + "methodName": "setHintEnabled", + "className": "text.TextInputLayoutCaller", + "attributeName": "app:hintEnabled", + "argumentType": "boolean" + }, + { + "name": "app:errorEnabled", + "methodName": "setErrorEnabled", + "className": "text.TextInputLayoutCaller", + "attributeName": "app:errorEnabled", + "argumentType": "boolean" + }, + { + "name": "app:counterEnabled", + "methodName": "setCounterEnabled", + "className": "text.TextInputLayoutCaller", + "attributeName": "app:counterEnabled", + "argumentType": "boolean" + } ] } \ No newline at end of file diff --git a/layouteditor/src/main/assets/palette/text.json b/layouteditor/src/main/assets/palette/text.json index d26ae8c1ce..370e177b48 100644 --- a/layouteditor/src/main/assets/palette/text.json +++ b/layouteditor/src/main/assets/palette/text.json @@ -138,5 +138,24 @@ "defaultAttributes": { "android:text": "CheckedTextView" } + }, + { + "name": "TextInputLayout", + "className": "org.appdevforall.codeonthego.layouteditor.editor.palette.text.TextInputLayoutDesign", + "iconName": "ic_palette_linear_layout_vert", + "defaultAttributes": { + "android:layout_width": "match_parent", + "android:layout_height": "wrap_content" + } + }, + { + "name": "TextInputEditText", + "className": "org.appdevforall.codeonthego.layouteditor.editor.palette.text.TextInputEditTextDesign", + "iconName": "ic_palette_edit_text", + "defaultAttributes": { + "android:layout_width": "match_parent", + "android:layout_height": "wrap_content", + "android:inputType": "text" } + } ] \ No newline at end of file diff --git a/layouteditor/src/main/assets/widgetclasses.json b/layouteditor/src/main/assets/widgetclasses.json index 7507fe9f3a..133eefd57f 100644 --- a/layouteditor/src/main/assets/widgetclasses.json +++ b/layouteditor/src/main/assets/widgetclasses.json @@ -53,5 +53,7 @@ "RatingBar": "org.appdevforall.codeonthego.layouteditor.editor.palette.widgets.RatingBarDesign", "SearchView": "org.appdevforall.codeonthego.layouteditor.editor.palette.widgets.SearchViewDesign", "TextureView": "org.appdevforall.codeonthego.layouteditor.editor.palette.widgets.TextureViewDesign", - "SurfaceView": "org.appdevforall.codeonthego.layouteditor.editor.palette.widgets.SurfaceViewDesign" + "SurfaceView": "org.appdevforall.codeonthego.layouteditor.editor.palette.widgets.SurfaceViewDesign", + "TextInputEditText": "org.appdevforall.codeonthego.layouteditor.editor.palette.text.TextInputEditTextDesign", + "TextInputLayout": "org.appdevforall.codeonthego.layouteditor.editor.palette.text.TextInputLayoutDesign" } \ No newline at end of file diff --git a/layouteditor/src/main/java/org/appdevforall/codeonthego/layouteditor/editor/DesignEditor.kt b/layouteditor/src/main/java/org/appdevforall/codeonthego/layouteditor/editor/DesignEditor.kt index 26dc132342..f6872f69e2 100644 --- a/layouteditor/src/main/java/org/appdevforall/codeonthego/layouteditor/editor/DesignEditor.kt +++ b/layouteditor/src/main/java/org/appdevforall/codeonthego/layouteditor/editor/DesignEditor.kt @@ -13,6 +13,7 @@ import android.view.ViewGroup import android.widget.AdapterView import android.widget.ArrayAdapter import android.widget.EditText +import android.widget.FrameLayout import android.widget.LinearLayout import android.widget.Spinner import android.widget.TextView @@ -363,11 +364,28 @@ class DesignEditor : LinearLayout { context, ) as View - newView.layoutParams = - ViewGroup.LayoutParams( - ViewGroup.LayoutParams.WRAP_CONTENT, - ViewGroup.LayoutParams.WRAP_CONTENT, - ) + newView.layoutParams = when (parent) { + is LinearLayout -> { + LayoutParams( + LayoutParams.WRAP_CONTENT, + LayoutParams.WRAP_CONTENT, + ) + } + + is FrameLayout -> { + FrameLayout.LayoutParams( + LayoutParams.WRAP_CONTENT, + LayoutParams.WRAP_CONTENT, + ) + } + + else -> { + ViewGroup.LayoutParams( + LayoutParams.WRAP_CONTENT, + LayoutParams.WRAP_CONTENT, + ) + } + } rearrangeListeners(newView) if (newView is ViewGroup) { diff --git a/layouteditor/src/main/java/org/appdevforall/codeonthego/layouteditor/editor/callers/TextInputLayoutCaller.java b/layouteditor/src/main/java/org/appdevforall/codeonthego/layouteditor/editor/callers/TextInputLayoutCaller.java deleted file mode 100644 index eb42b1b032..0000000000 --- a/layouteditor/src/main/java/org/appdevforall/codeonthego/layouteditor/editor/callers/TextInputLayoutCaller.java +++ /dev/null @@ -1,4 +0,0 @@ -package org.appdevforall.codeonthego.layouteditor.editor.callers; - -public class TextInputLayoutCaller { -} diff --git a/layouteditor/src/main/java/org/appdevforall/codeonthego/layouteditor/editor/callers/text/TextInputEditTextCaller.kt b/layouteditor/src/main/java/org/appdevforall/codeonthego/layouteditor/editor/callers/text/TextInputEditTextCaller.kt new file mode 100644 index 0000000000..04b1d92eb3 --- /dev/null +++ b/layouteditor/src/main/java/org/appdevforall/codeonthego/layouteditor/editor/callers/text/TextInputEditTextCaller.kt @@ -0,0 +1,5 @@ +package org.appdevforall.codeonthego.layouteditor.editor.callers.text + +open class TextInputEditTextCaller : EditTextCaller() { + // Inherits typical EditText attributes +} diff --git a/layouteditor/src/main/java/org/appdevforall/codeonthego/layouteditor/editor/callers/text/TextInputLayoutCaller.kt b/layouteditor/src/main/java/org/appdevforall/codeonthego/layouteditor/editor/callers/text/TextInputLayoutCaller.kt new file mode 100644 index 0000000000..9e4946b194 --- /dev/null +++ b/layouteditor/src/main/java/org/appdevforall/codeonthego/layouteditor/editor/callers/text/TextInputLayoutCaller.kt @@ -0,0 +1,38 @@ +package org.appdevforall.codeonthego.layouteditor.editor.callers.text + +import android.content.Context +import android.view.View +import com.google.android.material.textfield.TextInputLayout +import org.appdevforall.codeonthego.layouteditor.managers.ProjectManager +import org.appdevforall.codeonthego.layouteditor.managers.ValuesManager +import org.appdevforall.codeonthego.layouteditor.tools.ValuesResourceParser + +object TextInputLayoutCaller { + + @JvmStatic + fun setHint(target: View, value: String, context: Context) { + var finalValue = value + if (finalValue.startsWith("@string/")) { + val project = ProjectManager.instance.openedProject ?: return + finalValue = ValuesManager.getValueFromResources( + ValuesResourceParser.TAG_STRING, finalValue, project.stringsPath + ) + } + (target as TextInputLayout).hint = finalValue + } + + @JvmStatic + fun setHintEnabled(target: View, value: String, context: Context) { + (target as TextInputLayout).isHintEnabled = value.toBoolean() + } + + @JvmStatic + fun setErrorEnabled(target: View, value: String, context: Context) { + (target as TextInputLayout).isErrorEnabled = value.toBoolean() + } + + @JvmStatic + fun setCounterEnabled(target: View, value: String, context: Context) { + (target as TextInputLayout).isCounterEnabled = value.toBoolean() + } +} diff --git a/layouteditor/src/main/java/org/appdevforall/codeonthego/layouteditor/editor/palette/text/TextInputEditTextDesign.kt b/layouteditor/src/main/java/org/appdevforall/codeonthego/layouteditor/editor/palette/text/TextInputEditTextDesign.kt new file mode 100644 index 0000000000..999fc58858 --- /dev/null +++ b/layouteditor/src/main/java/org/appdevforall/codeonthego/layouteditor/editor/palette/text/TextInputEditTextDesign.kt @@ -0,0 +1,43 @@ +package org.appdevforall.codeonthego.layouteditor.editor.palette.text + +import android.content.Context +import android.graphics.Canvas +import com.google.android.material.textfield.TextInputEditText +import org.appdevforall.codeonthego.layouteditor.utils.Constants +import org.appdevforall.codeonthego.layouteditor.utils.Utils + +open class TextInputEditTextDesign(context: Context) : TextInputEditText(context) { + + private var drawStrokeEnabled: Boolean = false + private var isBlueprint: Boolean = false + + override fun dispatchDraw(canvas: Canvas) { + super.dispatchDraw(canvas) + + if (drawStrokeEnabled) { + Utils.drawDashPathStroke( + this, + canvas, + if (isBlueprint) Constants.BLUEPRINT_DASH_COLOR else Constants.DESIGN_DASH_COLOR + ) + } + } + + fun setStrokeEnabled(enabled: Boolean) { + drawStrokeEnabled = enabled + invalidate() + } + + override fun draw(canvas: Canvas) { + if (isBlueprint) { + Utils.drawDashPathStroke(this, canvas, Constants.BLUEPRINT_DASH_COLOR) + } else { + super.draw(canvas) + } + } + + fun setBlueprint(isBlueprint: Boolean) { + this.isBlueprint = isBlueprint + invalidate() + } +} diff --git a/layouteditor/src/main/java/org/appdevforall/codeonthego/layouteditor/editor/palette/text/TextInputLayoutDesign.kt b/layouteditor/src/main/java/org/appdevforall/codeonthego/layouteditor/editor/palette/text/TextInputLayoutDesign.kt new file mode 100644 index 0000000000..03049bb20c --- /dev/null +++ b/layouteditor/src/main/java/org/appdevforall/codeonthego/layouteditor/editor/palette/text/TextInputLayoutDesign.kt @@ -0,0 +1,43 @@ +package org.appdevforall.codeonthego.layouteditor.editor.palette.text + +import android.content.Context +import android.graphics.Canvas +import com.google.android.material.textfield.TextInputLayout +import org.appdevforall.codeonthego.layouteditor.utils.Constants +import org.appdevforall.codeonthego.layouteditor.utils.Utils + +open class TextInputLayoutDesign(context: Context) : TextInputLayout(context) { + + private var drawStrokeEnabled: Boolean = false + private var isBlueprint: Boolean = false + + override fun dispatchDraw(canvas: Canvas) { + super.dispatchDraw(canvas) + + if (drawStrokeEnabled) { + Utils.drawDashPathStroke( + this, + canvas, + if (isBlueprint) Constants.BLUEPRINT_DASH_COLOR else Constants.DESIGN_DASH_COLOR + ) + } + } + + fun setStrokeEnabled(enabled: Boolean) { + drawStrokeEnabled = enabled + invalidate() + } + + override fun draw(canvas: Canvas) { + if (isBlueprint) { + Utils.drawDashPathStroke(this, canvas, Constants.BLUEPRINT_DASH_COLOR) + } else { + super.draw(canvas) + } + } + + fun setBlueprint(isBlueprint: Boolean) { + this.isBlueprint = isBlueprint + invalidate() + } +} diff --git a/layouteditor/src/main/java/org/appdevforall/codeonthego/layouteditor/tools/XmlLayoutGenerator.java b/layouteditor/src/main/java/org/appdevforall/codeonthego/layouteditor/tools/XmlLayoutGenerator.java index 631fa235c0..67342a8893 100644 --- a/layouteditor/src/main/java/org/appdevforall/codeonthego/layouteditor/tools/XmlLayoutGenerator.java +++ b/layouteditor/src/main/java/org/appdevforall/codeonthego/layouteditor/tools/XmlLayoutGenerator.java @@ -18,6 +18,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Objects; public class XmlLayoutGenerator { final StringBuilder builder = new StringBuilder(); @@ -35,60 +36,69 @@ public String generate(@NonNull DesignEditor editor, boolean useSuperclasses) { } builder.append("\n"); - return peek(editor.getChildAt(0), editor.getViewAttributeMap(), 0); + peek(editor.getChildAt(0), editor.getViewAttributeMap(), 0); + return builder.toString().trim(); } - private String peek(View view, HashMap attributeMap, int depth) { - if (attributeMap == null || view == null) return ""; + private void peek(View view, HashMap attributeMap, int depth) { + if (attributeMap == null || view == null) return; + + if (!attributeMap.containsKey(view)) { + if (!(view instanceof ViewGroup group)) return; + + for (int i = 0; i < group.getChildCount(); i++) { + peek(group.getChildAt(i), attributeMap, depth); + } + return; + } + if (tryWriteInclude(view, attributeMap, depth)) { - return builder.toString(); + return; } if (tryWriteFragment(view, attributeMap, depth)) { - return builder.toString(); + return; } if (tryWriteMerge(view, attributeMap, depth)) { - return builder.toString(); + return; } String indent = getIndent(depth); - int nextDepth = depth; String className = getClassName(view, indent); List keys = - (attributeMap.get(view) != null) ? attributeMap.get(view).keySet() : new ArrayList<>(); + (attributeMap.get(view) != null) ? new ArrayList<>(Objects.requireNonNull(attributeMap.get(view)).keySet()) : new ArrayList<>(); for (String key : keys) { - builder.append(TAB).append(indent).append(key).append("=\"").append(StringEscapeUtils.escapeXml11(attributeMap.get(view).getValue(key))).append("\"\n"); + builder.append(TAB).append(indent).append(key).append("=\"").append(StringEscapeUtils.escapeXml11(Objects.requireNonNull(attributeMap.get(view)).getValue(key))).append("\"\n"); } - builder.deleteCharAt(builder.length() - 1); + if (builder.charAt(builder.length() - 1) == '\n') { + builder.deleteCharAt(builder.length() - 1); + } - if (view instanceof ViewGroup group) { - if (!(group instanceof CalendarView) - && !(group instanceof SearchView) - && !(group instanceof NavigationView) - && !(group instanceof BottomNavigationView) - && !(group instanceof TabLayout)) { - nextDepth++; + if (!(view instanceof ViewGroup group) + || group instanceof CalendarView + || group instanceof SearchView + || group instanceof NavigationView + || group instanceof BottomNavigationView + || group instanceof TabLayout + || group.getChildCount() == 0) { + builder.append(" />\n\n"); + return; + } - if (group.getChildCount() > 0) { - builder.append(">\n\n"); + builder.append(">\n\n"); + int beforeLen = builder.length(); - for (int i = 0; i < group.getChildCount(); i++) { - peek(group.getChildAt(i), attributeMap, nextDepth); - } + for (int i = 0; i < group.getChildCount(); i++) { + peek(group.getChildAt(i), attributeMap, depth + 1); + } - builder.append(indent).append("\n\n"); - } else { - builder.append(" />\n\n"); - } - } else { + if (builder.length() == beforeLen) { + builder.setLength(beforeLen - 3); builder.append(" />\n\n"); - } } else { - builder.append(" />\n\n"); + builder.append(indent).append("\n\n"); } - - return builder.toString().trim(); } @NonNull diff --git a/layouteditor/src/main/java/org/appdevforall/codeonthego/layouteditor/tools/XmlLayoutParser.kt b/layouteditor/src/main/java/org/appdevforall/codeonthego/layouteditor/tools/XmlLayoutParser.kt index 3482cd09a0..4e656a926d 100644 --- a/layouteditor/src/main/java/org/appdevforall/codeonthego/layouteditor/tools/XmlLayoutParser.kt +++ b/layouteditor/src/main/java/org/appdevforall/codeonthego/layouteditor/tools/XmlLayoutParser.kt @@ -228,10 +228,12 @@ class XmlLayoutParser( } else { view = result as? View view?.let { - it.layoutParams = ViewGroup.LayoutParams( - ViewGroup.LayoutParams.WRAP_CONTENT, - ViewGroup.LayoutParams.WRAP_CONTENT, - ) + if (listViews.isEmpty()) { + it.layoutParams = ViewGroup.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT, + ) + } listViews.add(it) } } diff --git a/layouteditor/src/main/java/org/appdevforall/codeonthego/layouteditor/views/StructureView.kt b/layouteditor/src/main/java/org/appdevforall/codeonthego/layouteditor/views/StructureView.kt index 96576b78d7..8c84ea7a65 100644 --- a/layouteditor/src/main/java/org/appdevforall/codeonthego/layouteditor/views/StructureView.kt +++ b/layouteditor/src/main/java/org/appdevforall/codeonthego/layouteditor/views/StructureView.kt @@ -59,9 +59,11 @@ import com.google.android.material.bottomnavigation.BottomNavigationView import com.google.android.material.chip.Chip import com.google.android.material.chip.ChipGroup import com.google.android.material.color.MaterialColors +import com.google.android.material.textfield.TextInputLayout import com.google.android.material.floatingactionbutton.FloatingActionButton import com.google.android.material.navigation.NavigationView import com.google.android.material.tabs.TabLayout +import com.google.android.material.textfield.TextInputEditText import org.appdevforall.codeonthego.layouteditor.R import org.appdevforall.codeonthego.layouteditor.databinding.LayoutStructureViewItemBinding import org.appdevforall.codeonthego.layouteditor.managers.IdManager.idMap @@ -143,7 +145,7 @@ class StructureView( viewId.visibility = VISIBLE viewId.text = idMap[view] } - if (view is LinearLayout && view !is RadioGroup) { + if (view is LinearLayout && view !is RadioGroup && view !is TextInputLayout) { val orientation = if (view.orientation == LinearLayout.HORIZONTAL) "horizontal" else "vertical" val imgResId = imgMap[LinearLayout::class.java.simpleName + orientation]!! @@ -178,7 +180,8 @@ class StructureView( group !is SearchView && group !is NavigationView && group !is BottomNavigationView && - group !is TabLayout + group !is TabLayout && + group !is TextInputLayout ) { nextDepth++ @@ -186,73 +189,102 @@ class StructureView( val child = group.getChildAt(i) peek(child, nextDepth) } - } - } - } - - /** This method is called to draw rectangles, lines, and circles for each TextView in the view. */ - override fun dispatchDraw(canvas: Canvas) { - super.dispatchDraw(canvas) - - for (text in textViewMap.keys) { - val view = textViewMap[text] - val parent = text.parent.parent as ViewGroup - - if (view is ViewGroup && view.childCount > 0) { - val x = parent.x - val y = parent.y + parent.height.toFloat() / 2 - - val group = view - if (group !is CalendarView && - group !is SearchView && - group !is NavigationView && - group !is BottomNavigationView && - group !is TabLayout - ) { - canvas.drawRect( - x - pointRadius, - y - pointRadius, - x + pointRadius, - y + pointRadius, - paint, - ) - for (i in 0 until group.childCount) { - val current = viewTextMap[group.getChildAt(i)] - val currentParent = current!!.parent.parent as ViewGroup - canvas.drawLine( - parent.x, - parent.y + parent.height.toFloat() / 2, - parent.x, - currentParent.y + currentParent.height.toFloat() / 2, - paint, - ) - canvas.drawLine( - parent.x, - currentParent.y + currentParent.height.toFloat() / 2, - currentParent.x, - currentParent.y + currentParent.height.toFloat() / 2, - paint, - ) - } - } else { - canvas.drawCircle( - parent.x, - parent.y + parent.height.toFloat() / 2, - pointRadius.toFloat(), - paint, - ) + } else if (group is TextInputLayout) { + nextDepth++ + val editText = group.editText + if (editText != null) { + peek(editText, nextDepth) } - } else { - canvas.drawCircle( - parent.x, - parent.y + parent.height.toFloat() / 2, - pointRadius.toFloat(), - paint, - ) } } } + /** This method is called to draw rectangles, lines, and circles for each TextView in the view. */ + override fun dispatchDraw(canvas: Canvas) { + super.dispatchDraw(canvas) + + for (text in textViewMap.keys) { + val view = textViewMap[text] + val parent = text.parent.parent as ViewGroup + + val centerX = parent.x + val centerY = parent.y + parent.height.toFloat() / 2 + + fun drawCircle() { + canvas.drawCircle( + centerX, + centerY, + pointRadius.toFloat(), + paint, + ) + } + + fun drawRectangle() { + canvas.drawRect( + centerX - pointRadius, + centerY - pointRadius, + centerX + pointRadius, + centerY + pointRadius, + paint, + ) + } + + fun drawLine(targetParent: ViewGroup) { + val targetY = targetParent.y + targetParent.height.toFloat() / 2 + + canvas.drawLine( + centerX, + centerY, + centerX, + targetY, + paint, + ) + + canvas.drawLine( + centerX, + targetY, + targetParent.x, + targetY, + paint, + ) + } + + if (view !is ViewGroup || view.childCount <= 0) { + drawCircle() + continue + } + + when (view) { + is CalendarView, + is SearchView, + is NavigationView, + is BottomNavigationView, + is TabLayout -> { drawCircle() } + is TextInputLayout -> { + drawRectangle() + + val editText = view.editText ?: continue + val current = viewTextMap[editText] ?: continue + val currentParent = current.parent.parent as ViewGroup + + drawLine(currentParent) + } + + else -> { + drawRectangle() + + for (i in 0 until view.childCount) { + val child = view.getChildAt(i) + val current = viewTextMap[child] ?: continue + val currentParent = current.parent.parent as ViewGroup + + drawLine(currentParent) + } + } + } + } + } + /** * This method is called when a TextView is clicked, and it calls the OnItemClickListener's * onItemClick method. @@ -369,6 +401,8 @@ class StructureView( imgMap[CoordinatorLayout::class.java.simpleName] = R.mipmap.ic_palette_coordinator_layout imgMap[DrawerLayout::class.java.simpleName] = R.mipmap.ic_palette_drawer_layout + imgMap[TextInputLayout::class.java.simpleName] = R.mipmap.ic_palette_linear_layout_vert + imgMap[TextInputEditText::class.java.simpleName] = R.mipmap.ic_palette_edit_text } } }