diff --git a/packages/compiler/src/api.ts b/packages/compiler/src/api.ts index f126ed701..0b00f1d59 100644 --- a/packages/compiler/src/api.ts +++ b/packages/compiler/src/api.ts @@ -282,7 +282,6 @@ export interface TargetOutput< Spec = TargetComponentSpec, > { processedComponents: Map>; - sources: Set; dependencies: Set; assetBindings: AssetBindings; projectName: string; diff --git a/packages/compiler/src/compiler.ts b/packages/compiler/src/compiler.ts index d43cf75d1..6119111c6 100644 --- a/packages/compiler/src/compiler.ts +++ b/packages/compiler/src/compiler.ts @@ -445,11 +445,6 @@ export abstract class TargetCompiler< */ output: OutputType; - /** - * Updates a output based on the contents of bindings. - */ - protected abstract mergeBindingToOutput (binding: BindingType): void; - /** * Creates fresh output. */ @@ -722,11 +717,6 @@ export abstract class TargetCompiler< return false; } - for (const {binding} of this.output.processedComponents.values()) { - if (binding) { - this.mergeBindingToOutput(binding as BindingType); - } - } await this.writeAssets(); copySync(this.program.emitRoot, this.program.hotRoot); return true; diff --git a/packages/compiler/test/helpers.ts b/packages/compiler/test/helpers.ts index ba7b76f60..1015bdca9 100644 --- a/packages/compiler/test/helpers.ts +++ b/packages/compiler/test/helpers.ts @@ -56,18 +56,11 @@ export class TestTargetCompiler extends TargetCompiler androidDataClassStart }} } -{{else}} -) { - companion object {} -} -{{/if}} diff --git a/packages/targets/sources/android/android.data-class.start.handlebars b/packages/targets/sources/android/android.data-class.start.handlebars new file mode 100644 index 000000000..63581d34c --- /dev/null +++ b/packages/targets/sources/android/android.data-class.start.handlebars @@ -0,0 +1,22 @@ +{{#if hasProperties}} +data class {{{componentName}}}( +{{#if singleton}} +{{#each properties}} + val {{{@key}}}: {{{this.type}}} = {{{this.initializer}}}{{#unless @last}},{{/unless}} +{{/each}} +{{else}} +{{#each properties}} + val {{{@key}}}: {{{this.type}}}{{#unless @last}},{{/unless}} +{{/each}} +{{/if}} +{{else}} +data class {{{componentName}}}( + val nonce: String = "" +{{/if}} +{{#if public}}) : StateBag { + companion object {} + override val name = "{{{componentName}}}" +{{else}} +) { + companion object {} +{{/if}} diff --git a/packages/targets/sources/android/bindings/Color.kt b/packages/targets/sources/android/bindings/Color.kt index fbe6fe347..4e21d9b02 100644 --- a/packages/targets/sources/android/bindings/Color.kt +++ b/packages/targets/sources/android/bindings/Color.kt @@ -1,13 +1,18 @@ +package {{{packageName}}} + import android.graphics.Color as CoreColor import android.support.v4.graphics.ColorUtils -val Color.color: Int - get() { - val rgb = ColorUtils.HSLToColor(floatArrayOf(this.h * 360, this.s, this.l)) - return CoreColor.argb( - (this.a * 255).toInt(), - CoreColor.red(rgb), - CoreColor.green(rgb), - CoreColor.blue(rgb) - ) - } +{{> androidDataClassStart }} + + val color: Int + get() { + val rgb = ColorUtils.HSLToColor(floatArrayOf(this.h * 360, this.s, this.l)) + return CoreColor.argb( + (this.a * 255).toInt(), + CoreColor.red(rgb), + CoreColor.green(rgb), + CoreColor.blue(rgb) + ) + } +} diff --git a/packages/targets/sources/android/bindings/File.kt b/packages/targets/sources/android/bindings/File.kt index ac3d2478d..1a3200b8e 100644 --- a/packages/targets/sources/android/bindings/File.kt +++ b/packages/targets/sources/android/bindings/File.kt @@ -1,53 +1,57 @@ +package {{{packageName}}} + import android.net.Uri import java.net.URL -private val extensionReplacer = """(.+)(_.+)""".toRegex() -private val fileReplacer = """[^a-z0-9_]""".toRegex() +{{> androidDataClassStart }} -internal val File.resourceName: String - get() { - return extensionReplacer.replace(fileReplacer.replace(src.toLowerCase(), "_"), "$1") - } - -private val File.resourcePath: String - get() { - return "$type/$resourceName" - } - -internal val File.resourceId: Int - get() { - return Environment.resources.getIdentifier( - resourceName, - type, - Environment.packageName - ) - } - -internal val File.canonicalURL: String - get() { - if (Environment.isHot) { - return "${Environment.serverUrl}/$src" + val uri: Uri + get() { + return Uri.parse(this.canonicalURL) } - return "android.resource://${Environment.packageName}/$resourcePath" - } + val url: URL + get() { + return URL(this.canonicalURL) + } -internal val File.websafeURL: String - get() { - if (Environment.isHot) { - return canonicalURL + internal val resourceName: String + get() { + return extensionReplacer.replace(fileReplacer.replace(src.toLowerCase(), "_"), "$1") } - return "file:///android_res/$resourcePath" - } + internal val resourceId: Int + get() { + return Environment.resources.getIdentifier( + resourceName, + type, + Environment.packageName + ) + } + + internal val canonicalURL: String + get() { + if (Environment.isHot) { + return "${Environment.serverUrl}/$src" + } -val File.uri: Uri - get() { - return Uri.parse(this.canonicalURL) - } + return "android.resource://${Environment.packageName}/$resourcePath" + } + internal val websafeURL: String + get() { + if (Environment.isHot) { + return canonicalURL + } -val File.url: URL - get() { - return URL(this.canonicalURL) - } + return "file:///android_res/$resourcePath" + } + + private val resourcePath: String + get() { + return "$type/$resourceName" + } +} + +private val extensionReplacer = """(.+)(_.+)""".toRegex() +private val fileReplacer = """[^a-z0-9_]""".toRegex() diff --git a/packages/targets/sources/android/bindings/Image.kt b/packages/targets/sources/android/bindings/Image.kt index a5cef0976..092fda098 100644 --- a/packages/targets/sources/android/bindings/Image.kt +++ b/packages/targets/sources/android/bindings/Image.kt @@ -1,3 +1,5 @@ +package {{{packageName}}} + import android.graphics.BitmapFactory import android.graphics.Shader import android.graphics.drawable.BitmapDrawable @@ -12,6 +14,37 @@ import com.bumptech.glide.signature.ObjectKey import android.widget.ImageView import android.widget.TextView +{{> androidDataClassStart }} + + internal val correctDensityFile: File + get () { + return when (effectiveDensity) { + 1 -> file + 2 -> file2x + 3 -> file3x + else -> file4x + } + } + + internal val resourceId: Int + get () { + return this.correctDensityFile.resourceId + } + + internal val drawableFromRawResource: Drawable? + get () { + return ResourcesCompat.getDrawable( + Environment.resources, + Environment.resources.getIdentifier( + file.resourceName, + "drawable", + Environment.packageName + ), + null + ) + } +} + fun ImageView.load(image: Image) { if (Environment.isHot) { getFromNetwork(image, this, fun(drawable) { @@ -79,21 +112,6 @@ private val effectiveDensity: Int return Math.ceil(density).toInt() } -private val Image.correctDensityFile: File - get () { - return when (effectiveDensity) { - 1 -> file - 2 -> file2x - 3 -> file3x - else -> file4x - } - } - -private val Image.resourceId: Int - get () { - return this.correctDensityFile.resourceId - } - private fun getFromNetwork(image: Image, view: View, callback: (BitmapDrawable) -> Unit) { val width = (image.width * Environment.resources.displayMetrics.density.toDouble()).toInt() val height = (image.height * Environment.resources.displayMetrics.density.toDouble()).toInt() @@ -108,17 +126,3 @@ private fun getFromNetwork(image: Image, view: View, callback: (BitmapDrawable) } }) } - - -private val Image.drawableFromRawResource: Drawable? - get () { - return ResourcesCompat.getDrawable( - Environment.resources, - Environment.resources.getIdentifier( - file.resourceName, - "drawable", - Environment.packageName - ), - null - ) - } diff --git a/packages/targets/sources/android/bindings/Lottie.kt b/packages/targets/sources/android/bindings/Lottie.kt index 7dd55a445..4c0ac6f4d 100644 --- a/packages/targets/sources/android/bindings/Lottie.kt +++ b/packages/targets/sources/android/bindings/Lottie.kt @@ -1,3 +1,5 @@ +package {{{packageName}}} + import android.util.Log import android.widget.FrameLayout import com.airbnb.lottie.LottieAnimationView @@ -6,6 +8,9 @@ import com.airbnb.lottie.LottieComposition import com.airbnb.lottie.LottieListener import com.airbnb.lottie.LottieDrawable +{{> androidDataClassStart }} +} + fun LottieAnimationView.load(lottie: Lottie) { val task = when(Environment.isHot) { true -> LottieCompositionFactory.fromUrl(context, lottie.file.canonicalURL) diff --git a/packages/targets/sources/android/bindings/Typograph.kt b/packages/targets/sources/android/bindings/Typograph.kt index b5df87306..8f6beb893 100644 --- a/packages/targets/sources/android/bindings/Typograph.kt +++ b/packages/targets/sources/android/bindings/Typograph.kt @@ -1,3 +1,5 @@ +package {{{packageName}}} + import java.io.File as CoreFile import android.graphics.Typeface import android.widget.TextView @@ -5,6 +7,44 @@ import java.io.BufferedInputStream import java.io.FileOutputStream import android.os.StrictMode +{{> androidDataClassStart }} + + val typeface: Typeface + get() { + if (foreignFontCache.containsKey(font.name)) { + return foreignFontCache.get(font.name)!! + } + + if (font.file.src == "") { + if (systemFontCache.isEmpty()) { + registerSystemFonts() + } + + if (systemFontCache.containsKey(font.name)) { + val resolver = systemFontCache.get(font.name)!! + val typeface = Typeface.create( + Typeface.create(resolver.name, resolver.style), + resolver.weight, + resolver.style == Typeface.ITALIC || resolver.style == Typeface.BOLD_ITALIC + ) + foreignFontCache.set(font.name, typeface) + return typeface + } + + + return Typeface.create("", Typeface.NORMAL) + } + + return getTypeface(font) + } +} + +fun TextView.apply(typograph: Typograph) { + this.typeface = typograph.typeface + this.textSize = typograph.fontSize + this.setTextColor(typograph.color.color) +} + private data class FontResolver(val name: String, val weight: Int, val style: Int) private val tempPrefix = "diez-fonts" @@ -111,38 +151,3 @@ private fun getTypeface(font: Font): Typeface { foreignFontCache.set(font.name, typeface) return typeface } - -val Typograph.typeface: Typeface - get() { - if (foreignFontCache.containsKey(font.name)) { - return foreignFontCache.get(font.name)!! - } - - if (font.file.src == "") { - if (systemFontCache.isEmpty()) { - registerSystemFonts() - } - - if (systemFontCache.containsKey(font.name)) { - val resolver = systemFontCache.get(font.name)!! - val typeface = Typeface.create( - Typeface.create(resolver.name, resolver.style), - resolver.weight, - resolver.style == Typeface.ITALIC || resolver.style == Typeface.BOLD_ITALIC - ) - foreignFontCache.set(font.name, typeface) - return typeface - } - - - return Typeface.create("", Typeface.NORMAL) - } - - return getTypeface(font) - } - -fun TextView.apply(typograph: Typograph) { - this.typeface = typograph.typeface - this.textSize = typograph.fontSize - this.setTextColor(typograph.color.color) -} diff --git a/packages/targets/sources/android/core/Diez.kt b/packages/targets/sources/android/core/Diez.kt index cdc99054d..90d63de29 100644 --- a/packages/targets/sources/android/core/Diez.kt +++ b/packages/targets/sources/android/core/Diez.kt @@ -1,3 +1,5 @@ +package {{{packageName}}} + import android.annotation.SuppressLint import android.util.Log import android.view.ViewGroup diff --git a/packages/targets/sources/android/core/Environment.kt b/packages/targets/sources/android/core/Environment.kt index 3d08ca781..1ff15755c 100644 --- a/packages/targets/sources/android/core/Environment.kt +++ b/packages/targets/sources/android/core/Environment.kt @@ -1,3 +1,5 @@ +package {{{packageName}}} + import android.content.Context import android.content.res.Resources diff --git a/packages/targets/src/targets/android.api.ts b/packages/targets/src/targets/android.api.ts index b6ca58aeb..070c3aa57 100644 --- a/packages/targets/src/targets/android.api.ts +++ b/packages/targets/src/targets/android.api.ts @@ -23,10 +23,6 @@ export interface AndroidBinding extends TargetB * Describes the complete output for a transpiled Android target. */ export interface AndroidOutput extends TargetOutput { - files: Map; packageName: string; resources: Map>; } diff --git a/packages/targets/src/targets/android.handler.ts b/packages/targets/src/targets/android.handler.ts index 676eb9635..d98dacc84 100644 --- a/packages/targets/src/targets/android.handler.ts +++ b/packages/targets/src/targets/android.handler.ts @@ -8,19 +8,17 @@ import { TargetComponentSpec, } from '@diez/compiler'; import {File} from '@diez/prefabs'; -import {getTempFileName, outputTemplatePackage} from '@diez/storage'; +import {outputTemplatePackage} from '@diez/storage'; import camelCase from 'camel-case'; import { copySync, ensureDirSync, - openSync, outputFileSync, readFileSync, removeSync, writeFileSync, - writeSync, } from 'fs-extra'; -import {compile} from 'handlebars'; +import {compile, registerPartial} from 'handlebars'; import {v4} from 'internal-ip'; import {basename, join} from 'path'; import {sourcesPath} from '../utils'; @@ -186,20 +184,6 @@ export class AndroidCompiler extends TargetCompiler(), @@ -264,8 +245,6 @@ class MainActivity … { * @abstract */ clear () { - this.output.sources.clear(); - this.output.files.clear(); this.output.processedComponents.clear(); this.output.dependencies.clear(); this.output.assetBindings.clear(); @@ -298,10 +277,35 @@ class MainActivity … { } } + protected bindingContainsExtension (binding: AndroidBinding | undefined, filename: string) { + if (!binding) { + return false; + } + + const match = binding.sources.find((source) => basename(source) === filename); + return match !== undefined; + } + /** * @abstract */ async writeSdk () { + const packageComponents = this.output.packageName.split('.'); + const sourcesRoot = join(this.output.sdkRoot, 'src', 'main', 'java', ...packageComponents); + ensureDirSync(sourcesRoot); + + const coreBasenames = [ + 'Diez.kt', + 'Environment.kt', + ]; + for (const filename of coreBasenames) { + const template = readFileSync(join(coreAndroid, 'core', filename)).toString(); + const path = join(sourcesRoot, filename); + writeFileSync(path, compile(template)({ + packageName: this.output.packageName, + })); + } + // Pass through to take note of our singletons. const singletons = new Set(); for (const [type, {instances, binding}] of this.output.processedComponents) { @@ -311,12 +315,12 @@ class MainActivity … { } } + const dataClassStartTemplate = readFileSync(join(coreAndroid, 'android.data-class.start.handlebars')).toString(); + registerPartial('androidDataClassStart', dataClassStartTemplate); + const componentTemplate = readFileSync(join(coreAndroid, 'android.component.handlebars')).toString(); - for (const [type, {spec, binding}] of this.output.processedComponents) { - if (binding) { - this.mergeBindingToOutput(binding); - } + for (const [type, {spec, binding}] of this.output.processedComponents) { // For each singleton, replace it with its simple constructor. for (const property of Object.values(spec.properties)) { if (singletons.has(property.type)) { @@ -324,49 +328,45 @@ class MainActivity … { } } - const filename = getTempFileName(); - writeFileSync( - filename, - compile(componentTemplate)({ - ...spec, - singleton: spec.public || singletons.has(type), - hasProperties: Object.keys(spec.properties).length > 0, - }), - ); - - this.output.files.set(`${spec.componentName}.kt`, {dataClass: filename}); - } + const dataClassStartTokens = { + ...spec, + singleton: spec.public || singletons.has(type), + hasProperties: Object.keys(spec.properties).length > 0, + }; - const packageComponents = this.output.packageName.split('.'); - const sourcesRoot = join(this.output.sdkRoot, 'src', 'main', 'java', ...packageComponents); - const prefixBuffer = Buffer.from(`package ${this.output.packageName}\n\n`); - ensureDirSync(sourcesRoot); + const componentTokens = { + ...dataClassStartTokens, + packageName: this.output.packageName, + }; - for (const source of this.output.sources) { - const filename = basename(source); - if (this.output.files.has(filename)) { - this.output.files.get(filename)!.extension = source; - } else { - this.output.files.set(filename, {dataClass: source}); - } - } + const componentBasename = `${spec.componentName}.kt`; + let hasComponentOverride = false; + if (binding) { + for (const source of binding.sources) { + const template = readFileSync(source).toString(); + const sourceBasename = basename(source); + const bindingPath = join(sourcesRoot, sourceBasename); + writeFileSync(bindingPath, compile(template)(componentTokens)); + + if (sourceBasename === componentBasename) { + hasComponentOverride = true; + } + } - for (const [filename, {dataClass, extension}] of this.output.files) { - const outputPath = join(sourcesRoot, filename); - const handle = openSync(outputPath, 'w+'); - let cursor = 0; - writeSync(handle, prefixBuffer, 0, prefixBuffer.length, cursor); - cursor += prefixBuffer.length; - if (extension) { - const extensionBuffer = readFileSync(extension); - writeSync(handle, extensionBuffer, 0, extensionBuffer.length, cursor); - cursor += extensionBuffer.length; - writeSync(handle, Buffer.from('\n'), 0, 1, cursor); - cursor += 1; + if (binding.dependencies) { + for (const dependency of binding.dependencies) { + mergeDependency(this.output.dependencies, dependency); + } + } } - const sourceBuffer = readFileSync(dataClass); - writeSync(handle, sourceBuffer, 0, sourceBuffer.length, cursor); + // We only need to write a component here if a binding hasn't already been written with the data class + // implementation. This is determined by checking the name of the file to see if it matches our component name. + if (!hasComponentOverride) { + const sourceBasename = basename(`${spec.componentName}.kt`); + const bindingPath = join(sourcesRoot, sourceBasename); + writeFileSync(bindingPath, compile(componentTemplate)(componentTokens)); + } } const tokens = { diff --git a/packages/targets/src/targets/ios.api.ts b/packages/targets/src/targets/ios.api.ts index 0b2095af8..fe4af5245 100644 --- a/packages/targets/src/targets/ios.api.ts +++ b/packages/targets/src/targets/ios.api.ts @@ -56,6 +56,7 @@ export interface IosComponentSpec { */ export interface IosOutput extends TargetOutput { bundleIdPrefix: string; + sources: Set; /** * A temporary directory used as an intermediary copy step when writing the SDK. diff --git a/packages/targets/src/targets/ios.handler.ts b/packages/targets/src/targets/ios.handler.ts index 5e96a0e7a..ed7b2ccbd 100644 --- a/packages/targets/src/targets/ios.handler.ts +++ b/packages/targets/src/targets/ios.handler.ts @@ -189,7 +189,7 @@ export class IosCompiler extends TargetCompiler { } /** - * @abstract + * Updates the output based on the contents of the binding. */ protected mergeBindingToOutput (binding: IosBinding): void { const sourcesRoot = this.sourcesRoot; diff --git a/packages/targets/src/targets/web.api.ts b/packages/targets/src/targets/web.api.ts index 4bcf96b90..adaf3c6c5 100644 --- a/packages/targets/src/targets/web.api.ts +++ b/packages/targets/src/targets/web.api.ts @@ -32,6 +32,7 @@ export interface WebBinding extends TargetBindi * Describes the complete output for a transpiled Web target. */ export interface WebOutput extends TargetOutput { + sources: Set; declarations: Set; declarationImports: Set; } diff --git a/packages/targets/src/targets/web.handler.ts b/packages/targets/src/targets/web.handler.ts index af928b4bb..dba424e80 100644 --- a/packages/targets/src/targets/web.handler.ts +++ b/packages/targets/src/targets/web.handler.ts @@ -139,7 +139,7 @@ export class WebCompiler extends TargetCompiler { } /** - * @abstract + * Updates the output based on the contents of the binding. */ protected mergeBindingToOutput (binding: WebBinding): void { for (const bindingSource of binding.sources) { diff --git a/packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/Stub/Color.kt b/packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/Stub/Color.kt index b54e65937..6b3d96434 100644 --- a/packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/Stub/Color.kt +++ b/packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/Stub/Color.kt @@ -3,17 +3,6 @@ package org.diez.stub import android.graphics.Color as CoreColor import android.support.v4.graphics.ColorUtils -val Color.color: Int - get() { - val rgb = ColorUtils.HSLToColor(floatArrayOf(this.h * 360, this.s, this.l)) - return CoreColor.argb( - (this.a * 255).toInt(), - CoreColor.red(rgb), - CoreColor.green(rgb), - CoreColor.blue(rgb) - ) - } - data class Color( val h: Float, val s: Float, @@ -21,4 +10,15 @@ data class Color( val a: Float ) { companion object {} + + val color: Int + get() { + val rgb = ColorUtils.HSLToColor(floatArrayOf(this.h * 360, this.s, this.l)) + return CoreColor.argb( + (this.a * 255).toInt(), + CoreColor.red(rgb), + CoreColor.green(rgb), + CoreColor.blue(rgb) + ) + } } diff --git a/packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/Stub/Diez.kt b/packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/Stub/Diez.kt new file mode 100644 index 000000000..c5c23ec55 --- /dev/null +++ b/packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/Stub/Diez.kt @@ -0,0 +1,66 @@ +package org.diez.stub + +import android.annotation.SuppressLint +import android.util.Log +import android.view.ViewGroup +import android.webkit.* +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory +import com.squareup.moshi.Moshi.Builder + +interface StateBag { + val name : String +} + +@SuppressLint("SetJavaScriptEnabled") +class Diez(var component: T, val view: ViewGroup) { + val adapter : JsonAdapter + val subscribers = mutableListOf<(T) -> Unit>() + + init { + val builder = Builder() + builder.add(KotlinJsonAdapterFactory()) + adapter = builder.build().adapter(component.javaClass) + Environment.setContext(view.context) + if (Environment.isHot) { + // Enables webview debugging in chrome://inspect. + WebView.setWebContentsDebuggingEnabled(true) + val webview = WebView(view.context) + // Allows `window.location.reload()` to behave as expected. + webview.webViewClient = WebViewClient() + webview.settings.javaScriptEnabled = true + webview.addJavascriptInterface(this, "puente") + webview.loadUrl("${Environment.serverUrl}/components/${component.name}") + Log.d("DIEZ", "Loading ${Environment.serverUrl}/components/${component.name}") + webview.alpha = 0F + view.addView(webview, 0, 0) + } + } + + @JavascriptInterface + fun patch(json: String) { + try { + component = adapter.fromJson(json)!! + broadcast() + } catch (e: Exception) { + Log.e("DIEZ", e.toString()) + } + } + + fun attach(subscriber: (T) -> Unit) { + subscriber(component) + if (Environment.isHot) { + subscribe(subscriber) + } + } + + fun subscribe(subscriber: (T) -> Unit) { + subscribers.add(subscriber) + } + + fun broadcast() { + for (subscriber in subscribers) { + subscriber(component) + } + } +} diff --git a/packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/Stub/Environment.kt b/packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/Stub/Environment.kt new file mode 100644 index 000000000..228710cab --- /dev/null +++ b/packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/Stub/Environment.kt @@ -0,0 +1,26 @@ +package org.diez.stub + +import android.content.Context +import android.content.res.Resources + +class Environment { + companion object { + var isHot = false + var serverUrl = "" + lateinit var resources: Resources + lateinit var packageName: String + + fun setContext(context: Context) { + resources = context.resources + packageName = context.packageName + val isHotResourceId = resources.getIdentifier("diez_is_hot", "bool", packageName) + val serverUrlResourceId = resources.getIdentifier("diez_server_url", "string", packageName) + if (isHotResourceId != 0) { + isHot = resources.getBoolean(isHotResourceId) + } + if (serverUrlResourceId != 0) { + serverUrl = resources.getString(serverUrlResourceId) + } + } + } +} diff --git a/packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/Stub/File.kt b/packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/Stub/File.kt index 8d0b8d3b5..dfb203a93 100644 --- a/packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/Stub/File.kt +++ b/packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/Stub/File.kt @@ -3,60 +3,59 @@ package org.diez.stub import android.net.Uri import java.net.URL -private val extensionReplacer = """(.+)(_.+)""".toRegex() -private val fileReplacer = """[^a-z0-9_]""".toRegex() +data class File( + val src: String, + val type: String +) { + companion object {} -internal val File.resourceName: String - get() { - return extensionReplacer.replace(fileReplacer.replace(src.toLowerCase(), "_"), "$1") - } - -private val File.resourcePath: String - get() { - return "$type/$resourceName" - } - -internal val File.resourceId: Int - get() { - return Environment.resources.getIdentifier( - resourceName, - type, - Environment.packageName - ) - } - -internal val File.canonicalURL: String - get() { - if (Environment.isHot) { - return "${Environment.serverUrl}/$src" + val uri: Uri + get() { + return Uri.parse(this.canonicalURL) } - return "android.resource://${Environment.packageName}/$resourcePath" - } + val url: URL + get() { + return URL(this.canonicalURL) + } -internal val File.websafeURL: String - get() { - if (Environment.isHot) { - return canonicalURL + internal val resourceName: String + get() { + return extensionReplacer.replace(fileReplacer.replace(src.toLowerCase(), "_"), "$1") } - return "file:///android_res/$resourcePath" - } + internal val resourceId: Int + get() { + return Environment.resources.getIdentifier( + resourceName, + type, + Environment.packageName + ) + } -val File.uri: Uri - get() { - return Uri.parse(this.canonicalURL) - } + internal val canonicalURL: String + get() { + if (Environment.isHot) { + return "${Environment.serverUrl}/$src" + } + return "android.resource://${Environment.packageName}/$resourcePath" + } -val File.url: URL - get() { - return URL(this.canonicalURL) - } + internal val websafeURL: String + get() { + if (Environment.isHot) { + return canonicalURL + } -data class File( - val src: String, - val type: String -) { - companion object {} + return "file:///android_res/$resourcePath" + } + + private val resourcePath: String + get() { + return "$type/$resourceName" + } } + +private val extensionReplacer = """(.+)(_.+)""".toRegex() +private val fileReplacer = """[^a-z0-9_]""".toRegex() diff --git a/packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/Stub/Image.kt b/packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/Stub/Image.kt index f6860138f..ddb88013f 100644 --- a/packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/Stub/Image.kt +++ b/packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/Stub/Image.kt @@ -14,6 +14,45 @@ import com.bumptech.glide.signature.ObjectKey import android.widget.ImageView import android.widget.TextView +data class Image( + val file: File, + val file2x: File, + val file3x: File, + val file4x: File, + val width: Int, + val height: Int +) { + companion object {} + + internal val correctDensityFile: File + get () { + return when (effectiveDensity) { + 1 -> file + 2 -> file2x + 3 -> file3x + else -> file4x + } + } + + internal val resourceId: Int + get () { + return this.correctDensityFile.resourceId + } + + internal val drawableFromRawResource: Drawable? + get () { + return ResourcesCompat.getDrawable( + Environment.resources, + Environment.resources.getIdentifier( + file.resourceName, + "drawable", + Environment.packageName + ), + null + ) + } +} + fun ImageView.load(image: Image) { if (Environment.isHot) { getFromNetwork(image, this, fun(drawable) { @@ -81,21 +120,6 @@ private val effectiveDensity: Int return Math.ceil(density).toInt() } -private val Image.correctDensityFile: File - get () { - return when (effectiveDensity) { - 1 -> file - 2 -> file2x - 3 -> file3x - else -> file4x - } - } - -private val Image.resourceId: Int - get () { - return this.correctDensityFile.resourceId - } - private fun getFromNetwork(image: Image, view: View, callback: (BitmapDrawable) -> Unit) { val width = (image.width * Environment.resources.displayMetrics.density.toDouble()).toInt() val height = (image.height * Environment.resources.displayMetrics.density.toDouble()).toInt() @@ -110,28 +134,3 @@ private fun getFromNetwork(image: Image, view: View, callback: (BitmapDrawable) } }) } - - -private val Image.drawableFromRawResource: Drawable? - get () { - return ResourcesCompat.getDrawable( - Environment.resources, - Environment.resources.getIdentifier( - file.resourceName, - "drawable", - Environment.packageName - ), - null - ) - } - -data class Image( - val file: File, - val file2x: File, - val file3x: File, - val file4x: File, - val width: Int, - val height: Int -) { - companion object {} -} diff --git a/packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/Stub/Lottie.kt b/packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/Stub/Lottie.kt index d43988494..fdfa5b61d 100644 --- a/packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/Stub/Lottie.kt +++ b/packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/Stub/Lottie.kt @@ -8,6 +8,14 @@ import com.airbnb.lottie.LottieComposition import com.airbnb.lottie.LottieListener import com.airbnb.lottie.LottieDrawable +data class Lottie( + val file: File, + val loop: Boolean, + val autoplay: Boolean +) { + companion object {} +} + fun LottieAnimationView.load(lottie: Lottie) { val task = when(Environment.isHot) { true -> LottieCompositionFactory.fromUrl(context, lottie.file.canonicalURL) @@ -30,11 +38,3 @@ fun LottieAnimationView.load(lottie: Lottie) { } }) } - -data class Lottie( - val file: File, - val loop: Boolean, - val autoplay: Boolean -) { - companion object {} -} diff --git a/packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/Stub/Typograph.kt b/packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/Stub/Typograph.kt index 53fcdf339..4412b1d85 100644 --- a/packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/Stub/Typograph.kt +++ b/packages/targets/test/goldens/Bindings/diez-stub-android/src/main/java/org/diez/Stub/Typograph.kt @@ -7,6 +7,49 @@ import java.io.BufferedInputStream import java.io.FileOutputStream import android.os.StrictMode +data class Typograph( + val font: Font, + val fontSize: Float, + val color: Color +) { + companion object {} + + val typeface: Typeface + get() { + if (foreignFontCache.containsKey(font.name)) { + return foreignFontCache.get(font.name)!! + } + + if (font.file.src == "") { + if (systemFontCache.isEmpty()) { + registerSystemFonts() + } + + if (systemFontCache.containsKey(font.name)) { + val resolver = systemFontCache.get(font.name)!! + val typeface = Typeface.create( + Typeface.create(resolver.name, resolver.style), + resolver.weight, + resolver.style == Typeface.ITALIC || resolver.style == Typeface.BOLD_ITALIC + ) + foreignFontCache.set(font.name, typeface) + return typeface + } + + + return Typeface.create("", Typeface.NORMAL) + } + + return getTypeface(font) + } +} + +fun TextView.apply(typograph: Typograph) { + this.typeface = typograph.typeface + this.textSize = typograph.fontSize + this.setTextColor(typograph.color.color) +} + private data class FontResolver(val name: String, val weight: Int, val style: Int) private val tempPrefix = "diez-fonts" @@ -113,46 +156,3 @@ private fun getTypeface(font: Font): Typeface { foreignFontCache.set(font.name, typeface) return typeface } - -val Typograph.typeface: Typeface - get() { - if (foreignFontCache.containsKey(font.name)) { - return foreignFontCache.get(font.name)!! - } - - if (font.file.src == "") { - if (systemFontCache.isEmpty()) { - registerSystemFonts() - } - - if (systemFontCache.containsKey(font.name)) { - val resolver = systemFontCache.get(font.name)!! - val typeface = Typeface.create( - Typeface.create(resolver.name, resolver.style), - resolver.weight, - resolver.style == Typeface.ITALIC || resolver.style == Typeface.BOLD_ITALIC - ) - foreignFontCache.set(font.name, typeface) - return typeface - } - - - return Typeface.create("", Typeface.NORMAL) - } - - return getTypeface(font) - } - -fun TextView.apply(typograph: Typograph) { - this.typeface = typograph.typeface - this.textSize = typograph.fontSize - this.setTextColor(typograph.color.color) -} - -data class Typograph( - val font: Font, - val fontSize: Float, - val color: Color -) { - companion object {} -} diff --git a/packages/targets/test/goldens/Primitives/diez-stub-android/src/main/java/org/diez/stub/Diez.kt b/packages/targets/test/goldens/Primitives/diez-stub-android/src/main/java/org/diez/stub/Diez.kt new file mode 100644 index 000000000..c5c23ec55 --- /dev/null +++ b/packages/targets/test/goldens/Primitives/diez-stub-android/src/main/java/org/diez/stub/Diez.kt @@ -0,0 +1,66 @@ +package org.diez.stub + +import android.annotation.SuppressLint +import android.util.Log +import android.view.ViewGroup +import android.webkit.* +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory +import com.squareup.moshi.Moshi.Builder + +interface StateBag { + val name : String +} + +@SuppressLint("SetJavaScriptEnabled") +class Diez(var component: T, val view: ViewGroup) { + val adapter : JsonAdapter + val subscribers = mutableListOf<(T) -> Unit>() + + init { + val builder = Builder() + builder.add(KotlinJsonAdapterFactory()) + adapter = builder.build().adapter(component.javaClass) + Environment.setContext(view.context) + if (Environment.isHot) { + // Enables webview debugging in chrome://inspect. + WebView.setWebContentsDebuggingEnabled(true) + val webview = WebView(view.context) + // Allows `window.location.reload()` to behave as expected. + webview.webViewClient = WebViewClient() + webview.settings.javaScriptEnabled = true + webview.addJavascriptInterface(this, "puente") + webview.loadUrl("${Environment.serverUrl}/components/${component.name}") + Log.d("DIEZ", "Loading ${Environment.serverUrl}/components/${component.name}") + webview.alpha = 0F + view.addView(webview, 0, 0) + } + } + + @JavascriptInterface + fun patch(json: String) { + try { + component = adapter.fromJson(json)!! + broadcast() + } catch (e: Exception) { + Log.e("DIEZ", e.toString()) + } + } + + fun attach(subscriber: (T) -> Unit) { + subscriber(component) + if (Environment.isHot) { + subscribe(subscriber) + } + } + + fun subscribe(subscriber: (T) -> Unit) { + subscribers.add(subscriber) + } + + fun broadcast() { + for (subscriber in subscribers) { + subscriber(component) + } + } +} diff --git a/packages/targets/test/goldens/Primitives/diez-stub-android/src/main/java/org/diez/stub/Environment.kt b/packages/targets/test/goldens/Primitives/diez-stub-android/src/main/java/org/diez/stub/Environment.kt new file mode 100644 index 000000000..228710cab --- /dev/null +++ b/packages/targets/test/goldens/Primitives/diez-stub-android/src/main/java/org/diez/stub/Environment.kt @@ -0,0 +1,26 @@ +package org.diez.stub + +import android.content.Context +import android.content.res.Resources + +class Environment { + companion object { + var isHot = false + var serverUrl = "" + lateinit var resources: Resources + lateinit var packageName: String + + fun setContext(context: Context) { + resources = context.resources + packageName = context.packageName + val isHotResourceId = resources.getIdentifier("diez_is_hot", "bool", packageName) + val serverUrlResourceId = resources.getIdentifier("diez_server_url", "string", packageName) + if (isHotResourceId != 0) { + isHot = resources.getBoolean(isHotResourceId) + } + if (serverUrlResourceId != 0) { + serverUrl = resources.getString(serverUrlResourceId) + } + } + } +}