diff --git a/.idea/caches/build_file_checksums.ser b/.idea/caches/build_file_checksums.ser new file mode 100644 index 0000000..ed13fbf Binary files /dev/null and b/.idea/caches/build_file_checksums.ser differ diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..97626ba --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/annotation/.gitignore b/annotation/.gitignore new file mode 100644 index 0000000..ed53c05 --- /dev/null +++ b/annotation/.gitignore @@ -0,0 +1,29 @@ +/build +# Java class files +*.class + +# Generated files +bin/ +gen/ +out/ + +# Gradle files +.gradle/ +build/ + +# Intellij +*.iml +.idea/workspace.xml +.idea/tasks.xml +.idea/gradle.xml +.idea/dictionaries +.idea/libraries + +# Freeline +freeline.py +freeline/ +freeline_project_description.json +.idea/misc.xml +.idea/runConfigurations.xml +.idea/vcs.xml +.idea/modules.xml \ No newline at end of file diff --git a/annotation/build.gradle b/annotation/build.gradle new file mode 100644 index 0000000..3e82836 --- /dev/null +++ b/annotation/build.gradle @@ -0,0 +1,10 @@ +apply plugin: 'java-library' +apply plugin: 'kotlin' + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" +} + +sourceCompatibility = "7" +targetCompatibility = "7" diff --git a/annotation/src/main/java/com/umairjavid/kombind/anontation/SimpleKombindAdapter.kt b/annotation/src/main/java/com/umairjavid/kombind/anontation/SimpleKombindAdapter.kt new file mode 100644 index 0000000..5cfd3eb --- /dev/null +++ b/annotation/src/main/java/com/umairjavid/kombind/anontation/SimpleKombindAdapter.kt @@ -0,0 +1,5 @@ +package com.umairjavid.kombind.anontation + +@Retention(AnnotationRetention.SOURCE) +@Target(AnnotationTarget.FIELD) +annotation class SimpleKombindAdapter(val layoutRes: Int = 0) diff --git a/app/build.gradle b/app/build.gradle index abfed1a..2a9aaee 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -36,6 +36,9 @@ android { proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } + kapt { + generateStubs = true + } dataBinding { enabled = true } @@ -44,6 +47,9 @@ android { dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') implementation project(':kombind') + compileOnly project(':annotation') + kapt project(':processor') + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation "androidx.legacy:legacy-support-v4:$androidx_legacy_support_library_version" implementation "androidx.appcompat:appcompat:$androidx_appcompat" diff --git a/app/src/main/java/com/umairjavid/kombindsample/ui/main/MainActivity.kt b/app/src/main/java/com/umairjavid/kombindsample/ui/main/MainActivity.kt index a35ad05..c8999bb 100644 --- a/app/src/main/java/com/umairjavid/kombindsample/ui/main/MainActivity.kt +++ b/app/src/main/java/com/umairjavid/kombindsample/ui/main/MainActivity.kt @@ -4,8 +4,10 @@ import android.os.Bundle import androidx.recyclerview.widget.LinearLayoutManager import com.umairjavid.kombind.ui.KombindActivity import com.umairjavid.kombindsample.R +import com.umairjavid.kombindsample.model.SimpleHeader import com.umairjavid.kombindsample.repo.SimpleItemRepository -import kotlinx.android.synthetic.main.activity_main.* +import kotlinx.android.synthetic.main.activity_main.simple_item_list +import main.KombindAdapter_MainState_Items class MainActivity : KombindActivity() { override val viewModelClass = MainViewModel::class.java @@ -21,8 +23,12 @@ class MainActivity : KombindActivity() { private fun setupSimpleItemList() { simple_item_list.apply { layoutManager = LinearLayoutManager(this@MainActivity) - adapter = SimpleItemAdapter(viewModel.state.items, viewModel) - .registerObserver(this@MainActivity) + adapter = object :KombindAdapter_MainState_Items(viewModel.state.items, viewModel) { + override fun getLayout(position: Int) = when(items[position]) { + is SimpleHeader -> R.layout.item_simpleheader + else -> R.layout.item_simpleitem + } + } .registerObserver(this@MainActivity) } } } diff --git a/app/src/main/java/com/umairjavid/kombindsample/ui/main/MainState.kt b/app/src/main/java/com/umairjavid/kombindsample/ui/main/MainState.kt index 1c90a48..d65a6a3 100644 --- a/app/src/main/java/com/umairjavid/kombindsample/ui/main/MainState.kt +++ b/app/src/main/java/com/umairjavid/kombindsample/ui/main/MainState.kt @@ -1,10 +1,12 @@ package com.umairjavid.kombindsample.ui.main +import com.umairjavid.kombind.anontation.SimpleKombindAdapter import com.umairjavid.kombind.model.MutableLiveArrayList import com.umairjavid.kombind.model.MutableLiveField class MainState { val title = MutableLiveField("Items Loaded!") + @SimpleKombindAdapter val items = MutableLiveArrayList() operator fun invoke(func: MainState.() -> Unit) = func() diff --git a/app/src/main/java/com/umairjavid/kombindsample/ui/main/MainViewModel.kt b/app/src/main/java/com/umairjavid/kombindsample/ui/main/MainViewModel.kt index 9b92ea8..ecd33b8 100644 --- a/app/src/main/java/com/umairjavid/kombindsample/ui/main/MainViewModel.kt +++ b/app/src/main/java/com/umairjavid/kombindsample/ui/main/MainViewModel.kt @@ -11,8 +11,7 @@ import com.umairjavid.kombindsample.repo.SimpleItemRepository class MainViewModel(application: Application, private val simpleItemRepository: SimpleItemRepository) : KombindViewModel(application), SimpleItemAdapter.HeaderActionHandler, SimpleItemAdapter.ItemActionHandler { - val state = MainState() - + val state = MainState() init { loadItems() } diff --git a/build.gradle b/build.gradle index c6004e0..98f7055 100644 --- a/build.gradle +++ b/build.gradle @@ -1,8 +1,8 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.android_plugin_version = '3.2.1' - ext.kotlin_version = '1.3.0' + ext.android_plugin_version = '3.4.0' + ext.kotlin_version = '1.3.30' repositories { google() jcenter() diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 5e54eca..277ee54 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Sat Nov 10 09:57:43 EST 2018 +#Thu Apr 18 00:03:04 EDT 2019 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip diff --git a/kombind/build.gradle b/kombind/build.gradle index afd9684..1c8e559 100644 --- a/kombind/build.gradle +++ b/kombind/build.gradle @@ -8,7 +8,6 @@ group = 'com.github.ujavid' project.ext { compileSdkVersion = 28 - buildToolsVersion = '28.0.3' minSdkVersion = 14 targetSdkVersion = 28 versionCode = 1 @@ -16,7 +15,7 @@ project.ext { support_library_version = '27.1.1' androidx_appcompat = '1.0.2' - android_material = '1.1.0-alpha01' + android_material = '1.1.0-alpha05' lifecycle_version = '2.0.0' junit_version = '4.12' test_runner_version = '1.1.0' @@ -25,7 +24,6 @@ project.ext { android { compileSdkVersion project.ext.compileSdkVersion - buildToolsVersion project.ext.buildToolsVersion defaultConfig { minSdkVersion project.ext.minSdkVersion targetSdkVersion project.ext.targetSdkVersion @@ -54,7 +52,7 @@ dependencies { task sourcesJar(type: Jar) { from android.sourceSets.main.java.srcDirs - classifier 'sources' + archiveClassifier.set("sources") } artifacts { diff --git a/kombind/src/main/java/com/umairjavid/kombind/ui/DialogFragmentBuilder.kt b/kombind/src/main/java/com/umairjavid/kombind/ui/DialogFragmentBuilder.kt index a6dc133..a67ac2a 100644 --- a/kombind/src/main/java/com/umairjavid/kombind/ui/DialogFragmentBuilder.kt +++ b/kombind/src/main/java/com/umairjavid/kombind/ui/DialogFragmentBuilder.kt @@ -10,7 +10,7 @@ open class DialogFragmentBuilder(private val clazz: Class val fragment = clazz.newInstance() fragment.arguments = arguments return fragment - } catch (e: java.lang.InstantiationException) { + } catch (e: InstantiationException) { throw RuntimeException(e) } catch (e: IllegalAccessException) { throw RuntimeException(e) @@ -27,7 +27,7 @@ open class DialogFragmentBuilder(private val clazz: Class val fragment = fragmentManager.findFragmentByTag(fragmentTag) return if (fragment != null) { - clazz.cast(fragment) + clazz.cast(fragment)!! } else { forceShow(fragmentManager) } diff --git a/kombind/src/main/java/com/umairjavid/kombind/ui/KombindDialogFragment.kt b/kombind/src/main/java/com/umairjavid/kombind/ui/KombindDialogFragment.kt index 811bb90..41d6957 100644 --- a/kombind/src/main/java/com/umairjavid/kombind/ui/KombindDialogFragment.kt +++ b/kombind/src/main/java/com/umairjavid/kombind/ui/KombindDialogFragment.kt @@ -1,17 +1,17 @@ package com.umairjavid.kombind.ui import android.app.Dialog -import androidx.lifecycle.ViewModelProvider -import androidx.lifecycle.ViewModelProviders -import androidx.databinding.DataBindingUtil -import androidx.databinding.ViewDataBinding import android.os.Bundle -import androidx.fragment.app.DialogFragment import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.view.Window +import androidx.databinding.DataBindingUtil +import androidx.databinding.ViewDataBinding import androidx.databinding.library.baseAdapters.BR +import androidx.fragment.app.DialogFragment +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.ViewModelProviders import com.umairjavid.kombind.ext.registerViewActionObserver abstract class KombindDialogFragment : DialogFragment() { @@ -28,7 +28,7 @@ abstract class KombindDialogFragment : DialogFragment() { viewModel.activityViewModel = (activity as KombindActivity<*>).viewModel viewBinding = DataBindingUtil.inflate(inflater, layoutResId, container, false) viewBinding.setVariable(BR.viewModel, viewModel) - viewBinding.setLifecycleOwner(this) + viewBinding.lifecycleOwner = this registerViewActionObserver(viewModel.viewAction) return viewBinding.root } @@ -43,7 +43,7 @@ abstract class KombindDialogFragment : DialogFragment() { super.onViewStateRestored(savedInstanceState) val width = resources.displayMetrics.widthPixels - dialog.window?.setLayout(width, ViewGroup.LayoutParams.WRAP_CONTENT) + dialog!!.window?.setLayout(width, ViewGroup.LayoutParams.WRAP_CONTENT) } override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { diff --git a/processor/.gitignore b/processor/.gitignore new file mode 100644 index 0000000..8cdebca --- /dev/null +++ b/processor/.gitignore @@ -0,0 +1,29 @@ +/build +# Java class files +*.class + +# Generated files +bin/ +gen/ +out/ + +# Gradle files +.gradle/ +build/ + +# Intellij +*.iml +.idea/workspace.xml +.idea/tasks.xml +.idea/gradle.xml +.idea/dictionaries +.idea/libraries + +# Freeline +freeline.py +freeline/ +freeline_project_description.json +.idea/misc.xml +.idea/runConfigurations.xml +.idea/vcs.xml +.idea/modules.xml diff --git a/processor/build.gradle b/processor/build.gradle new file mode 100644 index 0000000..b888386 --- /dev/null +++ b/processor/build.gradle @@ -0,0 +1,34 @@ +apply plugin: 'java-library' +apply plugin: 'kotlin' +apply plugin: 'kotlin-kapt' + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" + kapt 'com.google.auto.service:auto-service:1.0-rc2' + implementation 'com.google.auto.service:auto-service:1.0-rc2' + implementation project(':annotation') + implementation 'com.squareup:kotlinpoet:1.2.0' +} + +sourceCompatibility = "7" +targetCompatibility = "7" +buildscript { + repositories { + mavenCentral() + } + +} +repositories { + mavenCentral() +} +compileKotlin { + kotlinOptions { + jvmTarget = "1.8" + } +} +compileTestKotlin { + kotlinOptions { + jvmTarget = "1.8" + } +} diff --git a/processor/src/main/java/com/umairjavid/kombind/processor/AdapterProcessor.kt b/processor/src/main/java/com/umairjavid/kombind/processor/AdapterProcessor.kt new file mode 100644 index 0000000..34b4b44 --- /dev/null +++ b/processor/src/main/java/com/umairjavid/kombind/processor/AdapterProcessor.kt @@ -0,0 +1,129 @@ +package com.umairjavid.kombind.processor + +import com.google.auto.service.AutoService +import com.squareup.kotlinpoet.ClassName +import com.squareup.kotlinpoet.FileSpec +import com.squareup.kotlinpoet.FunSpec +import com.squareup.kotlinpoet.KModifier +import com.squareup.kotlinpoet.ParameterSpec +import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy +import com.squareup.kotlinpoet.PropertySpec +import com.squareup.kotlinpoet.TypeName +import com.squareup.kotlinpoet.TypeSpec +import com.squareup.kotlinpoet.asTypeName +import com.umairjavid.kombind.anontation.SimpleKombindAdapter +import java.io.File +import javax.annotation.processing.AbstractProcessor +import javax.annotation.processing.Messager +import javax.annotation.processing.ProcessingEnvironment +import javax.annotation.processing.Processor +import javax.annotation.processing.RoundEnvironment +import javax.lang.model.SourceVersion +import javax.lang.model.element.TypeElement +import javax.lang.model.element.VariableElement +import javax.tools.Diagnostic +import kotlin.reflect.KClass + +@AutoService(Processor::class) +class AdapterProcessor : AbstractProcessor() { + lateinit var messenger: Messager + val genaratedClassSet = mutableSetOf() + + override fun init(env: ProcessingEnvironment?) { + super.init(env) + messenger = env!!.messager + } + override fun getSupportedAnnotationTypes() = mutableSetOf(SimpleKombindAdapter::class.java.name) + + override fun getSupportedSourceVersion() = SourceVersion.latestSupported() + + override fun process(typeElemnts: MutableSet?, roundEnv: RoundEnvironment?): Boolean { + val env = roundEnv!!.getElementsAnnotatedWith(SimpleKombindAdapter::class.java) + env.forEach { + val layoutRes = it.getAnnotation(SimpleKombindAdapter::class.java).layoutRes + if (it is VariableElement) + generateAdapterClass(it, layoutRes) + } + return false + } + + fun generateAdapterClass(element: VariableElement, layoutRes: Int) { + if (!element.asType().asTypeName().isMutableLiveArraylist()) { + messenger.printMessage(Diagnostic.Kind.ERROR, "Needs to be of type MutableArrayList") + return + } + + val kombindPackage = "com.umairjavid.kombind.ui" + val appPackageName = processingEnv.elementUtils.getPackageOf(element).simpleName.toString() + val kombindAdapterName = "KombindAdapter" + val viewHolder = "KombindAdapter.ViewHolder" + val elementName = element.simpleName + val enclosingClassName = element.enclosingElement.simpleName + val className = "KombindAdapter_${enclosingClassName.toString().capitalize()}_${elementName.toString().capitalize()}" + + if (genaratedClassSet.contains(className)) { + messenger.printMessage(Diagnostic.Kind.ERROR, "Properties with sig not allowed,consider combinding both types into one adapter") + return + } + genaratedClassSet.add(className) + + val file = FileSpec.builder(appPackageName, className) + .addType(TypeSpec.classBuilder(className) + .makeAbstractIfTrue(layoutRes == 0) + .primaryConstructor(FunSpec.constructorBuilder() + .addParameter("items", element.asType().asTypeName().checkForAnyType()) + .addParameter("handler", Any::class).build()) + .addProperty(generateConstructorProperty("handler", Any::class)) + .addSuperclassConstructorParameter("items") + .addFunction(generateGetHandlerMethod()) + .addFunction(if (layoutRes == 0) generateAbstractGetLayoutMethod() else generateGetLayoutMethod(layoutRes)) + .superclass(ClassName(kombindPackage, kombindAdapterName).parameterizedBy(ClassName(kombindPackage, viewHolder)) + ).build()) + .build() + val kaptKotlnGenDir = processingEnv.options[KAPT_KOTLIN_GENERATED_OPTION_NAME] + file.writeTo(File(kaptKotlnGenDir, "Gen$className")) + } + + private fun generateConstructorProperty(propertyName: String, propertyType: KClass<*>) = PropertySpec.builder(propertyName, propertyType) + .initializer(propertyName) + .addModifiers(KModifier.PRIVATE) + .build() + + private fun generateGetHandlerMethod() = FunSpec.builder("getHandler") + .addModifiers(KModifier.OVERRIDE) + .addParameter(ParameterSpec.builder("position", Int::class).build()) + .addStatement("return handler") + .build() + + private fun generateGetLayoutMethod(layoutRes: Int): FunSpec { + return FunSpec.builder("getLayout") + .addModifiers(KModifier.OVERRIDE) + .addParameter(ParameterSpec.builder("position", Int::class).build()) + .addStatement("return $layoutRes") + .returns(Int::class) + .build() + } + + private fun generateAbstractGetLayoutMethod() = + FunSpec.builder("getLayout") + .addModifiers(KModifier.OVERRIDE, KModifier.ABSTRACT) + .addParameter(ParameterSpec.builder("position", Int::class).build()) + .returns(Int::class) + .build() + + private fun String.capitalize() = this.substring(0..0).toUpperCase() + this.substring(1) + + + private fun TypeName.checkForAnyType() = if (this.toString().equals("com.umairjavid.kombind.model.MutableLiveArrayList")) { + ClassName("com.umairjavid.kombind.model", "MutableLiveArrayList").parameterizedBy(ClassName("kotlin", "Any")) + } else this + + private fun TypeSpec.Builder.makeAbstractIfTrue(isAbstract: Boolean) = if (isAbstract) { + + this.addModifiers(KModifier.ABSTRACT) + } else this + + private fun TypeName.isMutableLiveArraylist() = this.toString().startsWith("com.umairjavid.kombind.model.MutableLiveArrayList") + + companion object { const val KAPT_KOTLIN_GENERATED_OPTION_NAME = "kapt.kotlin.generated" } +} diff --git a/settings.gradle b/settings.gradle index d0f092e..5379f76 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -include ':app', ':kombind' +include ':app', ':kombind', ':processor', ':annotation'