diff --git a/annotations/.gitignore b/annotations/.gitignore new file mode 100644 index 000000000..42afabfd2 --- /dev/null +++ b/annotations/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/annotations/build.gradle.kts b/annotations/build.gradle.kts new file mode 100644 index 000000000..b0ca5924d --- /dev/null +++ b/annotations/build.gradle.kts @@ -0,0 +1,9 @@ +plugins { + id("java-library") + id("org.jetbrains.kotlin.jvm") +} + +java { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 +} \ No newline at end of file diff --git a/annotations/src/main/java/ceui/lisa/annotations/ItemHolder.kt b/annotations/src/main/java/ceui/lisa/annotations/ItemHolder.kt new file mode 100644 index 000000000..6ba2ad0db --- /dev/null +++ b/annotations/src/main/java/ceui/lisa/annotations/ItemHolder.kt @@ -0,0 +1,8 @@ +package ceui.lisa.annotations + +import kotlin.reflect.KClass + +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.SOURCE) +@MustBeDocumented +annotation class ItemHolder(val itemHolderCls: KClass<*>) \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 473caeaf9..5dda72372 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -126,6 +126,9 @@ dependencies { api 'io.reactivex.rxjava2:rxjava:2.2.21' api 'io.reactivex.rxjava2:rxandroid:2.1.1' + implementation(project(":annotations")) // 1 + implementation(project(":processor")) // 2 + kapt(project(":processor")) // 3 implementation 'com.github.bumptech.glide:glide:4.14.2' kapt 'com.github.bumptech.glide:compiler:4.14.2' diff --git a/app/src/main/java/ceui/lisa/activities/MainActivity.java b/app/src/main/java/ceui/lisa/activities/MainActivity.java index 15b0d5734..280e7b5e4 100644 --- a/app/src/main/java/ceui/lisa/activities/MainActivity.java +++ b/app/src/main/java/ceui/lisa/activities/MainActivity.java @@ -41,7 +41,6 @@ import ceui.lisa.fragments.FragmentViewPager; import ceui.lisa.helper.DrawerLayoutHelper; import ceui.lisa.helper.NavigationLocationHelper; -import ceui.lisa.jetpack.NavActivity; import ceui.lisa.utils.Common; import ceui.lisa.utils.Dev; import ceui.lisa.utils.GlideUtil; diff --git a/app/src/main/java/ceui/loxia/SpaceHolder.kt b/app/src/main/java/ceui/loxia/SpaceHolder.kt index ae1c671a3..e3168418e 100644 --- a/app/src/main/java/ceui/loxia/SpaceHolder.kt +++ b/app/src/main/java/ceui/loxia/SpaceHolder.kt @@ -1,5 +1,6 @@ package ceui.loxia +import ceui.lisa.annotations.ItemHolder import ceui.lisa.databinding.CellSpaceBinding import ceui.refactor.ListItemHolder import ceui.refactor.ListItemViewHolder @@ -7,6 +8,7 @@ import ceui.refactor.ListItemViewHolder class SpaceHolder : ListItemHolder() { } +@ItemHolder(SpaceHolder::class) class SpaceViewHolder(private val bd: CellSpaceBinding) : ListItemViewHolder(bd) { override fun onBindViewHolder(holder: SpaceHolder, position: Int) { diff --git a/app/src/main/java/ceui/loxia/TextDescHolder.kt b/app/src/main/java/ceui/loxia/TextDescHolder.kt index c67515fd1..c5543a595 100644 --- a/app/src/main/java/ceui/loxia/TextDescHolder.kt +++ b/app/src/main/java/ceui/loxia/TextDescHolder.kt @@ -1,5 +1,6 @@ package ceui.loxia +import ceui.lisa.annotations.ItemHolder import ceui.lisa.databinding.CellSpaceBinding import ceui.lisa.databinding.CellTextDescBinding import ceui.refactor.ListItemHolder @@ -8,6 +9,7 @@ import ceui.refactor.ListItemViewHolder class TextDescHolder(val content: String) : ListItemHolder() { } +@ItemHolder(TextDescHolder::class) class TextDescViewHolder(private val bd: CellTextDescBinding) : ListItemViewHolder(bd) { override fun onBindViewHolder(holder: TextDescHolder, position: Int) { diff --git a/app/src/main/java/ceui/loxia/flag/FlagReasonRepository.kt b/app/src/main/java/ceui/loxia/flag/FlagReasonRepository.kt index 249c3393a..ca94cc4e1 100644 --- a/app/src/main/java/ceui/loxia/flag/FlagReasonRepository.kt +++ b/app/src/main/java/ceui/loxia/flag/FlagReasonRepository.kt @@ -1,11 +1,13 @@ package ceui.loxia.flag import ceui.lisa.R +import ceui.lisa.annotations.ItemHolder import ceui.lisa.databinding.CellFlagReasonBinding import ceui.loxia.RefreshState import ceui.loxia.Repository import ceui.loxia.findActionReceiverOrNull import ceui.loxia.findFragmentOrNull +import ceui.loxia.novel.NovelTextHolder import ceui.refactor.ListItemHolder import ceui.refactor.ListItemViewHolder import ceui.refactor.setOnClick @@ -52,6 +54,7 @@ class FlagReasonHolder(val id: Int, val content: String, val key: String) : List } } +@ItemHolder(FlagReasonHolder::class) class FlagReasonViewHolder(binding: CellFlagReasonBinding) : ListItemViewHolder(binding) { diff --git a/app/src/main/java/ceui/loxia/novel/NovelTextHolder.kt b/app/src/main/java/ceui/loxia/novel/NovelTextHolder.kt index a622e50ae..fcca4f033 100644 --- a/app/src/main/java/ceui/loxia/novel/NovelTextHolder.kt +++ b/app/src/main/java/ceui/loxia/novel/NovelTextHolder.kt @@ -1,6 +1,7 @@ package ceui.loxia.novel import ceui.lisa.activities.Shaft +import ceui.lisa.annotations.ItemHolder import ceui.lisa.databinding.CellNovelImageBinding import ceui.lisa.databinding.CellNovelTextBinding import ceui.lisa.models.NovelImages @@ -8,6 +9,7 @@ import ceui.lisa.models.WebNovel import ceui.lisa.utils.Common import ceui.lisa.utils.GlideUtil import ceui.lisa.utils.PixivOperate +import ceui.loxia.SpaceHolder import ceui.refactor.ListItemHolder import ceui.refactor.ListItemViewHolder import ceui.refactor.setOnClick @@ -19,6 +21,7 @@ class NovelTextHolder(val text: String, val textColor: Int) : ListItemHolder() { } +@ItemHolder(NovelTextHolder::class) class NovelTextViewHolder(private val bd: CellNovelTextBinding) : ListItemViewHolder(bd) { override fun onBindViewHolder(holder: NovelTextHolder, position: Int) { @@ -42,6 +45,7 @@ class NovelImageHolder( } } +@ItemHolder(NovelImageHolder::class) class NovelImageViewHolder(private val bd: CellNovelImageBinding) : ListItemViewHolder(bd) { override fun onBindViewHolder(holder: NovelImageHolder, position: Int) { diff --git a/app/src/main/java/ceui/refactor/CommonAdapter.kt b/app/src/main/java/ceui/refactor/CommonAdapter.kt index 4146d25c7..0094fda82 100644 --- a/app/src/main/java/ceui/refactor/CommonAdapter.kt +++ b/app/src/main/java/ceui/refactor/CommonAdapter.kt @@ -9,9 +9,12 @@ import androidx.lifecycle.LifecycleOwner import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView +import androidx.viewbinding.ViewBinding import ceui.lisa.databinding.CellNoneBinding import ceui.lisa.databinding.FragmentItemAaaaBinding import ceui.lisa.databinding.FragmentItemBbbbBinding +import ceui.loxia.flag.viewholdermap.ViewHolderFactory +import java.lang.RuntimeException val listItemHolderDiffUtil = object : DiffUtil.ItemCallback() { @@ -32,22 +35,29 @@ val listItemHolderDiffUtil = object : class CommonAdapter(private val lifecycleOwner: LifecycleOwner) : - ListAdapter>( + ListAdapter>( listItemHolderDiffUtil ) { override fun onCreateViewHolder( parent: ViewGroup, viewType: Int - ): ListItemViewHolder { - return ViewHolderMapping.buildViewHolder(parent, viewType) + ): ListItemViewHolder { + val autoGeneratedBuilder = ViewHolderFactory.VIEW_HOLDER_MAP[viewType] + if (autoGeneratedBuilder == null) { + throw RuntimeException("Add ItemHolder annotation") + } else { + return autoGeneratedBuilder.invoke(parent) as ListItemViewHolder + } } override fun onBindViewHolder( - holder: ListItemViewHolder, + holder: ListItemViewHolder, position: Int ) { val item = getItem(position) - holder.binding.lifecycleOwner = lifecycleOwner + if (holder.binding is ViewDataBinding) { + holder.binding.lifecycleOwner = lifecycleOwner + } holder.binding.root.setOnClick { item.retrieveListener()(it) } @@ -87,7 +97,7 @@ open class ListItemHolder { } } -open class ListItemViewHolder(val binding: Binding) : +open class ListItemViewHolder(val binding: Binding) : RecyclerView.ViewHolder(binding.root) { open fun onBindViewHolder(holder: T, position: Int) { diff --git a/app/src/main/java/ceui/refactor/ViewHolderMapping.kt b/app/src/main/java/ceui/refactor/ViewHolderMapping.kt deleted file mode 100644 index b07faf93b..000000000 --- a/app/src/main/java/ceui/refactor/ViewHolderMapping.kt +++ /dev/null @@ -1,80 +0,0 @@ -package ceui.refactor - -import android.view.LayoutInflater -import android.view.ViewGroup -import androidx.databinding.ViewDataBinding -import ceui.lisa.databinding.CellFlagReasonBinding -import ceui.lisa.databinding.CellNoneBinding -import ceui.lisa.databinding.CellNovelImageBinding -import ceui.lisa.databinding.CellNovelTextBinding -import ceui.lisa.databinding.CellSpaceBinding -import ceui.lisa.databinding.CellTextDescBinding -import ceui.loxia.SpaceHolder -import ceui.loxia.SpaceViewHolder -import ceui.loxia.TextDescHolder -import ceui.loxia.TextDescViewHolder -import ceui.loxia.flag.FlagReasonHolder -import ceui.loxia.flag.FlagReasonViewHolder -import ceui.loxia.novel.NovelImageHolder -import ceui.loxia.novel.NovelImageViewHolder -import ceui.loxia.novel.NovelTextHolder -import ceui.loxia.novel.NovelTextViewHolder - -object ViewHolderMapping { - - @Suppress("UNCHECKED_CAST") - fun buildViewHolder( - parent: ViewGroup, - itemType: Int - ): ListItemViewHolder { - if (itemType == FlagReasonHolder::class.java.hashCode()) { - return FlagReasonViewHolder( - CellFlagReasonBinding.inflate( - LayoutInflater.from(parent.context), - parent, - false - ) - ) as ListItemViewHolder - } else if (itemType == NovelTextHolder::class.java.hashCode()) { - return NovelTextViewHolder( - CellNovelTextBinding.inflate( - LayoutInflater.from(parent.context), - parent, - false - ) - ) as ListItemViewHolder - } else if (itemType == NovelImageHolder::class.java.hashCode()) { - return NovelImageViewHolder( - CellNovelImageBinding.inflate( - LayoutInflater.from(parent.context), - parent, - false - ) - ) as ListItemViewHolder - } else if (itemType == SpaceHolder::class.java.hashCode()) { - return SpaceViewHolder( - CellSpaceBinding.inflate( - LayoutInflater.from(parent.context), - parent, - false - ) - ) as ListItemViewHolder - } else if (itemType == TextDescHolder::class.java.hashCode()) { - return TextDescViewHolder( - CellTextDescBinding.inflate( - LayoutInflater.from(parent.context), - parent, - false - ) - ) as ListItemViewHolder - } else { - return ListItemViewHolder( - CellNoneBinding.inflate( - LayoutInflater.from(parent.context), - parent, - false - ) - ) - } - } -} \ No newline at end of file diff --git a/build.gradle b/build.gradle index 8707e54eb..1703b2065 100644 --- a/build.gradle +++ b/build.gradle @@ -26,6 +26,7 @@ buildscript { plugins { id "com.github.ben-manes.versions" version "0.41.0" id 'org.jetbrains.kotlin.android' version '1.8.0' apply false + id 'org.jetbrains.kotlin.jvm' version '1.8.0' apply false } allprojects { diff --git a/processor/.gitignore b/processor/.gitignore new file mode 100644 index 000000000..42afabfd2 --- /dev/null +++ b/processor/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/processor/build.gradle.kts b/processor/build.gradle.kts new file mode 100644 index 000000000..a301363ed --- /dev/null +++ b/processor/build.gradle.kts @@ -0,0 +1,20 @@ +plugins { + id("java-library") + id("org.jetbrains.kotlin.jvm") + id("kotlin-kapt") +} + +java { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 +} + +dependencies { + kapt(project(":annotations")) + compileOnly(project(":annotations")) + + kapt ("com.google.auto.service:auto-service:1.0") + implementation ("com.google.auto.service:auto-service:1.0") + + +} \ No newline at end of file diff --git a/processor/src/main/java/ceui/lisa/processor/AnnotationProcessor.kt b/processor/src/main/java/ceui/lisa/processor/AnnotationProcessor.kt new file mode 100644 index 000000000..d73299f2c --- /dev/null +++ b/processor/src/main/java/ceui/lisa/processor/AnnotationProcessor.kt @@ -0,0 +1,145 @@ +package ceui.lisa.processor + +import ceui.lisa.annotations.ItemHolder +import com.google.auto.service.AutoService +import java.io.File +import java.lang.Exception +import javax.annotation.processing.AbstractProcessor +import javax.annotation.processing.Processor +import javax.annotation.processing.RoundEnvironment +import javax.annotation.processing.SupportedOptions +import javax.annotation.processing.SupportedSourceVersion +import javax.lang.model.SourceVersion +import javax.lang.model.element.ElementKind +import javax.lang.model.element.ExecutableElement +import javax.lang.model.element.TypeElement +import javax.lang.model.type.MirroredTypeException +import kotlin.reflect.KClass + +fun getClassName(clsProvider: ()-> KClass<*>): String { + return try { + clsProvider().toString() + } catch (mte: MirroredTypeException) { + mte.typeMirror.toString() + } catch (ex: Exception) { + throw ex + } +} + +@AutoService(Processor::class) // For registering the service +@SupportedSourceVersion(SourceVersion.RELEASE_17) // to support Java 8 +@SupportedOptions(FileGenerator.KAPT_KOTLIN_GENERATED_OPTION_NAME) +class FileGenerator : AbstractProcessor() { + + private val logger by lazy { ProcessorLogger(processingEnv) } + + companion object { + const val KAPT_KOTLIN_GENERATED_OPTION_NAME = "kapt.kotlin.generated" + } + + override fun getSupportedAnnotationTypes(): MutableSet { + return mutableSetOf(ItemHolder::class.java.name) + } + + data class HolderEntry( + val existingPackage: String, + val itemHolder: String, + val binding: String, + val bindingFullname: String, + val viewHolder: String, + ) + + override fun process(set: MutableSet?, roundEnvironment: RoundEnvironment?): Boolean { + if (roundEnvironment == null) { + return true + } + + val holderElements = (roundEnvironment.getElementsAnnotatedWith(ItemHolder::class.java) ?: setOf()).toList() + if (holderElements.isEmpty()) { + return true + } + + val kaptKotlinGeneratedDir = processingEnv.options[KAPT_KOTLIN_GENERATED_OPTION_NAME] ?: return true + + val file = File(kaptKotlinGeneratedDir, "ViewHolderMap.kt") + + val content = StringBuilder() + + val packages = (holderElements).map { + processingEnv.elementUtils.getPackageOf(it).toString() + ".*" + } + + val packageName = (packages.first().split(".").subList(0, 3) + listOf("viewholdermap")).joinToString(".") + + val holderEntries = holderElements.map { + val itemHolderFullName = getClassName { + it.getAnnotation(ItemHolder::class.java).itemHolderCls + } + val itemHolderName = itemHolderFullName.split(".").last() + + val holderPackage = itemHolderFullName.replace(itemHolderName, "") + + val ctor = it.enclosedElements.first { it.kind == ElementKind.CONSTRUCTOR } as ExecutableElement + + val bindingType = ctor.parameters[0].asType() + val bindingClsName = bindingType.toString().split(".").last() + val viewHolderName = it.simpleName + + //print("FUCK Processing ${ctor.parameters.size} \n\n") + HolderEntry(holderPackage, itemHolderName, bindingClsName, bindingType.toString(), viewHolderName.toString()) + } + + val buildMapEntries = holderEntries.map { + " ${it.itemHolder}::class.java.hashCode() to ViewHolderFactory::${it.viewHolder}Builder" + } + + content.append("package ").append(packageName) + content.append("\n") + content.append("\n") + content.append("import android.view.LayoutInflater\n") + content.append("import android.view.ViewGroup\n") + content.append("import android.view.View\n") + content.append("import androidx.viewbinding.ViewBinding\n") + content.append("import ceui.refactor.ListItemHolder\n") + content.append("import ceui.refactor.ListItemViewHolder\n") + holderEntries.forEach { + content.append("import ${ it.existingPackage }${it.viewHolder}\n") + } + holderEntries.forEach { + content.append("import ${ it.existingPackage }${it.itemHolder}\n") + } + content.append(holderEntries.filter { it.binding.endsWith("Binding") }.map { "import ${it.bindingFullname}" }.distinct().joinToString("\n")) + content.append("\n") + content.append("\n") + content.append("object ViewHolderFactory {\n") + holderEntries.forEach { + content.append("\n") + logger.n("${it}") + content.append(" private fun ${it.viewHolder}Builder(parent: ViewGroup): ListItemViewHolder {") + content.append("\n") + content.append(" val binding = ${it.binding}.inflate(\n" + + " LayoutInflater.from(parent.context),\n" + + " parent,\n" + + " false\n" + + " )") + content.append("\n") + content.append(" return ${it.viewHolder}(binding)") + content.append("\n") + content.append(" }") + content.append("\n") + } + + content.append("\n") + content.append(" val VIEW_HOLDER_MAP = mapOf(\n " + buildMapEntries.joinToString(",\n ") + " \n )\n\n\n") + + + content.append("}") + content.append("\n") + content.append("\n") + + + file.writeText(content.toString()) + + return true + } +} \ No newline at end of file diff --git a/processor/src/main/java/ceui/lisa/processor/ProcessorLogger.kt b/processor/src/main/java/ceui/lisa/processor/ProcessorLogger.kt new file mode 100644 index 000000000..7834b880f --- /dev/null +++ b/processor/src/main/java/ceui/lisa/processor/ProcessorLogger.kt @@ -0,0 +1,25 @@ +package ceui.lisa.processor + +import javax.annotation.processing.ProcessingEnvironment +import javax.lang.model.element.Element +import javax.tools.Diagnostic + +class ProcessorLogger(private val env: ProcessingEnvironment) { + + fun n(message: String, element: Element? = null) { + print(Diagnostic.Kind.NOTE, message, element) // 1 + } + + fun w(message: String, element: Element? = null) { + print(Diagnostic.Kind.WARNING, message, element) // 1 + } + + fun e(message: String, element: Element? = null) { + print(Diagnostic.Kind.ERROR, message, element) // 1 + } + + private fun print(kind: Diagnostic.Kind, message: String, element: Element?) { + print("\n") + env.messager.printMessage(kind, message, element) // 2 + } +} diff --git a/settings.gradle b/settings.gradle index 6347afc8f..ffc021e14 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1,3 @@ include ':app', ':models', ':progressmanager', ':flowlayout-lib' +include ':annotations' +include ':processor'