diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index 681f41ae..3628614f 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -1,5 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 03451793..32a98187 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,27 @@ Change Log ========== +## Version 1.1.6 + +_2020-09-01_ + + * **BREAKING**: Removed activity field from Screen class due to Firebase deprecated _setCurrentScreen_ method. + * **BREAKING**: Implement LiveCollector configuration class. + * **BREAKING**: Rename Collar plugin extension parameter _filePath_ to _fileName_. + * Update Kotlin to 1.4.0. + * Update various dependencies. + * Implement latest changes on GeneratorTask. + * Update Gradle wrapper to 6.6. + * Set explicit dependency version for Kotlin Reflect to avoid adding multiple versions resolution. + * Add Javadoc to exposed classes and methods. + * Update KotlinPoet to 1.6.0. + * Update Detekt to 1.11.0. + * Implement redaction feature. + * Replace Moshi with KotlinX Serialization in generators + * Make description optional in generator models + * Make members optional in AnalyticsModel + * Make GenerateTask inherit from SourceTask + ## Version 1.1.5 _2020-04-28_ diff --git a/README.md b/README.md index c2dfa808..cd82b51a 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ buildscript { maven { url "https://dl.bintray.com/infinum/android" } } dependencies { - classpath "co.infinum.collar:collar-plugin:1.1.5" + classpath "co.infinum.collar:collar-plugin:1.1.6" } } ``` @@ -46,7 +46,7 @@ buildscript { maven(url = "https://dl.bintray.com/infinum/android") } dependencies { - classpath("co.infinum.collar:collar-plugin:1.1.5") + classpath("co.infinum.collar:collar-plugin:1.1.6") } } ``` @@ -250,28 +250,32 @@ javaCompileOptions { ### Plugin extension ```gradle collar { - version "1.1.5" + version "1.1.6" } ``` You can set a specific _Collar_ version to be used. + ## Debug UI + +![UI](ui.jpg) + A separate package and no-op package is provided if you want to visually track what has been sent through Collar. You can search, filter and clear all sent analytics. In your app `build.gradle` or `build.gradle.kts` add: **Groovy** ```gradle -debugImplementation "co.infinum.collar:collar-ui:1.1.5" -releaseImplementation "co.infinum.collar:collar-ui-no-op:1.1.5" +debugImplementation "co.infinum.collar:collar-ui:1.1.6" +releaseImplementation "co.infinum.collar:collar-ui-no-op:1.1.6" ``` **KotlinDSL** ```kotlin -debugImplementation("co.infinum.collar:collar-ui:1.1.5") -releaseImplementation("co.infinum.collar:collar-ui-no-op:1.1.5") +debugImplementation("co.infinum.collar:collar-ui:1.1.6") +releaseImplementation("co.infinum.collar:collar-ui-no-op:1.1.6") ``` In order to start tracking with UI you must use _LiveCollector_ as in this example: ```kotlin - Collar.attach(object : LiveCollector(true, false) { + Collar.attach(object : LiveCollector() { override fun onScreen(screen: Screen) = super.onScreen(screen).run { @@ -289,10 +293,11 @@ In order to start tracking with UI you must use _LiveCollector_ as in this examp } }) ``` -If you put the first parameter *showSystemNotification* as *true* in *LiveCollector*, a notification will show once analytics are gathered and clicking on it will open a dedicated screen. -Second parameter *showInAppNotification* with value *true* in *LiveCollector* will show a Snackbar-ish popup once analytics are gathered inside the current running Activity. +_LiveCollector_ constructor has a _Configuration_ parameter that consists of following members. +If you put the first parameter *showSystemNotification* as *true* in *Configuration*, a notification will show once analytics are gathered and clicking on it will open a dedicated screen. +Second parameter *showInAppNotification* with value *true* in *Configuration* will show a Snackbar-ish popup once analytics are gathered inside the current running Activity. These parameters are default values per collector session but can be changed via _CollarActivity_ menu and will remain valid until the next session. -Otherwise if set to *false* notification will **not** be shown but you can always run the UI with following command of getting the launch Intent: +Otherwise if set to *false* notification will **not** be shown but you can always run the UI with following command of getting the launch Intent instead of clicking the actual notification: ```kotlin startActivity( CollarUi.launchIntent().apply { @@ -300,31 +305,37 @@ Otherwise if set to *false* notification will **not** be shown but you can alway } ) ``` -Also you can use a dedicated method with default Intent setup: +Alternatively, you can use a dedicated method with default Intent setup: ```kotlin CollarUi.show() ``` +Third parameter in *Configuration* is a set of keywords to redact if found in screen names, analytics events names and parameters and user properties names or values. + +![Notification](notification.jpg) ![In app notification](in_app_notification.jpg) + +### Redaction +In order to prevent potential leaks of user sensitive data, developers have an option to implement a set of keywords to be replaced by a • in length of the matched keyword. +This set of keywords is provided to _LiveCollector_ via _Configuration_. -![Notification](notification.jpg)![UI](ui.jpg) +![Redacted notification](redacted_notification.jpg)![UI](redacted_ui.jpg) ## Tasks ### Generate Gradle plugin supports code generation from a JSON formatted file. -You will need to specify `filePath` and `packageName` in `collar` plugin extension. +You will need to specify `fileName` and `packageName` in `collar` plugin extension. For example: ``` collar { - version "1.1.5" + version "1.1.6" filePath = "example.json" packageName = "co.infinum.collar.sample.analytics.generated" - variant = "main" // main by default } ``` JSON file has to be formatted in the same way as it is in `sample` project module. -If you don't want to use this task simply don't specify mandatory data. -Using this file is just a temporary and fetching the tracking plan will be implemented soon in future releases. +If you don't want to use this task simply don't specify data parameters in plugin extension. +Using this file is just a temporary solution and fetching the tracking plan will be implemented soon in future releases. To run the task you can: diff --git a/annotations/src/main/kotlin/co/infinum/collar/annotations/EventName.kt b/annotations/src/main/kotlin/co/infinum/collar/annotations/EventName.kt index 15052784..d93c1efb 100644 --- a/annotations/src/main/kotlin/co/infinum/collar/annotations/EventName.kt +++ b/annotations/src/main/kotlin/co/infinum/collar/annotations/EventName.kt @@ -1,5 +1,12 @@ package co.infinum.collar.annotations +/** + * An analytics event name annotation + * + * @property value Holds the actual name of the event. If empty, class name will be taken and formatted into snake case. + * @property enabled Determines if this annotation will be processed or skipped. + * @constructor Default values are provided with an empty value and enabled annotation ready for processing. + */ @Target(AnnotationTarget.CLASS) @Retention(AnnotationRetention.RUNTIME) annotation class EventName( diff --git a/annotations/src/main/kotlin/co/infinum/collar/annotations/EventParameterName.kt b/annotations/src/main/kotlin/co/infinum/collar/annotations/EventParameterName.kt index 3949b9da..35a1e5ce 100644 --- a/annotations/src/main/kotlin/co/infinum/collar/annotations/EventParameterName.kt +++ b/annotations/src/main/kotlin/co/infinum/collar/annotations/EventParameterName.kt @@ -1,5 +1,12 @@ package co.infinum.collar.annotations +/** + * An event parameter name annotation + * + * @property value Holds the actual name of the event parameter name. If empty, actual field name will be taken. + * @property enabled Determines if this annotation will be processed or skipped. + * @constructor Default values are provided with an empty value and enabled annotation ready for processing. + */ @Target(AnnotationTarget.FIELD) @Retention(AnnotationRetention.RUNTIME) annotation class EventParameterName( diff --git a/annotations/src/main/kotlin/co/infinum/collar/annotations/PropertyName.kt b/annotations/src/main/kotlin/co/infinum/collar/annotations/PropertyName.kt index 5b096567..fcd25658 100644 --- a/annotations/src/main/kotlin/co/infinum/collar/annotations/PropertyName.kt +++ b/annotations/src/main/kotlin/co/infinum/collar/annotations/PropertyName.kt @@ -1,5 +1,12 @@ package co.infinum.collar.annotations +/** + * A user property name annotation + * + * @property value Holds the actual name of the user property. If empty, actual field name will be taken. + * @property enabled Determines if this annotation will be processed or skipped. + * @constructor Default values are provided with an empty value and enabled annotation ready for processing. + */ @Target(AnnotationTarget.CLASS) @Retention(AnnotationRetention.RUNTIME) annotation class PropertyName( diff --git a/build.gradle b/build.gradle index ec2ccdcf..8b67a4da 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ buildscript { "compileSdk": 29, "targetSdk" : 29, - "buildTools": "29.0.3" + "buildTools": "30.0.1" ] apply from: "dependencies.gradle" @@ -36,19 +36,23 @@ allprojects { maven { url "https://dl.bintray.com/infinum/android" } } -// configurations.all { -// resolutionStrategy { -// dependencySubstitution { -// substitute module(packages.collar.annotations) with project(':annotations') -// substitute module(packages.collar.core) with project(':core') -// substitute module(packages.collar.ui) with project(':ui') -// substitute module(packages.collar.ui_no_op) with project(':ui-no-op') -// substitute module(packages.collar.processor) with project(':processor') -// substitute module(packages.collar.generator) with project(':generator') -// substitute module(packages.collar.plugin) with project(':plugin') -// } -// } -// } + def buildProperties = new Properties() + file(rootDir.absolutePath+"/build.properties").withInputStream { buildProperties.load(it) } + if (buildProperties.getProperty("build.debug").toBoolean()) { + configurations.all { + resolutionStrategy { + dependencySubstitution { + substitute module(packages.collar.annotations) with project(':annotations') + substitute module(packages.collar.core) with project(':core') + substitute module(packages.collar.ui) with project(':ui') + substitute module(packages.collar.ui_no_op) with project(':ui-no-op') + substitute module(packages.collar.processor) with project(':processor') + substitute module(packages.collar.generator) with project(':generator') + substitute module(packages.collar.plugin) with project(':plugin') + } + } + } + } } subprojects { diff --git a/build.properties b/build.properties new file mode 100644 index 00000000..b6db6f86 --- /dev/null +++ b/build.properties @@ -0,0 +1 @@ +build.debug=false \ No newline at end of file diff --git a/core/src/main/kotlin/co/infinum/collar/Collar.kt b/core/src/main/kotlin/co/infinum/collar/Collar.kt index d2aaf8c9..2f68147a 100644 --- a/core/src/main/kotlin/co/infinum/collar/Collar.kt +++ b/core/src/main/kotlin/co/infinum/collar/Collar.kt @@ -1,28 +1,50 @@ package co.infinum.collar -import android.app.Activity import android.os.Bundle +/** + * Singleton object entry point for screen names, events and properties collection. + */ object Collar { private var collector: Collector? = null + /** + * Attach a collector to running process. + * + * @param collector interface or class implementing such interface. + */ @JvmStatic fun attach(collector: Collector) { this.collector = collector } - fun trackScreen(activity: Activity, screenName: String) = + /** + * Track screen names using a direct value. + * + * @param screenName value. + */ + fun trackScreen(screenName: String) = collector?.onScreen( Screen( - activity = activity, name = screenName ) ) + /** + * Track screen names using a provided wrapper class. + * + * @param screen wrapper class. + */ fun trackScreen(screen: Screen) = collector?.onScreen(screen) + /** + * Track events using direct values for event name and optional event parameters. + * + * @param eventName value. + * @param params value. + */ fun trackEvent(eventName: String, params: Bundle) = collector?.onEvent( Event( @@ -31,9 +53,21 @@ object Collar { ) ) + /** + * Track events using a provided wrapper class. + * + * @param event wrapper class. + */ fun trackEvent(event: Event) = collector?.onEvent(event) + /** + * Track user properties using direct values for property name and optional property value. + * If value is set as 'null', property is cleared. + * + * @param name value. + * @param value value. + */ fun trackProperty(name: String, value: String?) = collector?.onProperty( Property( @@ -42,6 +76,11 @@ object Collar { ) ) + /** + * Track user properties using a provided wrapper class. + * + * @param property wrapper class. + */ fun trackProperty(property: Property) = collector?.onProperty(property) } diff --git a/core/src/main/kotlin/co/infinum/collar/Collector.kt b/core/src/main/kotlin/co/infinum/collar/Collector.kt index f1bae4e5..1953a680 100644 --- a/core/src/main/kotlin/co/infinum/collar/Collector.kt +++ b/core/src/main/kotlin/co/infinum/collar/Collector.kt @@ -1,16 +1,32 @@ package co.infinum.collar /** - * Collar aggregates all tracked analytics events. - * Once any event is ready, Collar emits the event to the collector. + * Aggregation collector for all tracked screen names, analytics events and user properties. + * By default, has no implementation or UI. * - * This is a good place to implement your analytics tools such as Firebase, Amplitude, Mixpanel, etc. + * Implementation or invocation of this interface is intended for implementing analytics tools + * such as Firebase, Amplitude, Mixpanel, etc. */ interface Collector { + /** + * Invoked when a new screen is emitted. + * + * @param screen wrapper class. + */ fun onScreen(screen: Screen) + /** + * Invoked when a new analytics event is emitted. + * + * @param event wrapper class. + */ fun onEvent(event: Event) + /** + * Invoked when a new user property is emitted. + * + * @param property wrapper class. + */ fun onProperty(property: Property) } diff --git a/core/src/main/kotlin/co/infinum/collar/Event.kt b/core/src/main/kotlin/co/infinum/collar/Event.kt index b378aee0..1cc31170 100644 --- a/core/src/main/kotlin/co/infinum/collar/Event.kt +++ b/core/src/main/kotlin/co/infinum/collar/Event.kt @@ -6,6 +6,14 @@ import android.os.Bundle * This is the container model for the triggered tracking analytics event. */ data class Event( + + /** + * Name of the tracked analytics event. + */ val name: String, + + /** + * Optional parameters of the tracked analytics event. + */ val params: Bundle? = null ) diff --git a/core/src/main/kotlin/co/infinum/collar/Property.kt b/core/src/main/kotlin/co/infinum/collar/Property.kt index 0ba3d3fb..0cfa0964 100644 --- a/core/src/main/kotlin/co/infinum/collar/Property.kt +++ b/core/src/main/kotlin/co/infinum/collar/Property.kt @@ -4,6 +4,14 @@ package co.infinum.collar * This is the container model for the user property tracking. */ data class Property( + + /** + * Name of the tracked user property. + */ val name: String, + + /** + * Optional value of the tracked user property. + */ val value: String? ) diff --git a/core/src/main/kotlin/co/infinum/collar/Screen.kt b/core/src/main/kotlin/co/infinum/collar/Screen.kt index c071bbf5..58d825ae 100644 --- a/core/src/main/kotlin/co/infinum/collar/Screen.kt +++ b/core/src/main/kotlin/co/infinum/collar/Screen.kt @@ -1,11 +1,12 @@ package co.infinum.collar -import android.app.Activity - /** * This is the container model for the triggered screen tracking. */ data class Screen( - val activity: Activity, + + /** + * Name of the tracked screen. + */ val name: String ) diff --git a/dependencies.gradle b/dependencies.gradle index 72d1bc10..322dfabe 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -1,40 +1,43 @@ ext.collar = [ - "group" : "co.infinum.collar", - "version": "1.1.5", - "versionCode": 1*100*100 + 1*100 + 5 + "group" : "co.infinum.collar", + "version" : "1.1.6", + "versionCode": 1 * 100 * 100 + 1 * 100 + 6 ] ext.versions = [ - "collar" : "1.1.5", - "gradle" : '3.6.3', - "kotlin" : "1.3.72", - "poet" : "1.5.0", - "poet_metadata": "1.5.0", + "collar" : "1.1.6", + "gradle" : "4.0.1", + "kotlin" : "1.4.0", + "poet" : "1.6.0", + "poet_metadata": "1.6.0", + "serialization": "1.0.0-RC", "annotations" : "19.0.0", "incap" : "0.2", "bintray" : "1.8.5", - "core" : "1.2.0", - "appcompat" : "1.1.0", - "fragment" : "1.2.4", + "core" : "1.3.1", + "appcompat" : "1.2.0", + "fragment" : "1.2.5", "lifecycle" : "2.2.0", "viewmodel" : "2.2.0", "livedata" : "2.2.0", "recyclerview" : "1.1.0", "room" : "2.2.5", - "design" : "1.1.0", - "moshi" : "1.9.2", + "design" : "1.2.0", "maven" : "2.0", - "detekt" : "1.7.4", + "detekt" : "1.11.0", "dokka" : "0.10.1" ] ext.packages = [ "gradle" : "com.android.tools.build:gradle:${versions.gradle}", "kotlin" : [ - "plugin" : "org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}", - "core" : "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${versions.kotlin}", - "poet" : "com.squareup:kotlinpoet:${versions.poet}", - "poet_metadata": "com.squareup:kotlinpoet-metadata:${versions.poet_metadata}" + "plugin" : "org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}", + "core" : "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${versions.kotlin}", + "reflect" : "org.jetbrains.kotlin:kotlin-reflect:${versions.kotlin}", + "poet" : "com.squareup:kotlinpoet:${versions.poet}", + "poet_metadata" : "com.squareup:kotlinpoet-metadata:${versions.poet_metadata}", + "serialization_plugin": "org.jetbrains.kotlin:kotlin-serialization:${versions.kotlin}", + "serialization_core" : "org.jetbrains.kotlinx:kotlinx-serialization-core:${versions.serialization}" ], "annotations": "org.jetbrains:annotations-java5:${versions.annotations}", "incap" : [ @@ -66,9 +69,6 @@ ext.packages = [ "runtime" : "androidx.room:room-runtime:${versions.room}" ] ], - "moshi" : [ - "kotlin": "com.squareup.moshi:moshi-kotlin:${versions.moshi}" - ], "google" : [ "design": "com.google.android.material:material:${versions.design}" ], diff --git a/generator/build.gradle b/generator/build.gradle index 445c9d3c..92f89e3e 100644 --- a/generator/build.gradle +++ b/generator/build.gradle @@ -1,5 +1,14 @@ +buildscript { + repositories { jcenter() } + + dependencies { + classpath packages.kotlin.serialization_plugin + } +} + apply plugin: 'java-library' apply plugin: 'kotlin' +apply plugin: 'kotlinx-serialization' compileKotlin { kotlinOptions { @@ -21,7 +30,8 @@ test { dependencies { implementation packages.kotlin.core implementation packages.kotlin.poet - implementation packages.moshi.kotlin + implementation packages.kotlin.reflect + implementation packages.kotlin.serialization_core testImplementation "junit:junit:4.13" testImplementation 'org.mockito:mockito-core:3.3.3' diff --git a/generator/src/main/kotlin/co/infinum/collar/generator/CollarGenerator.kt b/generator/src/main/kotlin/co/infinum/collar/generator/CollarGenerator.kt index 48a8828e..15a1478e 100644 --- a/generator/src/main/kotlin/co/infinum/collar/generator/CollarGenerator.kt +++ b/generator/src/main/kotlin/co/infinum/collar/generator/CollarGenerator.kt @@ -5,27 +5,43 @@ import co.infinum.collar.generator.generators.Generator import co.infinum.collar.generator.generators.ScreensGenerator import co.infinum.collar.generator.generators.UserPropertiesGenerator import co.infinum.collar.generator.models.AnalyticsModel -import co.infinum.collar.generator.providers.MoshiProvider +import kotlinx.serialization.SerializationException +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.Json import java.io.File +/** + * Generator class used to parse JSON into according Kotlin models for Collar processor consumption. + */ class CollarGenerator { + /** + * Generate models from provided JSON. + * Parameters are passed from Collar plugin extension to a Gradle task. + * + * @param filePath absolute path for the provided JSON file. + * @param output absolute path for the generated classes. + * @param packageName package name for the generated classes. + */ fun generate(filePath: String, output: String, packageName: String): Boolean = - MoshiProvider.provide() - .value - .adapter(AnalyticsModel::class.java) - .fromJson(File(filePath).readText(Charsets.UTF_8)) - ?.let { - generators(it, output, packageName) - .fold(true, { accumulator: Boolean, generator: Generator -> accumulator && generator.generate() }) - } ?: false - - private fun generators(analyticsModel: AnalyticsModel, output: String, packageName: String) = - with(analyticsModel) { - listOf( - ScreensGenerator(screens, output, packageName), - UserPropertiesGenerator(userProperties, output, packageName), - EventsGenerator(events, output, packageName) - ) + try { + Json + .decodeFromString(File(filePath).readText(Charsets.UTF_8)) + .run { + listOf( + ScreensGenerator(screens, output, packageName), + UserPropertiesGenerator(properties, output, packageName), + EventsGenerator(events, output, packageName) + ) + .fold( + initial = true, + operation = { accumulator: Boolean, generator: Generator -> + accumulator && generator.generate() + } + ) + } + } catch (exception: SerializationException) { + exception.printStackTrace() + false } } diff --git a/generator/src/main/kotlin/co/infinum/collar/generator/extensions/String.kt b/generator/src/main/kotlin/co/infinum/collar/generator/extensions/String.kt index 19068c15..19eddb2f 100644 --- a/generator/src/main/kotlin/co/infinum/collar/generator/extensions/String.kt +++ b/generator/src/main/kotlin/co/infinum/collar/generator/extensions/String.kt @@ -1,5 +1,16 @@ package co.infinum.collar.generator.extensions -internal fun String.toCamelCase(): String = split(" ").map { it.capitalize() }.joinToString("") +import co.infinum.collar.generator.models.DataType +import java.util.Locale -internal fun String.hasDigit(): Boolean = this.any { it.isDigit() } +internal fun String.toCamelCase(): String = split(" ", "_").joinToString("") { it.capitalize(Locale.getDefault()) } + +internal fun String.isFirstCharDigit(): Boolean = this.firstOrNull()?.isDigit() ?: false + +internal fun String.toEnumValue(): String { + var parameterValueEnumName = this.toUpperCase(Locale.ENGLISH).replace(" ", "_").replace(".", "_") + if (this.isFirstCharDigit()) { + parameterValueEnumName = "${DataType.NUMBER.name.toUpperCase(Locale.ENGLISH)}_$parameterValueEnumName" + } + return parameterValueEnumName +} diff --git a/generator/src/main/kotlin/co/infinum/collar/generator/generators/CommonGenerator.kt b/generator/src/main/kotlin/co/infinum/collar/generator/generators/CommonGenerator.kt index fc63877b..7ee2dcc5 100644 --- a/generator/src/main/kotlin/co/infinum/collar/generator/generators/CommonGenerator.kt +++ b/generator/src/main/kotlin/co/infinum/collar/generator/generators/CommonGenerator.kt @@ -11,18 +11,21 @@ internal abstract class CommonGenerator( companion object { internal const val ANNOTATION_FORMAT = "value = %S" + internal const val DEFAULT_INDENT = " " } - override fun file(): FileSpec.Builder = FileSpec.builder(packageName, className) + override fun file(): FileSpec.Builder = FileSpec.builder(packageName, className).indent(DEFAULT_INDENT) override fun write(fileSpec: FileSpec) = fileSpec.writeTo(Paths.get(outputPath)) override fun generate(): Boolean { - write( - file() - .addType(type()) - .build() - ) + type()?.let { + write( + file() + .addType(it) + .build() + ) + } return true } } diff --git a/generator/src/main/kotlin/co/infinum/collar/generator/generators/EventsGenerator.kt b/generator/src/main/kotlin/co/infinum/collar/generator/generators/EventsGenerator.kt index f4de97b6..e3f81d3e 100644 --- a/generator/src/main/kotlin/co/infinum/collar/generator/generators/EventsGenerator.kt +++ b/generator/src/main/kotlin/co/infinum/collar/generator/generators/EventsGenerator.kt @@ -1,6 +1,8 @@ package co.infinum.collar.generator.generators import co.infinum.collar.generator.extensions.toCamelCase +import co.infinum.collar.generator.extensions.toEnumValue +import co.infinum.collar.generator.models.DataType import co.infinum.collar.generator.models.Event import com.squareup.kotlinpoet.AnnotationSpec import com.squareup.kotlinpoet.ClassName @@ -9,101 +11,121 @@ import com.squareup.kotlinpoet.KModifier import com.squareup.kotlinpoet.ParameterSpec import com.squareup.kotlinpoet.PropertySpec import com.squareup.kotlinpoet.TypeSpec +import java.util.Locale internal class EventsGenerator( - private val events: List, + private val items: List?, outputPath: String, packageName: String ) : CommonGenerator(outputPath, packageName, CLASS_NAME) { companion object { - const val CLASS_NAME = "AnalyticsEvents" + const val CLASS_NAME = "TrackingPlanEvents" } @Suppress("LongMethod", "NestedBlockDepth") - override fun type() = - TypeSpec.classBuilder(CLASS_NAME) - .addAnnotation(ClassName(Generator.COLLAR_ANNOTATION_PACKAGE, Generator.COLLAR_ANNOTATION_ANALYTICS_EVENTS)) - .addModifiers(KModifier.SEALED) - .apply { - events.forEach { event -> - val name = event.name.toCamelCase() - val eventClass = TypeSpec.classBuilder(name) - val constructorBuilder = FunSpec.constructorBuilder() + override fun type(): TypeSpec? = + items?.takeIf { it.isNotEmpty() }?.let { + TypeSpec.classBuilder(CLASS_NAME) + .addAnnotation( + ClassName( + Generator.COLLAR_ANNOTATION_PACKAGE, + Generator.COLLAR_ANNOTATION_ANALYTICS_EVENTS + ) + ) + .addModifiers(KModifier.SEALED) + .apply { + it.forEach { event -> + val name = event.name.toCamelCase() + val eventClass = TypeSpec.classBuilder(name) + val constructorBuilder = FunSpec.constructorBuilder() - event.parameters.forEach { - val eventParameterName = ClassName( - Generator.COLLAR_ANNOTATION_PACKAGE, - Generator.COLLAR_ANNOTATION_EVENT_PARAMETER_NAME - ) - val constructorParamAnnotation = AnnotationSpec.builder(eventParameterName) - .addMember(ANNOTATION_FORMAT, it.name).build() + event.parameters?.forEach { parameter -> + val eventParameterName = ClassName( + Generator.COLLAR_ANNOTATION_PACKAGE, + Generator.COLLAR_ANNOTATION_EVENT_PARAMETER_NAME + ) + val constructorParamAnnotation = AnnotationSpec.builder(eventParameterName) + .addMember(ANNOTATION_FORMAT, parameter.name).build() - val type = GeneratorUtils.getClassName(it) + val type = when { + parameter.values?.isNotEmpty() == true -> DataType.TEXT.className + else -> + DataType(parameter.type)?.className ?: error("${parameter.type} is not supported") + } - val constructorParamBuilder = ParameterSpec.builder( - GeneratorUtils.getParameterName(it.name), type - ).apply { - addKdoc(it.description) - addAnnotation(constructorParamAnnotation) - } - constructorBuilder.addParameter(constructorParamBuilder.build()) + val constructorParamBuilder = ParameterSpec.builder( + parameter.name.toCamelCase().decapitalize(Locale.ENGLISH), + type + ).apply { + parameter.description + ?.takeIf { it.isNotBlank() } + ?.let { addKdoc(it) } + addAnnotation(constructorParamAnnotation) + } + constructorBuilder.addParameter(constructorParamBuilder.build()) - if (it.values?.isNotEmpty() == true) { - val enumBuilder = TypeSpec.enumBuilder(GeneratorUtils.getParameterEnumName(it.name)) - .primaryConstructor( - FunSpec.constructorBuilder() - .addParameter("value", String::class) - .build() - ) - .addProperty( - PropertySpec.builder("value", String::class) - .initializer("value") - .build() - ) - .addFunction( - FunSpec.builder("toString") - .addModifiers(KModifier.OVERRIDE) - .addStatement("return value") - .build() + if (parameter.values?.isNotEmpty() == true) { + val enumBuilder = TypeSpec.enumBuilder( + parameter.name.toCamelCase() ) + .primaryConstructor( + FunSpec.constructorBuilder() + .addParameter("value", String::class, KModifier.PRIVATE) + .build() + ) + .addProperty( + PropertySpec.builder("value", String::class) + .initializer("value") + .build() + ) + .addFunction( + FunSpec.builder("toString") + .addModifiers(KModifier.OVERRIDE) + .addStatement("return value") + .build() + ) - it.values.forEach { value -> - enumBuilder.addEnumConstant( - GeneratorUtils.getParameterValueEnumName(value), TypeSpec.anonymousClassBuilder() - .addSuperclassConstructorParameter("%S", value) - .build() - ) - } + parameter.values.forEach { value -> + enumBuilder.addEnumConstant( + value.toEnumValue(), + TypeSpec.anonymousClassBuilder() + .addSuperclassConstructorParameter("%S", value) + .build() + ) + } - eventClass.addType(enumBuilder.build()) - } + eventClass.addType(enumBuilder.build()) + } - eventClass.addProperty( - PropertySpec.builder( - GeneratorUtils.getParameterName(it.name), - type + eventClass.addProperty( + PropertySpec.builder( + parameter.name.toCamelCase().decapitalize(Locale.ENGLISH), + type + ) + .initializer(parameter.name.toCamelCase().decapitalize(Locale.ENGLISH)) + .build() ) - .initializer(GeneratorUtils.getParameterName(it.name)) - .build() - ) - } + } - eventClass.addModifiers(KModifier.DATA) - eventClass.primaryConstructor(constructorBuilder.build()) - eventClass.addKdoc(event.description) - eventClass.superclass(ClassName("", CLASS_NAME)) - eventClass.addAnnotation( - AnnotationSpec.builder( - ClassName( - Generator.COLLAR_ANNOTATION_PACKAGE, - Generator.COLLAR_ANNOTATION_EVENT_NAME + if (event.parameters.isNullOrEmpty().not()) { + eventClass.addModifiers(KModifier.DATA) + } + eventClass.primaryConstructor(constructorBuilder.build()) + event.description?.takeIf { it.isNotBlank() }?.let { eventClass.addKdoc(it) } + eventClass.superclass(ClassName("", CLASS_NAME)) + eventClass.addAnnotation( + AnnotationSpec.builder( + ClassName( + Generator.COLLAR_ANNOTATION_PACKAGE, + Generator.COLLAR_ANNOTATION_EVENT_NAME + ) ) + .addMember(ANNOTATION_FORMAT, event.name).build() ) - .addMember(ANNOTATION_FORMAT, event.name).build() - ) - addType(eventClass.build()) - } - }.build() + addType(eventClass.build()) + } + }.build() + } } diff --git a/generator/src/main/kotlin/co/infinum/collar/generator/generators/Generator.kt b/generator/src/main/kotlin/co/infinum/collar/generator/generators/Generator.kt index a26c290f..33148b6a 100644 --- a/generator/src/main/kotlin/co/infinum/collar/generator/generators/Generator.kt +++ b/generator/src/main/kotlin/co/infinum/collar/generator/generators/Generator.kt @@ -18,7 +18,7 @@ internal interface Generator { fun file(): FileSpec.Builder - fun type(): TypeSpec + fun type(): TypeSpec? fun write(fileSpec: FileSpec) } diff --git a/generator/src/main/kotlin/co/infinum/collar/generator/generators/GeneratorUtils.kt b/generator/src/main/kotlin/co/infinum/collar/generator/generators/GeneratorUtils.kt deleted file mode 100644 index d8e2e7e6..00000000 --- a/generator/src/main/kotlin/co/infinum/collar/generator/generators/GeneratorUtils.kt +++ /dev/null @@ -1,48 +0,0 @@ -package co.infinum.collar.generator.generators - -import co.infinum.collar.generator.extensions.hasDigit -import co.infinum.collar.generator.extensions.toCamelCase -import co.infinum.collar.generator.models.DataType -import co.infinum.collar.generator.models.Parameter -import co.infinum.collar.generator.models.Property -import com.squareup.kotlinpoet.ClassName -import com.squareup.kotlinpoet.TypeName -import java.util.Locale - -internal class GeneratorUtils private constructor() { - - companion object { - - fun getClassName(parameter: Parameter): TypeName { - val dataTypeEnum = parameter.type - - if (parameter.values?.isNotEmpty() == true) { - return ClassName("kotlin", "String") - } - - return when (dataTypeEnum) { - DataType.TEXT.toString() -> ClassName("kotlin", "String") - DataType.NUMBER.toString() -> ClassName("kotlin", "Long") - DataType.DECIMAL.toString() -> ClassName("kotlin", "Double") - DataType.BOOLEAN.toString() -> ClassName("kotlin", "Boolean") - else -> throw NotImplementedError("$dataTypeEnum is not supported") - } - } - - fun getClassName(property: Property): TypeName { - return ClassName("kotlin", "String") - } - - fun getParameterName(parameter: String) = parameter.toCamelCase().decapitalize() - - fun getParameterEnumName(parameter: String) = parameter.toCamelCase() + "Enum" - - fun getParameterValueEnumName(value: String): String { - var parameterValueEnumName = value.toUpperCase(Locale.ENGLISH).replace(" ", "_").replace(".", "_") - if (value.hasDigit()) { - parameterValueEnumName = "NUMBER_$parameterValueEnumName" - } - return parameterValueEnumName - } - } -} diff --git a/generator/src/main/kotlin/co/infinum/collar/generator/generators/ScreensGenerator.kt b/generator/src/main/kotlin/co/infinum/collar/generator/generators/ScreensGenerator.kt index 17b0e99b..a44ad2f3 100644 --- a/generator/src/main/kotlin/co/infinum/collar/generator/generators/ScreensGenerator.kt +++ b/generator/src/main/kotlin/co/infinum/collar/generator/generators/ScreensGenerator.kt @@ -7,30 +7,36 @@ import com.squareup.kotlinpoet.TypeSpec import java.util.Locale internal class ScreensGenerator( - private val items: List, + private val items: List?, outputPath: String, packageName: String ) : CommonGenerator(outputPath, packageName, CLASS_NAME) { companion object { - const val CLASS_NAME = "AnalyticsScreens" + const val CLASS_NAME = "TrackingPlanScreens" } - override fun type(): TypeSpec = - TypeSpec.objectBuilder(CLASS_NAME) - .apply { - items.forEach { - addProperty( - PropertySpec.builder( - it.name.toUpperCase(Locale.ENGLISH).replace(" ", "_"), - String::class, - KModifier.CONST + override fun type(): TypeSpec? = + items?.takeIf { it.isNotEmpty() }?.let { + TypeSpec.objectBuilder(CLASS_NAME) + .apply { + it.forEach { screen -> + addProperty( + PropertySpec.builder( + screen.name.toUpperCase(Locale.ENGLISH).replace(" ", "_"), + String::class, + KModifier.CONST + ) + .initializer("%S", screen.name) + .apply { + screen.description + ?.takeIf { it.isNotBlank() } + ?.let { addKdoc(it) } + } + .build() ) - .initializer("%S", it.name) - .addKdoc(it.description) - .build() - ) + } } - } - .build() + .build() + } } diff --git a/generator/src/main/kotlin/co/infinum/collar/generator/generators/UserPropertiesGenerator.kt b/generator/src/main/kotlin/co/infinum/collar/generator/generators/UserPropertiesGenerator.kt index eb32da93..9b211678 100644 --- a/generator/src/main/kotlin/co/infinum/collar/generator/generators/UserPropertiesGenerator.kt +++ b/generator/src/main/kotlin/co/infinum/collar/generator/generators/UserPropertiesGenerator.kt @@ -1,6 +1,7 @@ package co.infinum.collar.generator.generators import co.infinum.collar.generator.extensions.toCamelCase +import co.infinum.collar.generator.extensions.toEnumValue import co.infinum.collar.generator.generators.Generator.Companion.COLLAR_ANNOTATION_PACKAGE import co.infinum.collar.generator.generators.Generator.Companion.COLLAR_ANNOTATION_PROPERTY_NAME import co.infinum.collar.generator.generators.Generator.Companion.COLLAR_ANNOTATION_USER_PROPERTIES @@ -13,82 +14,90 @@ import com.squareup.kotlinpoet.PropertySpec import com.squareup.kotlinpoet.TypeSpec internal class UserPropertiesGenerator( - private val userProperties: List, + private val items: List?, outputPath: String, packageName: String ) : CommonGenerator(outputPath, packageName, CLASS_NAME) { companion object { - const val CLASS_NAME = "AnalyticsUserProperties" + const val CLASS_NAME = "TrackingPlanUserProperties" const val PROPERTY_PARAMETER_NAME = "value" } @Suppress("LongMethod", "NestedBlockDepth") - override fun type(): TypeSpec = - TypeSpec.classBuilder(CLASS_NAME) - .addAnnotation(ClassName(COLLAR_ANNOTATION_PACKAGE, COLLAR_ANNOTATION_USER_PROPERTIES)) - .addModifiers(KModifier.SEALED) - .apply { - userProperties.forEach { userProperty -> - val name = userProperty.name.toCamelCase() - val propertyNameClassName = ClassName(COLLAR_ANNOTATION_PACKAGE, COLLAR_ANNOTATION_PROPERTY_NAME) - val propertyNameAnnotation = AnnotationSpec.builder(propertyNameClassName) - .addMember(ANNOTATION_FORMAT, userProperty.name).build() - val userPropertyClass = TypeSpec.classBuilder(name).apply { - addModifiers(KModifier.DATA) - primaryConstructor( - FunSpec.constructorBuilder() - .addParameter( - PROPERTY_PARAMETER_NAME, - GeneratorUtils.getClassName(userProperty) - ) - .build() + override fun type(): TypeSpec? = + items?.takeIf { it.isNotEmpty() }?.let { + TypeSpec.classBuilder(CLASS_NAME) + .addAnnotation(ClassName(COLLAR_ANNOTATION_PACKAGE, COLLAR_ANNOTATION_USER_PROPERTIES)) + .addModifiers(KModifier.SEALED) + .apply { + it.forEach { userProperty -> + val name = userProperty.name.toCamelCase() + val propertyNameClassName = ClassName( + COLLAR_ANNOTATION_PACKAGE, + COLLAR_ANNOTATION_PROPERTY_NAME ) - addProperty( - PropertySpec.builder( - PROPERTY_PARAMETER_NAME, - GeneratorUtils.getClassName(userProperty) - ) - .initializer(PROPERTY_PARAMETER_NAME) - .build() - ) - - addKdoc(userProperty.description) - superclass(ClassName("", CLASS_NAME)) - addAnnotation(propertyNameAnnotation) - } - if (userProperty.values?.isNotEmpty() == true) { - val enumBuilder = TypeSpec.enumBuilder(GeneratorUtils.getParameterEnumName(userProperty.name)) - .primaryConstructor( + val propertyNameAnnotation = AnnotationSpec.builder(propertyNameClassName) + .addMember(ANNOTATION_FORMAT, userProperty.name).build() + val userPropertyClass = TypeSpec.classBuilder(name).apply { + addModifiers(KModifier.DATA) + primaryConstructor( FunSpec.constructorBuilder() - .addParameter("value", String::class) - .build() - ) - .addProperty( - PropertySpec.builder("value", String::class) - .initializer("value") + .addParameter( + PROPERTY_PARAMETER_NAME, + ClassName("kotlin", "String") + ) .build() ) - .addFunction( - FunSpec.builder("toString") - .addModifiers(KModifier.OVERRIDE) - .addStatement("return value") + addProperty( + PropertySpec.builder( + PROPERTY_PARAMETER_NAME, + ClassName("kotlin", "String") + ) + .initializer(PROPERTY_PARAMETER_NAME) .build() ) - userProperty.values.forEach { value -> - enumBuilder.addEnumConstant( - GeneratorUtils.getParameterValueEnumName(value), TypeSpec.anonymousClassBuilder() - .addSuperclassConstructorParameter("%S", value) - .build() + userProperty.description?.takeIf { it.isNotBlank() }?.let { addKdoc(it) } + superclass(ClassName("", CLASS_NAME)) + addAnnotation(propertyNameAnnotation) + } + if (userProperty.values?.isNotEmpty() == true) { + val enumBuilder = TypeSpec.enumBuilder( + userProperty.name.toCamelCase() ) + .primaryConstructor( + FunSpec.constructorBuilder() + .addParameter("value", String::class, KModifier.PRIVATE) + .build() + ) + .addProperty( + PropertySpec.builder("value", String::class) + .initializer("value") + .build() + ) + .addFunction( + FunSpec.builder("toString") + .addModifiers(KModifier.OVERRIDE) + .addStatement("return value") + .build() + ) + + userProperty.values.forEach { value -> + enumBuilder.addEnumConstant( + value.toEnumValue(), + TypeSpec.anonymousClassBuilder() + .addSuperclassConstructorParameter("%S", value) + .build() + ) + } + + userPropertyClass.addType(enumBuilder.build()) } - userPropertyClass.addType(enumBuilder.build()) + addType(userPropertyClass.build()) } - - addType(userPropertyClass.build()) } - } - .build() + .build() + } } diff --git a/generator/src/main/kotlin/co/infinum/collar/generator/models/AnalyticsModel.kt b/generator/src/main/kotlin/co/infinum/collar/generator/models/AnalyticsModel.kt index f8f54e38..ebd8f8ad 100644 --- a/generator/src/main/kotlin/co/infinum/collar/generator/models/AnalyticsModel.kt +++ b/generator/src/main/kotlin/co/infinum/collar/generator/models/AnalyticsModel.kt @@ -1,7 +1,17 @@ package co.infinum.collar.generator.models +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable internal data class AnalyticsModel( - val events: List, - val screens: List, - val userProperties: List + + @SerialName("screens") + val screens: List? = null, + + @SerialName("events") + val events: List? = null, + + @SerialName("userProperties") + val properties: List? = null ) diff --git a/generator/src/main/kotlin/co/infinum/collar/generator/models/DataType.kt b/generator/src/main/kotlin/co/infinum/collar/generator/models/DataType.kt index a76b0a5a..0c0dc39d 100644 --- a/generator/src/main/kotlin/co/infinum/collar/generator/models/DataType.kt +++ b/generator/src/main/kotlin/co/infinum/collar/generator/models/DataType.kt @@ -1,12 +1,16 @@ package co.infinum.collar.generator.models -internal enum class DataType { - TEXT, - NUMBER, - DECIMAL, - BOOLEAN; - - override fun toString(): String { - return super.toString().toLowerCase() +import com.squareup.kotlinpoet.ClassName +import java.util.Locale + +internal enum class DataType(val className: ClassName) { + TEXT(ClassName("kotlin", "String")), + NUMBER(ClassName("kotlin", "Long")), + DECIMAL(ClassName("kotlin", "Double")), + BOOLEAN(ClassName("kotlin", "Boolean")); + + companion object { + + operator fun invoke(name: String) = values().firstOrNull { it.toString().toLowerCase(Locale.ENGLISH) == name } } } diff --git a/generator/src/main/kotlin/co/infinum/collar/generator/models/Event.kt b/generator/src/main/kotlin/co/infinum/collar/generator/models/Event.kt index 5c6b19a4..eec1080a 100644 --- a/generator/src/main/kotlin/co/infinum/collar/generator/models/Event.kt +++ b/generator/src/main/kotlin/co/infinum/collar/generator/models/Event.kt @@ -1,7 +1,17 @@ package co.infinum.collar.generator.models +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable internal data class Event( + + @SerialName("description") + val description: String? = null, + + @SerialName("name") val name: String, - val description: String, - val parameters: List + + @SerialName("properties") + val parameters: List? = null ) diff --git a/generator/src/main/kotlin/co/infinum/collar/generator/models/Parameter.kt b/generator/src/main/kotlin/co/infinum/collar/generator/models/Parameter.kt index 493356d1..fae43183 100644 --- a/generator/src/main/kotlin/co/infinum/collar/generator/models/Parameter.kt +++ b/generator/src/main/kotlin/co/infinum/collar/generator/models/Parameter.kt @@ -1,8 +1,20 @@ package co.infinum.collar.generator.models +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable internal data class Parameter( + + @SerialName("description") + val description: String? = null, + + @SerialName("name") val name: String, - val description: String, + + @SerialName("type") val type: String, - val values: List? + + @SerialName("values") + val values: List? = null ) diff --git a/generator/src/main/kotlin/co/infinum/collar/generator/models/Property.kt b/generator/src/main/kotlin/co/infinum/collar/generator/models/Property.kt index 4153c84b..bf0529b2 100644 --- a/generator/src/main/kotlin/co/infinum/collar/generator/models/Property.kt +++ b/generator/src/main/kotlin/co/infinum/collar/generator/models/Property.kt @@ -1,7 +1,17 @@ package co.infinum.collar.generator.models +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable internal data class Property( + + @SerialName("description") + val description: String? = null, + + @SerialName("name") val name: String, - val description: String, - val values: List? + + @SerialName("values") + val values: List? = null ) diff --git a/generator/src/main/kotlin/co/infinum/collar/generator/models/Screen.kt b/generator/src/main/kotlin/co/infinum/collar/generator/models/Screen.kt index 54c23794..210a9f48 100644 --- a/generator/src/main/kotlin/co/infinum/collar/generator/models/Screen.kt +++ b/generator/src/main/kotlin/co/infinum/collar/generator/models/Screen.kt @@ -1,6 +1,14 @@ package co.infinum.collar.generator.models +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable internal data class Screen( - val name: String, - val description: String + + @SerialName("description") + val description: String? = null, + + @SerialName("name") + val name: String ) diff --git a/generator/src/main/kotlin/co/infinum/collar/generator/providers/MoshiProvider.kt b/generator/src/main/kotlin/co/infinum/collar/generator/providers/MoshiProvider.kt deleted file mode 100644 index e4bff045..00000000 --- a/generator/src/main/kotlin/co/infinum/collar/generator/providers/MoshiProvider.kt +++ /dev/null @@ -1,13 +0,0 @@ -package co.infinum.collar.generator.providers - -import com.squareup.moshi.Moshi -import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory - -internal object MoshiProvider { - - fun provide(): Lazy = lazy { - Moshi.Builder() - .add(KotlinJsonAdapterFactory()) - .build() - } -} diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index d53029d2..20ba0f3c 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.6-all.zip diff --git a/in_app_notification.jpg b/in_app_notification.jpg new file mode 100644 index 00000000..76b39f02 Binary files /dev/null and b/in_app_notification.jpg differ diff --git a/notification.jpg b/notification.jpg index 9bfd005d..27b18d1c 100644 Binary files a/notification.jpg and b/notification.jpg differ diff --git a/plugin/build.gradle b/plugin/build.gradle index ff46fd1c..672c73e4 100644 --- a/plugin/build.gradle +++ b/plugin/build.gradle @@ -13,6 +13,7 @@ compileTestKotlin { dependencies { implementation packages.kotlin.core + implementation packages.kotlin.reflect implementation packages.collar.generator compileOnly gradleApi() compileOnly packages.gradle diff --git a/plugin/src/main/kotlin/co/infinum/collar/plugin/CollarExtension.kt b/plugin/src/main/kotlin/co/infinum/collar/plugin/CollarExtension.kt index 059fd9de..1220bfe2 100644 --- a/plugin/src/main/kotlin/co/infinum/collar/plugin/CollarExtension.kt +++ b/plugin/src/main/kotlin/co/infinum/collar/plugin/CollarExtension.kt @@ -5,29 +5,19 @@ internal open class CollarExtension { companion object { const val NAME = "collar" - private const val DEFAULT_VERSION = "1.1.5" - private const val DEFAULT_FILEPATH = "" - private const val DEFAULT_VARIANT = "main" + const val DEFAULT_VERSION = "1.1.6" + private const val DEFAULT_FILENAME = "" private const val DEFAULT_PACKAGE_NAME = "" } open var version = DEFAULT_VERSION - open var filePath = DEFAULT_FILEPATH - open var variant = DEFAULT_VARIANT + open var fileName = DEFAULT_FILENAME open var packageName = DEFAULT_PACKAGE_NAME } internal fun CollarExtension.validate(): List { val errors = mutableListOf() - if (filePath.isBlank()) { - errors.add("filePath must be specified") - } - - if (variant.isBlank()) { - errors.add("variant must be specified") - } - if (packageName.isBlank()) { errors.add("packageName must be specified") } diff --git a/plugin/src/main/kotlin/co/infinum/collar/plugin/CollarPlugin.kt b/plugin/src/main/kotlin/co/infinum/collar/plugin/CollarPlugin.kt index 0ab43f8b..88e85744 100644 --- a/plugin/src/main/kotlin/co/infinum/collar/plugin/CollarPlugin.kt +++ b/plugin/src/main/kotlin/co/infinum/collar/plugin/CollarPlugin.kt @@ -1,6 +1,7 @@ package co.infinum.collar.plugin import co.infinum.collar.plugin.tasks.GenerateTask +import com.android.build.gradle.AppExtension import org.gradle.api.Plugin import org.gradle.api.Project import java.net.URI @@ -40,10 +41,19 @@ internal class CollarPlugin : Plugin { private fun addTasks(project: Project) { with(project) { - tasks.create(GenerateTask.NAME, GenerateTask::class.java).run { - group = GenerateTask.GROUP - description = GenerateTask.DESCRIPTION + tasks.register(GenerateTask.NAME, GenerateTask::class.java) { task -> + task.group = GenerateTask.GROUP + task.description = GenerateTask.DESCRIPTION + task.setSource(projectDir) + task.include { + it.name == (extensions.findByName(CollarExtension.NAME) as CollarExtension).fileName + } } + .run { + extensions.findByType(AppExtension::class.java)?.applicationVariants?.all { variant -> + variant.registerJavaGeneratingTask(get(), get().outputDirectory) + } + } } } } diff --git a/plugin/src/main/kotlin/co/infinum/collar/plugin/tasks/GenerateTask.kt b/plugin/src/main/kotlin/co/infinum/collar/plugin/tasks/GenerateTask.kt index 18a4b802..99e9bb74 100644 --- a/plugin/src/main/kotlin/co/infinum/collar/plugin/tasks/GenerateTask.kt +++ b/plugin/src/main/kotlin/co/infinum/collar/plugin/tasks/GenerateTask.kt @@ -2,23 +2,47 @@ package co.infinum.collar.plugin.tasks import co.infinum.collar.generator.CollarGenerator import co.infinum.collar.plugin.CollarExtension -import co.infinum.collar.plugin.tasks.shared.BaseTask +import co.infinum.collar.plugin.tasks.shared.BaseSourceTask import co.infinum.collar.plugin.validate +import com.android.builder.model.AndroidProject.FD_GENERATED +import org.gradle.api.file.FileTree +import org.gradle.api.tasks.CacheableTask +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.InputFiles +import org.gradle.api.tasks.OutputDirectory +import org.gradle.api.tasks.PathSensitive +import org.gradle.api.tasks.PathSensitivity +import org.gradle.api.tasks.SkipWhenEmpty import org.gradle.api.tasks.TaskAction +import java.io.File -internal open class GenerateTask : BaseTask() { +@CacheableTask +internal open class GenerateTask : BaseSourceTask() { companion object { const val GROUP = "collar" const val NAME = "generate" const val DESCRIPTION = "Generates Kotlin files for screen names, events and user properties." - - private const val TEMPLATE_OUTPUT_PATH = "%s/src/%s/kotlin" - private const val TEMPLATE_FILE_PATH = "%s/%s" } + @Suppress("unused") // Required to invalidate the task on version updates. + @Input + val pluginVersion = CollarExtension.DEFAULT_VERSION + + @get:OutputDirectory + var outputDirectory: File = File( + project.buildDir, + "$FD_GENERATED${File.separatorChar}${CollarExtension.NAME}${File.separatorChar}trackingPlan" + ) + private val collarGenerator = CollarGenerator() + @InputFiles + @SkipWhenEmpty + @PathSensitive(PathSensitivity.ABSOLUTE) + override fun getSource(): FileTree = + super.getSource() + @TaskAction fun doOnRun() { (project.extensions.findByName(CollarExtension.NAME) as CollarExtension).run { @@ -26,25 +50,23 @@ internal open class GenerateTask : BaseTask() { when { errors.isNotEmpty() -> showError(errors.joinToString(System.lineSeparator())) else -> { - val outputPath = String.format(TEMPLATE_OUTPUT_PATH, project.projectDir, variant) - val filePath = String.format(TEMPLATE_FILE_PATH, project.projectDir, filePath) - generateTrackingPlan(filePath, outputPath, packageName) + generateTrackingPlan(packageName) } } } } @Suppress("TooGenericExceptionCaught") - private fun generateTrackingPlan(filePath: String, outputPath: String, packageName: String) { + private fun generateTrackingPlan(packageName: String) { try { - println("Tracking plan file path: $filePath") - if (collarGenerator.generate(filePath, outputPath, packageName)) { - println("Tracking plan classes generated on path: $outputPath") + println("Tracking plan file path: ${source.first().absolutePath}") + if (collarGenerator.generate(source.first().absolutePath, outputDirectory.absolutePath, packageName)) { + println("Tracking plan classes generated on path: ${outputDirectory.absolutePath}") } else { showError("Task generate failed") } - } catch (e: Exception) { - showError(e.stackTrace.toString()) + } catch (throwable: Throwable) { + throwable.printStackTrace() } } } diff --git a/plugin/src/main/kotlin/co/infinum/collar/plugin/tasks/shared/BaseTask.kt b/plugin/src/main/kotlin/co/infinum/collar/plugin/tasks/shared/BaseSourceTask.kt similarity index 69% rename from plugin/src/main/kotlin/co/infinum/collar/plugin/tasks/shared/BaseTask.kt rename to plugin/src/main/kotlin/co/infinum/collar/plugin/tasks/shared/BaseSourceTask.kt index e8a0ccc7..bc23aa9c 100644 --- a/plugin/src/main/kotlin/co/infinum/collar/plugin/tasks/shared/BaseTask.kt +++ b/plugin/src/main/kotlin/co/infinum/collar/plugin/tasks/shared/BaseSourceTask.kt @@ -1,9 +1,9 @@ package co.infinum.collar.plugin.tasks.shared -import org.gradle.api.DefaultTask +import org.gradle.api.tasks.SourceTask import org.gradle.api.tasks.TaskExecutionException -internal abstract class BaseTask : DefaultTask() { +internal abstract class BaseSourceTask : SourceTask() { internal fun showError(message: String): Nothing = throw TaskExecutionException(this, Exception(message)) } diff --git a/processor/build.gradle b/processor/build.gradle index 265acb0e..3c919b6e 100644 --- a/processor/build.gradle +++ b/processor/build.gradle @@ -7,6 +7,7 @@ dependencies { implementation packages.kotlin.core implementation packages.kotlin.poet_metadata implementation packages.kotlin.poet + implementation packages.kotlin.reflect compileOnly packages.incap.core kapt packages.incap.processor diff --git a/processor/src/main/kotlin/co/infinum/collar/processor/shared/Constants.kt b/processor/src/main/kotlin/co/infinum/collar/processor/shared/Constants.kt index b82f7c4d..04be31e3 100644 --- a/processor/src/main/kotlin/co/infinum/collar/processor/shared/Constants.kt +++ b/processor/src/main/kotlin/co/infinum/collar/processor/shared/Constants.kt @@ -4,11 +4,11 @@ import com.squareup.kotlinpoet.ClassName internal object Constants { - val CLASS_COMPONENT_ACTIVITY = ClassName("androidx.core.app", "ComponentActivity") - val CLASS_ACTIVITY = ClassName("android.app", "Activity") val CLASS_FRAGMENT = ClassName("android.app", "Fragment") val CLASS_SUPPORT_FRAGMENT = ClassName("android.support.v4.app", "Fragment") - val CLASS_ANDROIDX_FRAGMENT = ClassName("androidx.fragment.app", "Fragment") + private val CLASS_COMPONENT_ACTIVITY = ClassName("androidx.core.app", "ComponentActivity") + private val CLASS_ACTIVITY = ClassName("android.app", "Activity") + private val CLASS_ANDROIDX_FRAGMENT = ClassName("androidx.fragment.app", "Fragment") val SUPPORTED_SCREEN_NAME_CLASSES = listOf( CLASS_COMPONENT_ACTIVITY, diff --git a/processor/src/main/kotlin/co/infinum/collar/processor/specs/ScreenNameSpec.kt b/processor/src/main/kotlin/co/infinum/collar/processor/specs/ScreenNameSpec.kt index 0baa1b1d..8d68c750 100644 --- a/processor/src/main/kotlin/co/infinum/collar/processor/specs/ScreenNameSpec.kt +++ b/processor/src/main/kotlin/co/infinum/collar/processor/specs/ScreenNameSpec.kt @@ -2,13 +2,9 @@ package co.infinum.collar.processor.specs import co.infinum.collar.processor.extensions.applyIf import co.infinum.collar.processor.models.ScreenHolder -import co.infinum.collar.processor.shared.Constants.CLASS_ACTIVITY -import co.infinum.collar.processor.shared.Constants.CLASS_ANDROIDX_FRAGMENT -import co.infinum.collar.processor.shared.Constants.CLASS_COMPONENT_ACTIVITY import co.infinum.collar.processor.shared.Constants.CLASS_FRAGMENT import co.infinum.collar.processor.shared.Constants.CLASS_SUPPORT_FRAGMENT import com.squareup.kotlinpoet.AnnotationSpec -import com.squareup.kotlinpoet.ClassName import com.squareup.kotlinpoet.CodeBlock import com.squareup.kotlinpoet.FileSpec import com.squareup.kotlinpoet.FunSpec @@ -23,8 +19,7 @@ internal class ScreenNameSpec private constructor( private const val SIMPLE_NAME = "ScreenNames" private const val FUNCTION_NAME = "trackScreen" private const val PARAMETER_NAME = "this" - private const val STATEMENT_ACTIVITY = "is %T -> %T.%L(%L, %S)" - private const val STATEMENT_FRAGMENT = "is %T -> activity?.let { %T.%L(it, %S) }" + private const val STATEMENT = "is %T -> %T.%L(%S)" } internal open class Builder( @@ -61,22 +56,17 @@ internal class ScreenNameSpec private constructor( .receiver(keyClass) .applyIf(holders.isNotEmpty()) { beginControlFlow(CONTROL_FLOW_WHEN, parameterName()) - addCode(screens(keyClass, holders)) + addCode(screens(holders)) endControlFlow() } .build() } - private fun screens(keyClass: ClassName, holders: List): CodeBlock = + private fun screens(holders: List): CodeBlock = CodeBlock.builder() .apply { holders.forEach { - when (keyClass) { - CLASS_COMPONENT_ACTIVITY -> addActivityStatement(this, it) - CLASS_ACTIVITY -> addActivityStatement(this, it) - CLASS_ANDROIDX_FRAGMENT -> addFragmentStatement(this, it) - else -> addFragmentStatement(this, it) - } + addStatement(this, it) } } .build() @@ -93,19 +83,9 @@ internal class ScreenNameSpec private constructor( .addMember(CodeBlock.of("%S", "DEPRECATION")) .build() - private fun addActivityStatement(builder: CodeBlock.Builder, holder: ScreenHolder) = - builder.addStatement( - STATEMENT_ACTIVITY, - holder.className, - CLASS_COLLAR, - functionName(), - parameterName(), - holder.screenName - ) - - private fun addFragmentStatement(builder: CodeBlock.Builder, holder: ScreenHolder) = + private fun addStatement(builder: CodeBlock.Builder, holder: ScreenHolder) = builder.addStatement( - STATEMENT_FRAGMENT, + STATEMENT, holder.className, CLASS_COLLAR, functionName(), diff --git a/redacted_notification.jpg b/redacted_notification.jpg new file mode 100644 index 00000000..a73c7d1e Binary files /dev/null and b/redacted_notification.jpg differ diff --git a/redacted_ui.jpg b/redacted_ui.jpg new file mode 100644 index 00000000..a8023807 Binary files /dev/null and b/redacted_ui.jpg differ diff --git a/sample/build.gradle b/sample/build.gradle index d7af1b9a..a3db41bf 100644 --- a/sample/build.gradle +++ b/sample/build.gradle @@ -23,8 +23,8 @@ android { applicationId "co.infinum.collar.sample" minSdkVersion buildConfig.minSdk targetSdkVersion buildConfig.targetSdk - versionCode 10105 - versionName "1.1.5" + versionCode 10106 + versionName "1.1.6" buildTypes { debug { @@ -55,7 +55,7 @@ android { test.java.srcDirs += 'src/test/kotlin' } - viewBinding.enabled = true + buildFeatures.viewBinding = true } dependencies { @@ -67,7 +67,7 @@ dependencies { } collar { - version "1.1.5" - filePath = "example.json" - packageName = "co.infinum.collar.sample.analytics.generated" -} \ No newline at end of file + version "1.1.6" + fileName = "example.json" + packageName = "co.infinum.collar.sample.analytics.trackingplan" +} diff --git a/sample/example.json b/sample/example.json index 29964379..fbe9162b 100644 --- a/sample/example.json +++ b/sample/example.json @@ -1,83 +1,181 @@ { "events": [ { - "name": "loginUser", - "description": "User clicked on login button", - "parameters": [ + "name": "user_log_in", + "description": "User logged into the app", + "properties": [ { - "name": "languageType", - "description": "Type of login clicked", + "name": "type", + "description": "Type of log in", "type": "text", "values": [ - "kotlin", - "java" + "facebook", + "google", + "in app" + ] + }, + { + "name": "source", + "description": "Source of the log in", + "type": "text", + "values": [ + "onboarding", + "settings" ] } ] }, { - "name": "checkTypes", - "description": "We need to check types here", - "parameters": [ + "name": "analytics_consent", + "description": "User opts in or opts out for analytics", + "properties": [ { - "name": "checkBool", - "description": "checki if bool is ok", - "type": "boolean" + "name": "type", + "description": "Information if a user gave consent", + "type": "text", + "values": [ + "opt in", + "opt out" + ] }, { - "name": "checkText", - "description": "check if text is ok", - "type": "text" + "name": "source", + "description": "Source where a user gave or declined analytics consent", + "type": "text", + "values": [ + "onboarding", + "settings" + ] + } + ] + }, + { + "name": "philips_network_join", + "description": "User successfully completed connection or failed to connect to the Philips network", + "properties": [ + { + "name": "status", + "description": "Status of joining to the Philips network", + "type": "text", + "values": [ + "success", + "fail" + ] }, { - "name": "checkNumber", - "description": "check if number is ok", - "type": "number" + "name": "source", + "description": "Part of the app from which a user joined Philips network", + "type": "text", + "values": [ + "onboarding", + "home" + ] + } + ] + }, + { + "name": "machine_phone_pair", + "description": "User successfully completed pairing or failed to pair phone with the coffee machine", + "properties": [ + { + "name": "status", + "description": "Status of phone pairing to the Coffee machine", + "type": "text", + "values": [ + "success", + "fail" + ] }, { - "name": "checkDecimal", - "description": "check if decimal is ok", - "type": "decimal" + "name": "source", + "description": "Source of the pairing", + "type": "text", + "values": [ + "onboarding", + "home" + ] } ] - } - ], - "userProperties": [ + }, { - "name": "price", - "description": "user's price" + "name": "machine_wifi_connect", + "description": "User successfully completed connection or failed connect Coffee machine to the local WiFi network", + "properties": [ + { + "name": "status", + "description": "Status of the Coffee machine connecting to the WiFi network", + "type": "text", + "values": [ + "success", + "fail" + ] + }, + { + "name": "source", + "description": "Source of the machine connecting to the WiFi network", + "type": "text", + "values": [ + "onboarding", + "home" + ] + } + ] }, { - "name": "genderGo", - "values": [ - "male", - "female", - "unknown" - ], - "description": "..." + "name": "machine_turn_on", + "description": "User turned on the Coffee machine from the app" }, { - "name": "userType", - "values": [ - "corporate", - "retail", - "unknown" - ], - "description": "..." + "name": "machine_turn_off", + "description": "User turned off the Coffee machine from the app" + }, + { + "name": "machine_remove", + "description": "User removed Coffee machine from the app" }, { - "name": "userId", - "description": "..." + "name": "alexa_connect", + "description": "User connects or fails to connect Alexa", + "properties": [ + { + "name": "status", + "description": "Status of connecting Alexa", + "type": "text", + "values": [ + "success", + "fail" + ] + }, + { + "name": "source", + "description": "Source of connecting Alexa", + "type": "text", + "values": [ + "onboarding", + "settings" + ] + } + ] } ], - "screens": [ + "userProperties": [ { - "name": "Main Screen", - "description": "Main screen example" + "name": "machine_model", + "description": "Model of the Coffee machine (e.g. Philips 3200 LatteGo)" }, { - "name": "Child Screen", - "description": "child screen example" + "name": "analytics_consent", + "values": [ + "opt in", + "opt out" + ], + "description": "User segmentation by the analytics consent (opt in/opt out). Should be set and updated in two locations, onboarding and settings" + } + ], + "screens": [ + { + "name": "home", + "description": "Home screen viewed" } ] } \ No newline at end of file diff --git a/sample/src/main/java/co/infinum/collar/sample/SampleApplication.java b/sample/src/main/java/co/infinum/collar/sample/SampleApplication.java index 802bbc64..85afa45a 100644 --- a/sample/src/main/java/co/infinum/collar/sample/SampleApplication.java +++ b/sample/src/main/java/co/infinum/collar/sample/SampleApplication.java @@ -5,10 +5,14 @@ import org.jetbrains.annotations.NotNull; +import java.util.Set; + +import androidx.collection.ArraySet; import co.infinum.collar.Collar; import co.infinum.collar.Event; import co.infinum.collar.Property; import co.infinum.collar.Screen; +import co.infinum.collar.ui.Configuration; import co.infinum.collar.ui.LiveCollector; public class SampleApplication extends Application { @@ -21,7 +25,9 @@ public void onCreate() { } private void attachCollar() { - Collar.attach(new LiveCollector(true, true) { + final Set redactedWords = new ArraySet<>(1); + redactedWords.add("Java"); + Collar.attach(new LiveCollector(new Configuration(true, true, redactedWords)) { @Override public void onScreen(@NotNull Screen screen) { diff --git a/sample/src/main/kotlin/co/infinum/collar/sample/KotlinMainActivity.kt b/sample/src/main/kotlin/co/infinum/collar/sample/KotlinMainActivity.kt index 13eaf771..dadb120a 100644 --- a/sample/src/main/kotlin/co/infinum/collar/sample/KotlinMainActivity.kt +++ b/sample/src/main/kotlin/co/infinum/collar/sample/KotlinMainActivity.kt @@ -4,10 +4,11 @@ import android.app.Activity import android.content.Intent import android.os.Bundle import co.infinum.collar.annotations.ScreenName +import co.infinum.collar.sample.analytics.trackingplan.TrackingPlanScreens import co.infinum.collar.sample.databinding.ActivityMainKotlinBinding import co.infinum.collar.trackScreen -@ScreenName(value = KotlinScreenNames.MAIN_SCREEN, enabled = true) +@ScreenName(value = TrackingPlanScreens.HOME, enabled = true) class KotlinMainActivity : Activity() { override fun onCreate(savedInstanceState: Bundle?) { diff --git a/ui-no-op/build.gradle b/ui-no-op/build.gradle index 6ee72066..22b36dfc 100644 --- a/ui-no-op/build.gradle +++ b/ui-no-op/build.gradle @@ -28,7 +28,7 @@ android { test.java.srcDirs += 'src/test/kotlin' } - viewBinding.enabled = true + buildFeatures.viewBinding = true } dependencies { diff --git a/ui-no-op/src/main/kotlin/co/infinum/collar/ui/CollarUi.kt b/ui-no-op/src/main/kotlin/co/infinum/collar/ui/CollarUi.kt index b2e102ba..2a8db921 100644 --- a/ui-no-op/src/main/kotlin/co/infinum/collar/ui/CollarUi.kt +++ b/ui-no-op/src/main/kotlin/co/infinum/collar/ui/CollarUi.kt @@ -2,11 +2,20 @@ package co.infinum.collar.ui import android.content.Intent +/** + * No operation singleton object for UI entry point with convenience methods + */ object CollarUi { + /** + * No operation stub that creates an empty Intent. + */ @JvmStatic fun launchIntent() = Intent() + /** + * No operation stub that does nothing. + */ @JvmStatic fun show() = Unit } diff --git a/ui-no-op/src/main/kotlin/co/infinum/collar/ui/LiveCollector.kt b/ui-no-op/src/main/kotlin/co/infinum/collar/ui/LiveCollector.kt index ea7b4f0d..75b903a4 100644 --- a/ui-no-op/src/main/kotlin/co/infinum/collar/ui/LiveCollector.kt +++ b/ui-no-op/src/main/kotlin/co/infinum/collar/ui/LiveCollector.kt @@ -6,18 +6,40 @@ import co.infinum.collar.Event import co.infinum.collar.Property import co.infinum.collar.Screen +/** + * No operation stub that does nothing. + * Parameters are unused. + * + * @param showSystemNotifications is false by default. + * @param showInAppNotifications is false by default. + */ @Suppress("UNUSED_PARAMETER") open class LiveCollector( showSystemNotifications: Boolean = false, showInAppNotifications: Boolean = false ) : Collector { + /** + * No operation stub that does nothing. + * + * @param screen is unused. + */ @CallSuper override fun onScreen(screen: Screen) = Unit + /** + * No operation stub that does nothing. + * + * @param event is unused. + */ @CallSuper override fun onEvent(event: Event) = Unit + /** + * No operation stub that does nothing. + * + * @param property is unused. + */ @CallSuper override fun onProperty(property: Property) = Unit } diff --git a/ui.jpg b/ui.jpg index e1f33831..9f17d7b1 100644 Binary files a/ui.jpg and b/ui.jpg differ diff --git a/ui/build.gradle b/ui/build.gradle index 1f4e6db7..018620f5 100644 --- a/ui/build.gradle +++ b/ui/build.gradle @@ -46,7 +46,7 @@ android { it.java.srcDirs += "src/$it.name/kotlin" } - viewBinding.enabled = true + buildFeatures.viewBinding = true } dependencies { diff --git a/ui/src/main/kotlin/co/infinum/collar/ui/CollarUi.kt b/ui/src/main/kotlin/co/infinum/collar/ui/CollarUi.kt index 7ee1629a..5f8849b8 100644 --- a/ui/src/main/kotlin/co/infinum/collar/ui/CollarUi.kt +++ b/ui/src/main/kotlin/co/infinum/collar/ui/CollarUi.kt @@ -2,11 +2,22 @@ package co.infinum.collar.ui import co.infinum.collar.ui.presentation.Presentation +/** + * Singleton object for UI entry point with convenience methods + */ object CollarUi { + /** + * Creates and prepares an Intent for launching UI. + * This intent starts CollarActivity and applies Intent.FLAG_ACTIVITY_NEW_TASK flag. + */ @JvmStatic fun launchIntent() = Presentation.launchIntent() + /** + * Directly shows CollarActivity by previously prepared Intent. + * This intent applies Intent.FLAG_ACTIVITY_NEW_TASK flag. + */ @JvmStatic fun show() = Presentation.show() } diff --git a/ui/src/main/kotlin/co/infinum/collar/ui/Configuration.kt b/ui/src/main/kotlin/co/infinum/collar/ui/Configuration.kt new file mode 100644 index 00000000..0668a06b --- /dev/null +++ b/ui/src/main/kotlin/co/infinum/collar/ui/Configuration.kt @@ -0,0 +1,10 @@ +package co.infinum.collar.ui + +data class Configuration( + + val showSystemNotifications: Boolean = true, + + val showInAppNotifications: Boolean = true, + + val redactedKeywords: Set = setOf() +) diff --git a/ui/src/main/kotlin/co/infinum/collar/ui/LiveCollector.kt b/ui/src/main/kotlin/co/infinum/collar/ui/LiveCollector.kt index 8a65d28e..732f3fe0 100644 --- a/ui/src/main/kotlin/co/infinum/collar/ui/LiveCollector.kt +++ b/ui/src/main/kotlin/co/infinum/collar/ui/LiveCollector.kt @@ -10,22 +10,29 @@ import co.infinum.collar.ui.data.models.local.EntityType import co.infinum.collar.ui.data.models.local.SettingsEntity import co.infinum.collar.ui.domain.repositories.EntityRepository import co.infinum.collar.ui.domain.repositories.SettingsRepository +import co.infinum.collar.ui.extensions.redact import co.infinum.collar.ui.presentation.BundleMapper import co.infinum.collar.ui.presentation.Presentation import co.infinum.collar.ui.presentation.notifications.inapp.InAppNotificationProvider import co.infinum.collar.ui.presentation.notifications.system.SystemNotificationProvider -open class LiveCollector( - showSystemNotifications: Boolean = true, - showInAppNotifications: Boolean = true +/** + * Implementation of Collector interface providing UI for collected screen name, analytics event or user property. + * + * @param showSystemNotifications is true by default. + * @param showInAppNotifications is true by default. + * @constructor Default values are provided. + */ +open class LiveCollector constructor( + private val configuration: Configuration = Configuration() ) : Collector { private val systemNotificationProvider: SystemNotificationProvider = Presentation.systemNotification() private val inAppNotificationProvider: InAppNotificationProvider = Presentation.inAppNotification() private var settings = SettingsEntity( - showSystemNotifications = showSystemNotifications, - showInAppNotifications = showInAppNotifications + showSystemNotifications = configuration.showSystemNotifications, + showInAppNotifications = configuration.showInAppNotifications ) init { @@ -40,11 +47,16 @@ open class LiveCollector( } } + /** + * Invoked when a new screen is emitted. + * + * @param screen wrapper class. + */ @CallSuper override fun onScreen(screen: Screen) { val entity = CollarEntity( type = EntityType.SCREEN, - name = screen.name + name = screen.name.redact(configuration.redactedKeywords) ) EntityRepository.saveScreen(entity) if (settings.showSystemNotifications) { @@ -55,12 +67,17 @@ open class LiveCollector( } } + /** + * Invoked when a new analytics event is emitted. + * + * @param event wrapper class. + */ @CallSuper override fun onEvent(event: Event) { val entity = CollarEntity( type = EntityType.EVENT, - name = event.name, - parameters = event.params?.let { BundleMapper.toMap(it) } + name = event.name.redact(configuration.redactedKeywords), + parameters = event.params?.let { BundleMapper.toMap(it, configuration.redactedKeywords) } ) EntityRepository.saveEvent(entity) if (settings.showSystemNotifications) { @@ -71,12 +88,17 @@ open class LiveCollector( } } + /** + * Invoked when a new user property is emitted. + * + * @param property wrapper class. + */ @CallSuper override fun onProperty(property: Property) { val entity = CollarEntity( type = EntityType.PROPERTY, - name = property.name, - value = property.value + name = property.name.redact(configuration.redactedKeywords), + value = property.value?.redact(configuration.redactedKeywords) ) EntityRepository.saveProperty(entity) if (settings.showSystemNotifications) { diff --git a/ui/src/main/kotlin/co/infinum/collar/ui/domain/Domain.kt b/ui/src/main/kotlin/co/infinum/collar/ui/domain/Domain.kt index 908902ff..28852959 100644 --- a/ui/src/main/kotlin/co/infinum/collar/ui/domain/Domain.kt +++ b/ui/src/main/kotlin/co/infinum/collar/ui/domain/Domain.kt @@ -3,7 +3,7 @@ package co.infinum.collar.ui.domain import android.content.Context import co.infinum.collar.ui.data.Data -object Domain { +internal object Domain { fun initialise(context: Context) = Data.initialise(context) } diff --git a/ui/src/main/kotlin/co/infinum/collar/ui/extensions/String.kt b/ui/src/main/kotlin/co/infinum/collar/ui/extensions/String.kt new file mode 100644 index 00000000..19a5c35e --- /dev/null +++ b/ui/src/main/kotlin/co/infinum/collar/ui/extensions/String.kt @@ -0,0 +1,6 @@ +package co.infinum.collar.ui.extensions + +fun String.redact(keywords: Set): String = + this.replace( + Regex(keywords.joinToString("|") { it }) + ) { result -> "".padEnd(result.value.length, '•') } diff --git a/ui/src/main/kotlin/co/infinum/collar/ui/presentation/BundleMapper.kt b/ui/src/main/kotlin/co/infinum/collar/ui/presentation/BundleMapper.kt index cd9568cb..077a18b1 100644 --- a/ui/src/main/kotlin/co/infinum/collar/ui/presentation/BundleMapper.kt +++ b/ui/src/main/kotlin/co/infinum/collar/ui/presentation/BundleMapper.kt @@ -3,11 +3,12 @@ package co.infinum.collar.ui.presentation import android.os.Bundle import android.util.Log import co.infinum.collar.ui.CollarUi +import co.infinum.collar.ui.extensions.redact @Suppress("ComplexMethod") internal object BundleMapper { - fun toMap(bundle: Bundle): String { + fun toMap(bundle: Bundle, redactedKeywords: Set): String { val map = mutableMapOf() val keys: Set = bundle.keySet() @@ -29,7 +30,7 @@ internal object BundleMapper { is Short -> bundle.getShort(key).toString() is CharSequence -> bundle.getCharSequence(key).toString() is Bundle -> toMap(bundle.getBundle(key) - ?: Bundle.EMPTY) + ?: Bundle.EMPTY, redactedKeywords) else -> { Log.w( CollarUi.javaClass.simpleName, @@ -38,7 +39,7 @@ internal object BundleMapper { "" } } - }.orEmpty() + }.orEmpty().redact(redactedKeywords) } return map.toList().joinToString("\n") { "${it.first} = ${it.second}" } } diff --git a/ui/src/main/kotlin/co/infinum/collar/ui/presentation/CollarActivity.kt b/ui/src/main/kotlin/co/infinum/collar/ui/presentation/CollarActivity.kt index 2f054659..a85d0bb7 100644 --- a/ui/src/main/kotlin/co/infinum/collar/ui/presentation/CollarActivity.kt +++ b/ui/src/main/kotlin/co/infinum/collar/ui/presentation/CollarActivity.kt @@ -12,7 +12,6 @@ import androidx.core.app.ShareCompat import androidx.core.view.isGone import androidx.core.view.isVisible import androidx.lifecycle.ViewModelProvider -import androidx.lifecycle.observe import androidx.recyclerview.widget.LinearLayoutManager import co.infinum.collar.ui.R import co.infinum.collar.ui.data.models.local.CollarEntity @@ -167,9 +166,9 @@ internal class CollarActivity : AppCompatActivity() { detailDialog = MaterialAlertDialogBuilder(this) .setIcon( when (entity.type) { - EntityType.SCREEN -> R.drawable.collar_ic_screen_detail - EntityType.EVENT -> R.drawable.collar_ic_event_detail - EntityType.PROPERTY -> R.drawable.collar_ic_property_detail + EntityType.SCREEN -> R.drawable.collar_ic_screen + EntityType.EVENT -> R.drawable.collar_ic_event + EntityType.PROPERTY -> R.drawable.collar_ic_property else -> 0 } ) diff --git a/ui/src/main/kotlin/co/infinum/collar/ui/presentation/notifications/inapp/InAppNotificationProvider.kt b/ui/src/main/kotlin/co/infinum/collar/ui/presentation/notifications/inapp/InAppNotificationProvider.kt index 0cd6a7fe..d3e63c68 100644 --- a/ui/src/main/kotlin/co/infinum/collar/ui/presentation/notifications/inapp/InAppNotificationProvider.kt +++ b/ui/src/main/kotlin/co/infinum/collar/ui/presentation/notifications/inapp/InAppNotificationProvider.kt @@ -4,6 +4,7 @@ import android.app.Activity import android.app.Application import android.content.Context import android.view.View +import androidx.annotation.ColorRes import androidx.annotation.DrawableRes import androidx.core.app.ShareCompat import co.infinum.collar.ui.R @@ -33,8 +34,8 @@ internal class InAppNotificationProvider( override fun showScreen(entity: CollarEntity) { buildNotification( callbacks.currentActivity, - R.drawable.collar_shape_background_screen, - R.drawable.collar_ic_screen, + R.color.collar_color_screen, + R.drawable.collar_ic_screen_white, entity ) } @@ -42,8 +43,8 @@ internal class InAppNotificationProvider( override fun showEvent(entity: CollarEntity) { buildNotification( callbacks.currentActivity, - R.drawable.collar_shape_background_event, - R.drawable.collar_ic_event, + R.color.collar_color_event, + R.drawable.collar_ic_event_white, entity ) } @@ -51,21 +52,21 @@ internal class InAppNotificationProvider( override fun showProperty(entity: CollarEntity) { buildNotification( callbacks.currentActivity, - R.drawable.collar_shape_background_property, - R.drawable.collar_ic_property, + R.color.collar_color_property, + R.drawable.collar_ic_property_white, entity ) } private fun buildNotification( activity: Activity?, - @DrawableRes background: Int, + @ColorRes backgroundTint: Int, @DrawableRes icon: Int, entity: CollarEntity ) { CollarSnackbar.make( activity?.findViewById(android.R.id.content), - background, + backgroundTint, icon, entity.name, entity.parameters, diff --git a/ui/src/main/kotlin/co/infinum/collar/ui/presentation/notifications/inapp/snackbar/CollarSnackbar.kt b/ui/src/main/kotlin/co/infinum/collar/ui/presentation/notifications/inapp/snackbar/CollarSnackbar.kt index da654501..c1ad9359 100644 --- a/ui/src/main/kotlin/co/infinum/collar/ui/presentation/notifications/inapp/snackbar/CollarSnackbar.kt +++ b/ui/src/main/kotlin/co/infinum/collar/ui/presentation/notifications/inapp/snackbar/CollarSnackbar.kt @@ -3,6 +3,7 @@ package co.infinum.collar.ui.presentation.notifications.inapp.snackbar import android.view.View import android.view.ViewGroup import android.widget.FrameLayout +import androidx.annotation.ColorRes import androidx.annotation.DrawableRes import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.core.content.ContextCompat @@ -25,7 +26,7 @@ internal class CollarSnackbar( @Suppress("LongParameterList") fun make( parentLayout: FrameLayout?, - @DrawableRes background: Int, + @ColorRes backgroundTint: Int, @DrawableRes icon: Int, title: String?, message: String?, @@ -35,7 +36,7 @@ internal class CollarSnackbar( parentLayout?.let { CollarSnackbarView(it.context) .apply { - setIconBackgroundResource(background) + setIconBackgroundTint(backgroundTint) setIconResource(icon) setTitle(title) setValue(message) diff --git a/ui/src/main/kotlin/co/infinum/collar/ui/presentation/notifications/inapp/snackbar/CollarSnackbarView.kt b/ui/src/main/kotlin/co/infinum/collar/ui/presentation/notifications/inapp/snackbar/CollarSnackbarView.kt index 16c7a671..8ca40e67 100644 --- a/ui/src/main/kotlin/co/infinum/collar/ui/presentation/notifications/inapp/snackbar/CollarSnackbarView.kt +++ b/ui/src/main/kotlin/co/infinum/collar/ui/presentation/notifications/inapp/snackbar/CollarSnackbarView.kt @@ -1,10 +1,13 @@ package co.infinum.collar.ui.presentation.notifications.inapp.snackbar import android.content.Context +import android.content.res.ColorStateList import android.util.AttributeSet import android.view.LayoutInflater import android.widget.FrameLayout +import androidx.annotation.ColorRes import androidx.annotation.DrawableRes +import androidx.core.content.ContextCompat import co.infinum.collar.ui.CollarUi import co.infinum.collar.ui.databinding.CollarViewSnackbarBinding import com.google.android.material.snackbar.ContentViewCallback @@ -45,8 +48,8 @@ internal class CollarSnackbarView @JvmOverloads constructor( } } - fun setIconBackgroundResource(@DrawableRes drawableResId: Int) { - viewBinding.iconView.setBackgroundResource(drawableResId) + fun setIconBackgroundTint(@ColorRes colorResId: Int) { + viewBinding.iconView.backgroundTintList = ColorStateList.valueOf(ContextCompat.getColor(context, colorResId)) } fun setIconResource(@DrawableRes drawableResId: Int) { diff --git a/ui/src/main/res/drawable-night/collar_ic_clear.xml b/ui/src/main/res/drawable-night/collar_ic_clear.xml deleted file mode 100644 index a6187c8f..00000000 --- a/ui/src/main/res/drawable-night/collar_ic_clear.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - \ No newline at end of file diff --git a/ui/src/main/res/drawable-night/collar_ic_event_menu.xml b/ui/src/main/res/drawable-night/collar_ic_event_menu.xml deleted file mode 100644 index fda59c6b..00000000 --- a/ui/src/main/res/drawable-night/collar_ic_event_menu.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/ui/src/main/res/drawable-night/collar_ic_filter.xml b/ui/src/main/res/drawable-night/collar_ic_filter.xml deleted file mode 100644 index e4fc8b68..00000000 --- a/ui/src/main/res/drawable-night/collar_ic_filter.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/ui/src/main/res/drawable-night/collar_ic_logo.xml b/ui/src/main/res/drawable-night/collar_ic_logo.xml deleted file mode 100644 index a4871dd3..00000000 --- a/ui/src/main/res/drawable-night/collar_ic_logo.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/ui/src/main/res/drawable-night/collar_ic_notifications_inapp.xml b/ui/src/main/res/drawable-night/collar_ic_notifications_inapp.xml deleted file mode 100644 index 8e67d434..00000000 --- a/ui/src/main/res/drawable-night/collar_ic_notifications_inapp.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - diff --git a/ui/src/main/res/drawable-night/collar_ic_notifications_system.xml b/ui/src/main/res/drawable-night/collar_ic_notifications_system.xml deleted file mode 100644 index 02f99be9..00000000 --- a/ui/src/main/res/drawable-night/collar_ic_notifications_system.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - \ No newline at end of file diff --git a/ui/src/main/res/drawable-night/collar_ic_property_detail.xml b/ui/src/main/res/drawable-night/collar_ic_property_detail.xml deleted file mode 100644 index 0d1e755c..00000000 --- a/ui/src/main/res/drawable-night/collar_ic_property_detail.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/ui/src/main/res/drawable-night/collar_ic_screen_detail.xml b/ui/src/main/res/drawable-night/collar_ic_screen_detail.xml deleted file mode 100644 index 3b85cd49..00000000 --- a/ui/src/main/res/drawable-night/collar_ic_screen_detail.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/ui/src/main/res/drawable-night/collar_ic_search.xml b/ui/src/main/res/drawable-night/collar_ic_search.xml deleted file mode 100644 index eb90d030..00000000 --- a/ui/src/main/res/drawable-night/collar_ic_search.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/ui/src/main/res/drawable-night/collar_ic_settings.xml b/ui/src/main/res/drawable-night/collar_ic_settings.xml deleted file mode 100644 index 9e65421f..00000000 --- a/ui/src/main/res/drawable-night/collar_ic_settings.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/ui/src/main/res/drawable/collar_decoration_dot.xml b/ui/src/main/res/drawable/collar_decoration_dot.xml index a944c14a..06403dfb 100644 --- a/ui/src/main/res/drawable/collar_decoration_dot.xml +++ b/ui/src/main/res/drawable/collar_decoration_dot.xml @@ -2,10 +2,9 @@ - + - + \ No newline at end of file diff --git a/ui/src/main/res/drawable/collar_ic_event.xml b/ui/src/main/res/drawable/collar_ic_event.xml index c1940817..97550394 100644 --- a/ui/src/main/res/drawable/collar_ic_event.xml +++ b/ui/src/main/res/drawable/collar_ic_event.xml @@ -4,6 +4,6 @@ android:viewportWidth="24" android:viewportHeight="24"> diff --git a/ui/src/main/res/drawable/collar_ic_event_detail.xml b/ui/src/main/res/drawable/collar_ic_event_detail.xml deleted file mode 100644 index 1589aa3b..00000000 --- a/ui/src/main/res/drawable/collar_ic_event_detail.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/ui/src/main/res/drawable/collar_ic_event_menu.xml b/ui/src/main/res/drawable/collar_ic_event_menu.xml deleted file mode 100644 index 1589aa3b..00000000 --- a/ui/src/main/res/drawable/collar_ic_event_menu.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/ui/src/main/res/drawable-night/collar_ic_event_detail.xml b/ui/src/main/res/drawable/collar_ic_event_white.xml similarity index 92% rename from ui/src/main/res/drawable-night/collar_ic_event_detail.xml rename to ui/src/main/res/drawable/collar_ic_event_white.xml index fda59c6b..e70924ef 100644 --- a/ui/src/main/res/drawable-night/collar_ic_event_detail.xml +++ b/ui/src/main/res/drawable/collar_ic_event_white.xml @@ -4,6 +4,6 @@ android:viewportWidth="24" android:viewportHeight="24"> diff --git a/ui/src/main/res/drawable/collar_ic_filter.xml b/ui/src/main/res/drawable/collar_ic_filter.xml index 129f4a87..c70623cd 100644 --- a/ui/src/main/res/drawable/collar_ic_filter.xml +++ b/ui/src/main/res/drawable/collar_ic_filter.xml @@ -4,6 +4,6 @@ android:viewportWidth="24" android:viewportHeight="24"> diff --git a/ui/src/main/res/drawable/collar_ic_logo.xml b/ui/src/main/res/drawable/collar_ic_logo.xml index 9eac82c0..193416e2 100644 --- a/ui/src/main/res/drawable/collar_ic_logo.xml +++ b/ui/src/main/res/drawable/collar_ic_logo.xml @@ -4,7 +4,7 @@ android:viewportWidth="24" android:viewportHeight="24"> diff --git a/ui/src/main/res/drawable/collar_ic_notification.xml b/ui/src/main/res/drawable/collar_ic_notification.xml index f52f4def..4792268d 100644 --- a/ui/src/main/res/drawable/collar_ic_notification.xml +++ b/ui/src/main/res/drawable/collar_ic_notification.xml @@ -5,8 +5,5 @@ android:viewportHeight="24"> + android:fillColor="@android:color/white"/> diff --git a/ui/src/main/res/drawable/collar_ic_notifications_inapp.xml b/ui/src/main/res/drawable/collar_ic_notifications_inapp.xml index 08ddaa40..61e63bc4 100644 --- a/ui/src/main/res/drawable/collar_ic_notifications_inapp.xml +++ b/ui/src/main/res/drawable/collar_ic_notifications_inapp.xml @@ -3,16 +3,10 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - - + + diff --git a/ui/src/main/res/drawable/collar_ic_notifications_system.xml b/ui/src/main/res/drawable/collar_ic_notifications_system.xml index 76d029b6..905eac6b 100644 --- a/ui/src/main/res/drawable/collar_ic_notifications_system.xml +++ b/ui/src/main/res/drawable/collar_ic_notifications_system.xml @@ -4,6 +4,6 @@ android:viewportWidth="24" android:viewportHeight="24"> \ No newline at end of file diff --git a/ui/src/main/res/drawable/collar_ic_property.xml b/ui/src/main/res/drawable/collar_ic_property.xml index 654160fb..8e2d92a4 100644 --- a/ui/src/main/res/drawable/collar_ic_property.xml +++ b/ui/src/main/res/drawable/collar_ic_property.xml @@ -4,6 +4,6 @@ android:viewportWidth="24" android:viewportHeight="24"> diff --git a/ui/src/main/res/drawable/collar_ic_property_detail.xml b/ui/src/main/res/drawable/collar_ic_property_detail.xml deleted file mode 100644 index 07185e65..00000000 --- a/ui/src/main/res/drawable/collar_ic_property_detail.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/ui/src/main/res/drawable/collar_ic_property_menu.xml b/ui/src/main/res/drawable/collar_ic_property_menu.xml deleted file mode 100644 index 07185e65..00000000 --- a/ui/src/main/res/drawable/collar_ic_property_menu.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/ui/src/main/res/drawable-night/collar_ic_property_menu.xml b/ui/src/main/res/drawable/collar_ic_property_white.xml similarity index 91% rename from ui/src/main/res/drawable-night/collar_ic_property_menu.xml rename to ui/src/main/res/drawable/collar_ic_property_white.xml index 0d1e755c..d58e56fc 100644 --- a/ui/src/main/res/drawable-night/collar_ic_property_menu.xml +++ b/ui/src/main/res/drawable/collar_ic_property_white.xml @@ -4,6 +4,6 @@ android:viewportWidth="24" android:viewportHeight="24"> diff --git a/ui/src/main/res/drawable/collar_ic_screen.xml b/ui/src/main/res/drawable/collar_ic_screen.xml index 125538b1..c3895b0a 100644 --- a/ui/src/main/res/drawable/collar_ic_screen.xml +++ b/ui/src/main/res/drawable/collar_ic_screen.xml @@ -4,6 +4,6 @@ android:viewportWidth="24" android:viewportHeight="24"> diff --git a/ui/src/main/res/drawable/collar_ic_screen_detail.xml b/ui/src/main/res/drawable/collar_ic_screen_detail.xml deleted file mode 100644 index 15e9e55b..00000000 --- a/ui/src/main/res/drawable/collar_ic_screen_detail.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/ui/src/main/res/drawable/collar_ic_screen_menu.xml b/ui/src/main/res/drawable/collar_ic_screen_menu.xml deleted file mode 100644 index 15e9e55b..00000000 --- a/ui/src/main/res/drawable/collar_ic_screen_menu.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/ui/src/main/res/drawable-night/collar_ic_screen_menu.xml b/ui/src/main/res/drawable/collar_ic_screen_white.xml similarity index 86% rename from ui/src/main/res/drawable-night/collar_ic_screen_menu.xml rename to ui/src/main/res/drawable/collar_ic_screen_white.xml index 3b85cd49..d32a6551 100644 --- a/ui/src/main/res/drawable-night/collar_ic_screen_menu.xml +++ b/ui/src/main/res/drawable/collar_ic_screen_white.xml @@ -4,6 +4,6 @@ android:viewportWidth="24" android:viewportHeight="24"> diff --git a/ui/src/main/res/drawable/collar_ic_search.xml b/ui/src/main/res/drawable/collar_ic_search.xml index 2145a68a..d22246e2 100644 --- a/ui/src/main/res/drawable/collar_ic_search.xml +++ b/ui/src/main/res/drawable/collar_ic_search.xml @@ -4,6 +4,6 @@ android:viewportWidth="24" android:viewportHeight="24"> diff --git a/ui/src/main/res/drawable/collar_ic_settings.xml b/ui/src/main/res/drawable/collar_ic_settings.xml index 597cbed7..fac13892 100644 --- a/ui/src/main/res/drawable/collar_ic_settings.xml +++ b/ui/src/main/res/drawable/collar_ic_settings.xml @@ -4,6 +4,6 @@ android:viewportWidth="24" android:viewportHeight="24"> diff --git a/ui/src/main/res/drawable/collar_shape_background_icon.xml b/ui/src/main/res/drawable/collar_shape_background_item_line.xml similarity index 78% rename from ui/src/main/res/drawable/collar_shape_background_icon.xml rename to ui/src/main/res/drawable/collar_shape_background_item_line.xml index ff78e77b..6995bfd3 100644 --- a/ui/src/main/res/drawable/collar_shape_background_icon.xml +++ b/ui/src/main/res/drawable/collar_shape_background_item_line.xml @@ -2,7 +2,7 @@ - + - + - - - - - - \ No newline at end of file diff --git a/ui/src/main/res/drawable/collar_shape_background_screen.xml b/ui/src/main/res/drawable/collar_shape_background_screen.xml deleted file mode 100644 index e078e315..00000000 --- a/ui/src/main/res/drawable/collar_shape_background_screen.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/ui/src/main/res/drawable/collar_shape_background_unknown.xml b/ui/src/main/res/drawable/collar_shape_background_unknown.xml deleted file mode 100644 index fdfe11e3..00000000 --- a/ui/src/main/res/drawable/collar_shape_background_unknown.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/ui/src/main/res/layout/collar_activity_collar.xml b/ui/src/main/res/layout/collar_activity_collar.xml index 50635181..4b51fcf8 100644 --- a/ui/src/main/res/layout/collar_activity_collar.xml +++ b/ui/src/main/res/layout/collar_activity_collar.xml @@ -28,6 +28,8 @@ android:scrollbars="vertical" app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" /> - + \ No newline at end of file diff --git a/ui/src/main/res/layout/collar_item_event.xml b/ui/src/main/res/layout/collar_item_event.xml index 00d5b8b8..d3b707e8 100644 --- a/ui/src/main/res/layout/collar_item_event.xml +++ b/ui/src/main/res/layout/collar_item_event.xml @@ -15,7 +15,6 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="16dp" - android:layout_marginLeft="16dp" android:layout_marginTop="8dp" android:gravity="center_horizontal" android:maxWidth="56dp" @@ -30,14 +29,13 @@ android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_gravity="center_vertical" - android:layout_marginStart="16dp" - android:layout_marginLeft="16dp"> + android:layout_marginStart="16dp"> + android:background="@drawable/collar_shape_background_item_line" /> + android:src="@drawable/collar_ic_event_white" /> diff --git a/ui/src/main/res/layout/collar_item_property.xml b/ui/src/main/res/layout/collar_item_property.xml index ae533974..bcd042bc 100644 --- a/ui/src/main/res/layout/collar_item_property.xml +++ b/ui/src/main/res/layout/collar_item_property.xml @@ -15,14 +15,13 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="16dp" - android:layout_marginLeft="16dp" android:layout_marginTop="8dp" + android:layout_marginBottom="8dp" android:gravity="center_horizontal" android:maxWidth="56dp" - android:layout_marginBottom="8dp" - android:minHeight="48dp" android:maxLength="8" android:maxLines="1" + android:minHeight="48dp" android:visibility="invisible" tools:text="20:00:00" /> @@ -37,7 +36,7 @@ android:layout_width="1dp" android:layout_height="match_parent" android:layout_gravity="center" - android:background="@drawable/collar_shape_background_icon" /> + android:background="@drawable/collar_shape_background_item_line" /> + android:src="@drawable/collar_ic_property_white" /> diff --git a/ui/src/main/res/layout/collar_item_screen.xml b/ui/src/main/res/layout/collar_item_screen.xml index 9b197ac8..86a55830 100644 --- a/ui/src/main/res/layout/collar_item_screen.xml +++ b/ui/src/main/res/layout/collar_item_screen.xml @@ -15,14 +15,13 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="16dp" - android:layout_marginLeft="16dp" android:layout_marginTop="8dp" + android:layout_marginBottom="8dp" android:gravity="center_horizontal" android:maxWidth="56dp" - android:layout_marginBottom="8dp" - android:minHeight="48dp" android:maxLength="8" android:maxLines="1" + android:minHeight="48dp" android:visibility="invisible" tools:text="20:00:00" /> @@ -30,14 +29,13 @@ android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_gravity="center_vertical" - android:layout_marginStart="16dp" - android:layout_marginLeft="16dp"> + android:layout_marginStart="16dp"> + android:background="@drawable/collar_shape_background_item_line" /> + android:src="@drawable/collar_ic_screen_white" /> diff --git a/ui/src/main/res/layout/collar_view_snackbar.xml b/ui/src/main/res/layout/collar_view_snackbar.xml index 0a2d420d..cc44af63 100644 --- a/ui/src/main/res/layout/collar_view_snackbar.xml +++ b/ui/src/main/res/layout/collar_view_snackbar.xml @@ -18,16 +18,15 @@ android:layout_gravity="top" android:layout_marginTop="8dp" android:layout_marginBottom="8dp" - android:background="@drawable/collar_shape_background_screen" + android:background="@drawable/collar_shape_background_item_type" android:scaleType="centerInside" - android:src="@drawable/collar_ic_screen" /> + android:src="@drawable/collar_ic_screen_white" /> @@ -68,7 +67,6 @@ android:layout_height="wrap_content" android:layout_gravity="center_vertical|end" android:layout_marginStart="16dp" - android:layout_marginLeft="16dp" android:minWidth="48dp" android:text="@string/collar_share" android:textColor="@color/collar_color_primary" diff --git a/ui/src/main/res/menu/collar_menu.xml b/ui/src/main/res/menu/collar_menu.xml index 429f3de8..987e11fb 100644 --- a/ui/src/main/res/menu/collar_menu.xml +++ b/ui/src/main/res/menu/collar_menu.xml @@ -21,17 +21,17 @@ diff --git a/ui/src/main/res/values-night/colors.xml b/ui/src/main/res/values-night/colors.xml index dc35c02b..efbbbdd2 100644 --- a/ui/src/main/res/values-night/colors.xml +++ b/ui/src/main/res/values-night/colors.xml @@ -1,20 +1,8 @@ - #ffa000 #121212 - #121212 - #121212 - #005fff - #F44336 - #000000 - #000000 - #ffffff - #ffffff - #000000 - #00ffa0 - #005fff - #ffa000 - #F44336 #353535 + + @color/collar_color_primary \ No newline at end of file diff --git a/ui/src/main/res/values-night/themes.xml b/ui/src/main/res/values-night/themes.xml deleted file mode 100644 index 439810f3..00000000 --- a/ui/src/main/res/values-night/themes.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - -