GithubHelp home page GithubHelp logo

pocmo / recompose Goto Github PK

View Code? Open in Web Editor NEW
784.0 10.0 49.0 1.27 MB

recompose is a tool for converting Android layouts in XML to Kotlin code using Jetpack Compose.

License: Apache License 2.0

Kotlin 100.00%
kotlin jetpack-compose compose android android-studio-plugin intellij hacktoberfest

recompose's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

recompose's Issues

Formatting: When to use line breaks?

Currently we do not use line breaks for formatting.

So for example we'd create the following Text Composable:

Text(text = "I am a test", color = Color(0xffffcc00.toInt()), fontSize = 20.sp, modifier = Modifier.width(100.dp).background(Color(0xaa0000ff.toInt())))

We probably should define a threshold of number of arguments/modifier, where we start using line breaks, e.g.:

Text(
    text = "I am a test",
    color = Color(0xffffcc00.toInt()),
    fontSize = 20.sp,
    modifier = Modifier
        .width(100.dp)
        .background(Color(0xaa0000ff.toInt()))
)

Support ImageView

https://developer.android.com/reference/android/widget/ImageView

 <LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent">
     <ImageView
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:src="@drawable/my_image"
         android:contentDescription="@string/my_image_description"
         />
 </LinearLayout>

Image: imageResource vs. vectorResource

Currently a drawable value in ImageView will always be written as:

Image(imageResource(R.id.the_drawable), ...)

This works for drawable resources that are PNGs. But it fails if the drawable is a vector image. In that case it needs to be:

Image(vectorResource(R.id.the_drawable), ...)

From the XML alone we cannot know if the drawable is a vector resource or not. We could try finding the drawable (assuming default folders are used), but this fails if the resource is not in the app itself (e.g. provided by a library or a different module).

Alternatively the plugin in the IDE could try to figure out what kind of drawable is used - but this will only work in the plugin and not in the CI.

In general I am surprised to see Jetpack Compose force making this distinction unto the consumer. Wouldn't it be more convenient to provide a drawable resource to Compose and it can figure out itself what it is?

Add support for padding

fun Modifier.padding(
    start: Dp = 0.dp,
    top: Dp = 0.dp,
    end: Dp = 0.dp,
    bottom: Dp = 0.dp
)

Wrap code inserting Kotlin code in runWriteAction()

In the plugin code we are replacing the selection with the transformed Kotlin code here:

editor.document.replaceString(
bounds.startOffset,
bounds.endOffset,
code

This yields a warning. IntelliJ wants us to wrap this in runWriteAction().
https://jetbrains.org/intellij/sdk/docs/basics/architectural_overview/general_threading_rules.html

Generate code and build against compose libraries?

alpha04 was just released and as expected some things have changed. Since that is likely to happen more often, it would be nice if we could compile the generated code for our test cases against the jetpack compose libraries. That would reveal breakage whenever we switch to the latest version.

Map 0dp width/height in ConstraintLayout to fillMaxWidth()/fillMaxHeight()

Using ConstraintLayout you'd often set android:layout_width or android:layout_height of a View to 0dp to let the ConstraintLayout stretch it based on the constraints. ConstraintLayout() doesn't behave that way if you add a width(0.dp) modifier. In that case we need to translate it to fillMaxWidth() or fillMaxHeight().

For now we could just always translate 0dp to fillMaxWidth() or fillMaxHeight() if the node has any constraint set.

Add support for MaterialCardView

com.google.android.material.card.MaterialCardView extends androidx.cardview.widget.CardView and could use the same parser.

View available in:

com.google.android.material:material:1.2.1

Spike: Writing a "Composer" that has access to the plugin context

Currently the Composer (code) is an independent class that writes Kotlin code and outputs it as a string. This string is then used by the plugin and CLI.

What if we'd implement a Composer that knows its inside an IntelliJ / Android Studio plugin and can make use of the environment and tools to write the code to the destination. A big advantage would be that we'd get access to the AST of the surrounding code and could, for example, figure out if a drawable is an image or a vector (#69). Maybe we would also be able to use the internals for generating/writing code and wouldn't need to write a plain string?

Considering the composer is the "backend" and the parser is the "frontend" we may also be able to replace the "frontend" and let IntelliJ parse the XML and give us access to its AST?

A downside of this would be that this would basically reduce this project to the plugin and potentially kill the CLI. But then again, maybe parts of it could also survive in a standalone version. After all projects like detekt also make use of the parts of the Kotlin compiler without being a plugin.

Having a closer look at the Java to Kotlin conversion in the Kotlin plugin may give some hints about what is possible.

KotlinWriterHelper

I saw you were looking for a higher level for KotlinWriter?
Maybe something like this is an option?

The code below

internal class KotlinWriterHelper(
        val writer: KotlinWriter
) {
    operator fun String.invoke(
            vararg parameters: CallParameter?,
            block: (KotlinWriter.() -> Unit)? = null
    ) = writer.writeCall(
            name = this,
            parameters = parameters.toList(),
            block = block
    )
    fun write(writer: KotlinWriterHelper.() -> Unit) = writer()
}

would change this code

writer.writeCall(
       "Row",
       parameters = listOf(
           rowModifier.toCallParameter()
        )
) {
    writeCall(
        name = "RadioButton",
        parameters = listOf(
            CallParameter(
                name = "selected",
                value = ParameterValue.RawValue(node.checked)
            ),
            CallParameter(
                name = "onCheckedChange",
                value = ParameterValue.EmptyLambdaValue
            )
       )
   )
}

into

write{
    "Row"(
         rowModifier.toCallParameter()
    ){
    "RadioButton"(
        CallParameter(
             name = "selected",
             value = ParameterValue.RawValue(node.checked)
        ),
        CallParameter(
            name = "onCheckedChange",
            value = ParameterValue.EmptyLambdaValue
        )
    )
}

Adding functions like:

    internal infix fun String.withSizeValue(size: Size) = CallParameter(ParameterValue.SizeValue(size), this)
    internal infix fun String.withColorValue(color: Color) = CallParameter(ParameterValue.ColorValue(color), this)
    internal infix fun String?.withRawValue(raw: String) = CallParameter(ParameterValue.RawValue(raw), this)
    internal infix fun String.withRawValue(raw: Boolean) = CallParameter(ParameterValue.RawValue(raw), this)
    internal infix fun String.withRawValue(raw: Int) = CallParameter(ParameterValue.RawValue(raw), this)
    internal infix fun String?.withStringValue(raw: String) = CallParameter(ParameterValue.StringValue(raw), this)
    internal fun String.withEmptyLambda() = CallParameter(ParameterValue.EmptyLambdaValue, this)
    internal infix fun String.withKeyboardType(inputType: InputType) = CallParameter(ParameterValue.KeyboardTypeValue(inputType), this)

would simplify it to:

write{
    "Row"(
         rowModifier.toCallParameter()
    ){
    "RadioButton"(
        "selected" withRawValue node.checked,
        "onCheckedChange".withEmptyLambda()
    )
}

(these params could be written in an interal interface, which then would be implemented by the KotlinWriterHelper)

We then could make the names values or getters inside an interface:

    val Text = "Text"
    val Box = "Box"
    val Button = "Button"
    val Row = "Row"
    val Checkbox = "Checkbox"
    val RadioButton = "RadioButton"
    val Card = "Card"
    val Image = "Image"
    val TextField = "TextField"

Such that we can write:

write{
    Row(
         rowModifier.toCallParameter()
    ){
    RadioButton(
        "selected" withRawValue node.checked,
        "onCheckedChange".withEmptyLambda()
    )
}

And we could add a Text function:

fun KotlinWriterHelper.Text(
        vararg params : CallParameter?,
        text: String? = null,
    ) = "Text"(
        text?.let {
            null withStringValue it
        },
        *params
    )
)

(I placed the varargs at the start, as that would keep compiling all the old code)

Check paste destination

When pasting translated Kotlin code with the plugin we should check whether the destination is valid (Is it a Kotlin file?).

Right now if you copy XML and paste it back into an XML then we paste Kotlin code back into the XML file. :)

recompose.parser.Parser$ParserException: Unknown drawable format: @color/white

Hi, tnx for plugin,
is it possible to fix this error:

recompose.parser.Parser$ParserException: Unknown size value: @dimen/common_margin
at recompose.parser.values.SizeKt.size(Size.kt:31)
at recompose.parser.values.PaddingKt.padding(Padding.kt:29)
at recompose.parser.xml.ViewKt.viewAttributes(View.kt:82)
at recompose.parser.xml.viewgroup.LinearLayoutKt.linearLayout(LinearLayout.kt:33)
at recompose.parser.xml.ViewKt.node(View.kt:51)
at recompose.parser.xml.ViewGroupKt.viewGroupAttributes(ViewGroup.kt:37)
at recompose.parser.xml.viewgroup.UnknownKt.unknown(Unknown.kt:31)
at recompose.parser.xml.ViewKt.node(View.kt:69)
at recompose.parser.xml.ViewGroupKt.viewGroupAttributes(ViewGroup.kt:37)
at recompose.parser.xml.viewgroup.ConstraintLayoutKt.constraintLayout(ConstraintLayout.kt:32)
at recompose.parser.xml.ViewKt.node(View.kt:65)
at recompose.parser.xml.ViewGroupKt.viewGroupAttributes(ViewGroup.kt:37)
at recompose.parser.xml.viewgroup.UnknownKt.unknown(Unknown.kt:31)
at recompose.parser.xml.ViewKt.node(View.kt:69)
at recompose.parser.xml.LayoutKt.layout(Layout.kt:37)
at recompose.parser.Parser.parse(Parser.kt:62)
at recompose.parser.Parser.parse(Parser.kt:53)
at recompose.parser.Parser.parse(Parser.kt:37)

Support incomplete XML input

Needed for #5.

The XMLPullParser implementation only likes well-formed XML documents. E.g. when having multiple root nodes:

start tag not allowed in epilog but got T (position: END_TAG seen ...    android:text="padding"\n    android:background="#ff0000" />\n\n<T... @9:3) 
org.xmlpull.v1.XmlPullParserException: start tag not allowed in epilog but got T (position: END_TAG seen ...    android:text="padding"\n    android:background="#ff0000" />\n\n<T... @9:3)

But we also want to be able to recompose partial XML documents (e.g. see #5).

We could:

  • Figure out if XMLPullParser supports a quirks mode that will continue parsing documents that are not strictly following the XML standard.
  • Manually fix up the XML before parsing it (e.g. introducing a root node that we will skip when parsing).
  • Use a different, more forgiving parser.
  • Write a minimal parser implementation that parses tags / attributes and doesn't require strict XML documents.

Handle unknown elements more gracefully

Currently we just bail if we hit an XML element that we do not recognize:
https://github.com/pocmo/recompose/blob/main/recompose-parser/src/main/kotlin/recompose/parser/xml/View.kt#L48

This is of course pretty annoying for someone using the plugin. Ideally we'd parse it as an UnknownNode and transform this in the Composer into a comment that will make the user aware that there's a piece missing here. With that the user still gets the benefit of translating everything else and can then fill in the blanks.

TextView: Add support for android:maxLines

android:maxLines
Makes the TextView be at most this many lines tall. When used on an editable text, the inputType attribute's value must be combined with the textMultiLine flag for the maxLines attribute to apply.

XML attribute:
https://developer.android.com/reference/android/widget/TextView?hl=en#attr_android:maxLines

Jetpack Compose:

Text(
   ...
   maxLines = Int = Int.MAX_VALUE
   ...
)

We are parsing TextView here:
https://github.com/pocmo/recompose/blob/main/recompose-parser/src/main/kotlin/recompose/parser/xml/view/TextView.kt

And create a TextViewNode from it:
https://github.com/pocmo/recompose/blob/main/recompose-ast/src/main/kotlin/recompose/ast/view/TextViewNode.kt

And we turn the node into Kotlin code here:
https://github.com/pocmo/recompose/blob/main/recompose-composer/src/main/kotlin/recompose/composer/visitor/ComposingVisitor.kt#L82-L94

To solve this issue:

  • Parse the attribute and add the value to TextViewNode. Probably a nullable Int? is needed.
  • Add it to the list of parameters in ComposingVisitor.
  • Add a test case to ParserTest and ComposerTest.

Add support for Checkbox

Add CI setup

The usual. Run build and tests on PRs and pushes.

Add support for app:layout_constraintHorizontal_chainStyle/layout_constraintVertical_chainStyle

  • Find chains in children: "A set of widgets are considered a chain if they are linked together via a bi-directional connection"
  • Identify chain heads: "The head is the left-most widget for horizontal chains, and the top-most widget for vertical chains."
  • Use layout_constraintHorizontal_chainStyle/layout_constraintVertical_chainStyle of head

Composable example:

ConstraintLayout {
    createHorizontalChain(eye_left, eye_right, chainStyle = ChainStyle.Packed)

    ...
}

Add support for FrameLayout gravity

#11 added basic support for FrameLayout. This issue is about adding support for gravity so that children can be positioned in the FrameLayout / Box.

Also see discussion about how to map between the different gravity representations here: #11 (comment) about

On paste: Add required imports

Currently when pasting with the plugin we only add the code. But we can also add the required imports to the list of import statements (e.g. import androidx.compose.foundation.Text).

From reading the code of the Kotlin language plugin, I was expecting something like this to work:

val import = KtPsiFactory(project, true).createImportDirective(
    ImportPath(FqName("androidx.compose.foundation.Text"), false)
)

val target = PsiDocumentManager.getInstance(project).getPsiFile(editor.document) as KtFile
target.importList!!.imports.add(import)

But I end up with the following error:

e: recompose/recompose-idea/src/main/kotlin/recompose/plugin/copypaste/RecomposeCopyPasteProcessor.kt: (101, 16): Cannot access 'com.intellij.psi.PsiClassOwner' which is a supertype of 'org.jetbrains.kotlin.psi.KtFile'. Check your module classpath for missing or conflicting dependencies
e: recompose/recompose-idea/src/main/kotlin/recompose/plugin/copypaste/RecomposeCopyPasteProcessor.kt: (101, 16): Cannot access 'com.intellij.psi.PsiModifiableCodeBlock' which is a supertype of 'org.jetbrains.kotlin.psi.KtFile'. Check your module classpath for missing or conflicting dependencies

I am not sure how to get access to those super types?

Show a confirmation dialog before pasting

When using the Kotlin language plugin and pasting Java code then a popup will be shown, asking for confirmation first. This makes sense here too, to avoid pasting Kotlin code when this is not desired.

Screenshot 2020-09-15 at 09 28 35

Edittext seems to close the parent tag

The parser seems to close a parent tag for EditText.

FrameLayout {
    EditText()
    View()
}

Will result in:

FrameLayout {
    EditText()
}
View()

Same occurs with LinearLayout.

Only recompose selection, honoring startOffsets/endOffsets

Currently when copying layout XML with the plugin and pasting it into code, we will translate the whole file and paste the whole translation. That's of course not right and we need to look at the actual selection using startOffsets and endOffsets.

Of course using the selection means we may get incomplete XML or multiple "root" nodes. I am not sure what XMLPullParser will do with that. We may need to "fix up" the XML before processing it. Or manually reduce the selection to a valid XML subset.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.