begin work on evil bytecode modification stuff

master
dfsek 1 year ago
parent e900aa6046
commit 35d5410dba
  1. 2
      app/build.gradle.kts
  2. 8
      app/src/main/java/com/dfsek/dfchat/ui/RoomActivity.kt
  3. 2
      app/src/main/java/com/dfsek/dfchat/util/compose_fixes/FixedDefaultFlingBehavior.kt
  4. 183
      app/src/main/java/com/dfsek/dfchat/util/compose_fixes/FixedTextField.kt
  5. 3
      app/src/main/java/com/dfsek/dfchat/util/compose_fixes/README.md
  6. 6
      build.gradle.kts
  7. 27
      buildSrc/build.gradle.kts
  8. 61
      buildSrc/src/main/kotlin/ComposeFixingClassVisitorFactory.kt
  9. 24
      buildSrc/src/main/kotlin/TransformPlugin.kt

@ -1,6 +1,7 @@
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("com.dfsek.dfchat.compose-fixer")
}
android {
@ -27,7 +28,6 @@ android {
buildTypes {
release {
isMinifyEnabled = true
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
}
}

@ -47,25 +47,19 @@ import coil.request.ImageRequest
import com.dfsek.dfchat.AppState
import com.dfsek.dfchat.state.ChatRoomState
import com.dfsek.dfchat.util.*
import com.dfsek.dfchat.util.FixedDefaultFlingBehavior.Companion.fixedFlingBehavior
import com.dfsek.dfchat.util.compose_fixes.FixedDefaultFlingBehavior.Companion.fixedFlingBehavior
import com.dfsek.dfchat.util.vector.multipicker.toContentAttachmentData
import im.vector.lib.multipicker.MultiPicker
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.session.content.ContentAttachmentData
import org.matrix.android.sdk.api.session.events.model.getMsgType
import org.matrix.android.sdk.api.session.events.model.getRelationContent
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.getRoom
import org.matrix.android.sdk.api.session.room.getTimelineEvent
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
import org.matrix.android.sdk.api.session.room.model.message.MessageImageContent
import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent
import org.matrix.android.sdk.api.session.room.model.message.MessageType
import org.matrix.android.sdk.api.session.room.sender.SenderInfo
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import org.matrix.android.sdk.api.session.room.timeline.getLastEditNewContent
import org.matrix.android.sdk.api.session.room.timeline.isReply
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream

@ -1,4 +1,4 @@
package com.dfsek.dfchat.util
package com.dfsek.dfchat.util.compose_fixes
import androidx.compose.animation.core.AnimationState
import androidx.compose.animation.core.DecayAnimationSpec

@ -0,0 +1,183 @@
package com.dfsek.dfchat.util.compose_fixes
import androidx.compose.foundation.background
import androidx.compose.foundation.interaction.Interaction
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.shape.ZeroCornerSize
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.*
import androidx.compose.material.TextFieldDefaults.indicatorLine
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.takeOrElse
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.graphics.SolidColor
/**
* <a href="https://material.io/components/text-fields#filled-text-field" class="external" target="_blank">Material Design filled text field</a>.
*
* Filled text fields have more visual emphasis than outlined text fields, making them stand out
* when surrounded by other content and components.
*
* ![Filled text field image](https://developer.android.com/images/reference/androidx/compose/material/filled-text-field.png)
*
* If you are looking for an outlined version, see [OutlinedTextField].
*
* A simple single line text field looks like:
*
* @sample androidx.compose.material.samples.SimpleTextFieldSample
*
* You may provide a placeholder:
*
* @sample androidx.compose.material.samples.TextFieldWithPlaceholder
*
* You can also provide leading and trailing icons:
*
* @sample androidx.compose.material.samples.TextFieldWithIcons
*
* To handle the error input state, use [isError] parameter:
*
* @sample androidx.compose.material.samples.TextFieldWithErrorState
*
* Additionally, you may provide additional message at the bottom:
*
* @sample androidx.compose.material.samples.TextFieldWithHelperMessage
*
* Password text field example:
*
* @sample androidx.compose.material.samples.PasswordTextField
*
* Hiding a software keyboard on IME action performed:
*
* @sample androidx.compose.material.samples.TextFieldWithHideKeyboardOnImeAction
*
* If apart from input text change you also want to observe the cursor location, selection range,
* or IME composition use the TextField overload with the [TextFieldValue] parameter instead.
*
* @param value the input text to be shown in the text field
* @param onValueChange the callback that is triggered when the input service updates the text. An
* updated text comes as a parameter of the callback
* @param modifier a [Modifier] for this text field
* @param enabled controls the enabled state of the [TextField]. When `false`, the text field will
* be neither editable nor focusable, the input of the text field will not be selectable,
* visually text field will appear in the disabled UI state
* @param readOnly controls the editable state of the [TextField]. When `true`, the text
* field can not be modified, however, a user can focus it and copy text from it. Read-only text
* fields are usually used to display pre-filled forms that user can not edit
* @param textStyle the style to be applied to the input text. The default [textStyle] uses the
* [LocalTextStyle] defined by the theme
* @param label the optional label to be displayed inside the text field container. The default
* text style for internal [Text] is [Typography.caption] when the text field is in focus and
* [Typography.subtitle1] when the text field is not in focus
* @param placeholder the optional placeholder to be displayed when the text field is in focus and
* the input text is empty. The default text style for internal [Text] is [Typography.subtitle1]
* @param leadingIcon the optional leading icon to be displayed at the beginning of the text field
* container
* @param trailingIcon the optional trailing icon to be displayed at the end of the text field
* container
* @param isError indicates if the text field's current value is in error. If set to true, the
* label, bottom indicator and trailing icon by default will be displayed in error color
* @param visualTransformation transforms the visual representation of the input [value]
* For example, you can use
* [PasswordVisualTransformation][androidx.compose.ui.text.input.PasswordVisualTransformation] to
* create a password text field. By default no visual transformation is applied
* @param keyboardOptions software keyboard options that contains configuration such as
* [KeyboardType] and [ImeAction].
* @param keyboardActions when the input service emits an IME action, the corresponding callback
* is called. Note that this IME action may be different from what you specified in
* [KeyboardOptions.imeAction].
* @param singleLine when set to true, this text field becomes a single horizontally scrolling
* text field instead of wrapping onto multiple lines. The keyboard will be informed to not show
* the return key as the [ImeAction]. Note that [maxLines] parameter will be ignored as the
* maxLines attribute will be automatically set to 1.
* @param maxLines the maximum height in terms of maximum number of visible lines. Should be
* equal or greater than 1. Note that this parameter will be ignored and instead maxLines will be
* set to 1 if [singleLine] is set to true.
* @param interactionSource the [MutableInteractionSource] representing the stream of
* [Interaction]s for this TextField. You can create and pass in your own remembered
* [MutableInteractionSource] if you want to observe [Interaction]s and customize the
* appearance / behavior of this TextField in different [Interaction]s.
* @param shape the shape of the text field's container
* @param colors [TextFieldColors] that will be used to resolve color of the text, content
* (including label, placeholder, leading and trailing icons, indicator line) and background for
* this text field in different states. See [TextFieldDefaults.textFieldColors]
*/
@Composable
fun TextField(
value: String,
onValueChange: (String) -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
readOnly: Boolean = false,
textStyle: TextStyle = LocalTextStyle.current,
label: @Composable (() -> Unit)? = null,
placeholder: @Composable (() -> Unit)? = null,
leadingIcon: @Composable (() -> Unit)? = null,
trailingIcon: @Composable (() -> Unit)? = null,
isError: Boolean = false,
visualTransformation: VisualTransformation = VisualTransformation.None,
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
keyboardActions: KeyboardActions = KeyboardActions(),
singleLine: Boolean = false,
maxLines: Int = Int.MAX_VALUE,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
shape: Shape =
MaterialTheme.shapes.small.copy(bottomEnd = ZeroCornerSize, bottomStart = ZeroCornerSize),
colors: TextFieldColors = TextFieldDefaults.textFieldColors()
) {
// If color is not provided via the text style, use content color as a default
val textColor = textStyle.color.takeOrElse {
colors.textColor(enabled).value
}
val mergedTextStyle = textStyle.merge(TextStyle(color = textColor))
@OptIn(ExperimentalMaterialApi::class)
(BasicTextField(
value = value,
modifier = modifier
.background(colors.backgroundColor(enabled).value, shape)
.indicatorLine(enabled, isError, interactionSource, colors)
.defaultMinSize(
minWidth = TextFieldDefaults.MinWidth,
minHeight = TextFieldDefaults.MinHeight
),
onValueChange = onValueChange,
enabled = enabled,
readOnly = readOnly,
textStyle = mergedTextStyle,
cursorBrush = SolidColor(colors.cursorColor(isError).value),
visualTransformation = visualTransformation,
keyboardOptions = keyboardOptions,
keyboardActions = keyboardActions,
interactionSource = interactionSource,
singleLine = singleLine,
maxLines = maxLines,
decorationBox = @Composable { innerTextField ->
// places leading icon, text field with label and placeholder, trailing icon
TextFieldDefaults.TextFieldDecorationBox(
value = value,
visualTransformation = visualTransformation,
innerTextField = innerTextField,
placeholder = placeholder,
label = label,
leadingIcon = leadingIcon,
trailingIcon = trailingIcon,
singleLine = singleLine,
enabled = enabled,
isError = isError,
interactionSource = interactionSource,
colors = colors
)
}
))
}

@ -0,0 +1,3 @@
# Compose Fixes
Fixes to goofy Jetpack Compose bugs, mainly involving animations.

@ -1,6 +1,6 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
id("com.android.application") version "7.3.0" apply false
id("com.android.library") version "7.3.0" apply false
id("org.jetbrains.kotlin.android") version "1.7.20" apply false
id("com.android.application") apply false
id("com.android.library") apply false
id("org.jetbrains.kotlin.android") apply false
}

@ -0,0 +1,27 @@
plugins {
`kotlin-dsl`
kotlin("jvm") version embeddedKotlinVersion
}
repositories {
mavenCentral()
gradlePluginPortal()
google()
}
dependencies {
implementation("org.ow2.asm:asm:9.3")
implementation("org.ow2.asm:asm-commons:9.3")
implementation("com.android.tools.build:gradle:7.3.0")
api(kotlin("gradle-plugin:1.7.20"))
}
gradlePlugin {
plugins {
register("composeFixer") {
id = "com.dfsek.dfchat.compose-fixer"
implementationClass = "TransformPlugin"
}
}
}

@ -0,0 +1,61 @@
import com.android.build.api.instrumentation.AsmClassVisitorFactory
import com.android.build.api.instrumentation.ClassContext
import com.android.build.api.instrumentation.ClassData
import com.android.build.api.instrumentation.InstrumentationParameters
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Optional
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.MethodVisitor
import org.objectweb.asm.Opcodes
abstract class ComposeFixingClassVisitorFactory :
AsmClassVisitorFactory<ComposeFixingClassVisitorFactory.ComposeFixingParams> {
interface ComposeFixingParams : InstrumentationParameters {
@get:Input
@get:Optional
val invalidate: Property<Long>
}
override fun createClassVisitor(
classContext: ClassContext,
nextClassVisitor: ClassVisitor
): ClassVisitor {
println("Instrumenting class ${classContext.currentClassData.className}")
return object : ClassVisitor(Opcodes.ASM9, nextClassVisitor) {
override fun visitMethod(
access: Int,
name: String?,
descriptor: String?,
signature: String?,
exceptions: Array<out String>?
): MethodVisitor {
val s = super.visitMethod(access, name, descriptor, signature, exceptions)
/*if(name == "invokeSuspend") {
println("MODIFYING METHOD: $name")
return object : MethodVisitor(Opcodes.ASM9, s) {
override fun visitInsn(opcode: Int) {
if (opcode == Opcodes.FCONST_0) {
println("REPLACING FCONST_0 WITH 1")
s.visitInsn(Opcodes.FCONST_1)
} else if (opcode == Opcodes.FCONST_1) {
println("REPLACING FCONST_1 WITH 0")
s.visitInsn(Opcodes.FCONST_0)
} else {
s.visitInsn(opcode)
}
}
}
}
else */return s
}
}
}
override fun isInstrumentable(classData: ClassData): Boolean {
return classData.className.endsWith("NamelessClass_1")
}
}

@ -0,0 +1,24 @@
import com.android.build.api.instrumentation.InstrumentationScope
import com.android.build.api.variant.AndroidComponentsExtension
import com.android.build.gradle.internal.utils.setDisallowChanges
import org.gradle.api.Plugin
import org.gradle.api.Project
// We target and transform androidx/compose/foundation/text/TextFieldCursorKt$cursorAnimationSpec$1
class TransformPlugin : Plugin<Project> {
override fun apply(target: Project) {
target.pluginManager.withPlugin("com.android.application") {
val androidComponentsExtension =
target.extensions.getByType(AndroidComponentsExtension::class.java)
androidComponentsExtension.onVariants { variant ->
variant.instrumentation.transformClassesWith(
ComposeFixingClassVisitorFactory::class.java,
InstrumentationScope.ALL
) { params ->
params.invalidate.setDisallowChanges(System.currentTimeMillis())
}
}
}
}
}
Loading…
Cancel
Save