From dfb1d85c30ca39aab2d338214da2edb69d7a8136 Mon Sep 17 00:00:00 2001 From: 2BAB Date: Sun, 30 Sep 2018 19:27:08 +0800 Subject: [PATCH 1/7] bump gradle version --- build.gradle | 7 ++++++- gradle/wrapper/gradle-wrapper.properties | 2 +- sample/app/build.gradle | 4 ++-- sample/build.gradle | 2 +- settings.gradle | 9 +++++++++ 5 files changed, 19 insertions(+), 5 deletions(-) diff --git a/build.gradle b/build.gradle index dd41616..4643761 100644 --- a/build.gradle +++ b/build.gradle @@ -39,6 +39,11 @@ dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" implementation 'com.android.tools.build:gradle:3.2.0' + implementation 'org.jfree:jfreesvg:3.3' +} + +compileJava { + options.compilerArgs += ["-proc:none"] } ext.travisBuild = System.getenv("TRAVIS") == "true" @@ -52,6 +57,6 @@ if (project.extensions.findByName("buildScan") != null) { // publish group 'me.2bab' -version '2.2.0' +version '2.2.1-SNAPSHOT' apply from: 'bintray.gradle' apply from: 'mavenlocal.gradle' \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 6b6b17b..3be8881 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-4.6-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip diff --git a/sample/app/build.gradle b/sample/app/build.gradle index 7b29526..9031ccd 100644 --- a/sample/app/build.gradle +++ b/sample/app/build.gradle @@ -41,11 +41,11 @@ dependencies { } scratchPaper { - textSize = 12 + textSize = 11 textColor = "#FFFFFFFF" verticalLinePadding = 4 backgroundColor = "#99000000" - extraInfo = "This is a sample!" + extraInfo = new Date().format("MM-dd,HH:mm") enableGenerateIconOverlay = true enableGenerateBuildInfo = true enableXmlIconRemove = false diff --git a/sample/build.gradle b/sample/build.gradle index 1900440..74720f9 100644 --- a/sample/build.gradle +++ b/sample/build.gradle @@ -8,7 +8,7 @@ buildscript { dependencies { classpath 'com.android.tools.build:gradle:3.2.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath 'me.2bab:scratch-paper:2.2.0' + classpath 'me.2bab:scratch-paper:2.2.1-SNAPSHOT' } } diff --git a/settings.gradle b/settings.gradle index 55e3846..db69699 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1,10 @@ rootProject.name = 'scratch-paper' + +// As part of making the publishing plugins stable, the 'deferred configurable' behavior +// of the 'publishing {}' block has been deprecated. +// In Gradle 5.0 the 'enableFeaturePreview('STABLE_PUBLISHING')' flag will be removed +// and the new behavior will become the default. +// Please add 'enableFeaturePreview('STABLE_PUBLISHING')' to your settings file +// and do a test run by publishing to a local repository. +// If all artifacts are published as expected, there is nothing else to do. +enableFeaturePreview('STABLE_PUBLISHING') From 5c0b75fed0d018c8e58c80ee42df95222c5b6e77 Mon Sep 17 00:00:00 2001 From: 2BAB Date: Sun, 30 Sep 2018 19:27:23 +0800 Subject: [PATCH 2/7] support AdaptiveIcon --- .../scratchpaper/IconOverlayGenerator.kt | 117 +++++---------- .../iconprocessor/AdaptiveIconProcessor.kt | 134 ++++++++++++++++++ .../iconprocessor/BaseIconProcessor.kt | 92 ++++++++++++ .../iconprocessor/RegularIconProcessor.kt | 53 +++++++ 4 files changed, 314 insertions(+), 82 deletions(-) create mode 100644 src/main/kotlin/me/xx2bab/scratchpaper/iconprocessor/AdaptiveIconProcessor.kt create mode 100644 src/main/kotlin/me/xx2bab/scratchpaper/iconprocessor/BaseIconProcessor.kt create mode 100644 src/main/kotlin/me/xx2bab/scratchpaper/iconprocessor/RegularIconProcessor.kt diff --git a/src/main/kotlin/me/xx2bab/scratchpaper/IconOverlayGenerator.kt b/src/main/kotlin/me/xx2bab/scratchpaper/IconOverlayGenerator.kt index 50d25fb..9f5e49f 100644 --- a/src/main/kotlin/me/xx2bab/scratchpaper/IconOverlayGenerator.kt +++ b/src/main/kotlin/me/xx2bab/scratchpaper/IconOverlayGenerator.kt @@ -2,34 +2,21 @@ package me.xx2bab.scratchpaper import com.android.build.gradle.tasks.MergeManifests import com.android.build.gradle.tasks.MergeResources -import com.android.tools.r8.com.google.common.collect.Lists +import me.xx2bab.scratchpaper.iconprocessor.BaseIconProcessor import me.xx2bab.scratchpaper.utils.Aapt2Utils -import me.xx2bab.scratchpaper.utils.CacheUtils import me.xx2bab.scratchpaper.utils.Logger import org.gradle.api.Project -import java.awt.Color -import java.awt.Font -import java.awt.GraphicsEnvironment -import java.awt.RenderingHints.KEY_TEXT_ANTIALIASING -import java.awt.RenderingHints.VALUE_TEXT_ANTIALIAS_ON -import java.awt.image.BufferedImage import java.io.File -import javax.imageio.ImageIO import javax.xml.parsers.DocumentBuilderFactory + class IconOverlayGenerator(private val params: GeneratorParams) { // default icon name of Android is ic_launcher private val defaultIconName = "ic_launcher" - // to make sure the fontSize can be a regular number (like 14, 16, 18 that develops usually use) - // I test 14 on all dpi generating, and found 96 (which is the xhdpi icon size) can fits it well - // so we just make it as a standard size and compute the ratio for others to scale - private val prettyImageSizeFits14FontSize = 96.0 - fun process() { setAwtEnv() - params.variant.outputs.forEach { output -> val processManifestTask: MergeManifests = output.processManifest as MergeManifests @@ -39,11 +26,15 @@ class IconOverlayGenerator(private val params: GeneratorParams) { "AndroidManifest.xml") val resDirs = params.variant.sourceSets[0].resDirectories val version = "@" + params.variant.mergedFlavor.versionName - val iconName = getIconName(mergedManifestFile) - findIcons(resDirs, iconName).forEach { icon -> - val processedIcon = addTextToIcon(params.project, params.dimension, + val iconNames = getIconName(mergedManifestFile) + findIcons(resDirs, iconNames).forEach { icon -> + val icons = addTextToIcon(params.project, params.dimension, icon, params.config, params.dimension, version, params.config.extraInfo) - processedIcons.add(processedIcon) + if (icons != null) { + for (file in icons) { + processedIcons.add(file) + } + } } val mergeResTaskName = "merge${params.dimension}Resources" @@ -51,7 +42,7 @@ class IconOverlayGenerator(private val params: GeneratorParams) { val mergedResDir = mergeResTask.outputDir Aapt2Utils.compileResDir(params.project, mergedResDir, processedIcons) if (params.config.enableXmlIconRemove) { - removeXmlIconFiles(iconName, mergedResDir) + removeXmlIconFiles(iconNames, mergedResDir) } } } @@ -87,21 +78,25 @@ class IconOverlayGenerator(private val params: GeneratorParams) { * Icon name to search for in the app drawable folders * If no icon can be found in the manifest, IconOverlayGenerator#defaultIconName will be used */ - private fun getIconName(manifestFile: File): String { + private fun getIconName(manifestFile: File): Array { if (manifestFile.isDirectory || !manifestFile.exists()) { - return "" + return arrayOf() } val manifestXml = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(manifestFile) - val fileName = manifestXml.getElementsByTagName("application").item(0) + var regularIconName = manifestXml.getElementsByTagName("application").item(0) .attributes.getNamedItem("android:icon")?.nodeValue - return fileName?.split("/")?.get(1) ?: defaultIconName + var roundIconName = manifestXml.getElementsByTagName("application").item(0) + .attributes.getNamedItem("android:roundIcon")?.nodeValue + regularIconName = regularIconName?.split("/")?.get(1) ?: defaultIconName + roundIconName = roundIconName?.split("/")?.get(1) ?: defaultIconName + "_round" + return arrayOf(regularIconName, roundIconName) } /** * Finds all icon files matching the icon specified in the given manifest. */ - private fun findIcons(where: Collection, iconName: String): List { - val result: MutableList = Lists.newArrayList() + private fun findIcons(where: Collection, iconNames: Array): Collection { + val result: MutableSet = hashSetOf() where.forEach { it.walk() .filter { dir -> @@ -109,9 +104,10 @@ class IconOverlayGenerator(private val params: GeneratorParams) { } .forEach { file -> file.walk().forEach { image -> - if (isIconFile(iconName, image) - && image.extension != "xml") { - result.add(image) + iconNames.forEach { iconName -> + if (isIconFile(iconName, image)) { + result.add(image) + } } } } @@ -133,50 +129,8 @@ class IconOverlayGenerator(private val params: GeneratorParams) { dimension: String, image: File, config: ScratchPaperExtension = ScratchPaperExtension.DEFAULT_CONFIG, - vararg lines: String): File { - val bufferedImage: BufferedImage = ImageIO.read(image) - val backgroundOverlayColor: Color = config.getBackgroundColor() - val textColor: Color = config.getTextColor() - - val imgWidth: Int = bufferedImage.width - val imgHeight: Int = bufferedImage.height - val ratio = imgWidth / prettyImageSizeFits14FontSize - - val fontSize: Int = (config.textSize * ratio).toInt() - val linePadding: Int = (config.verticalLinePadding * ratio).toInt() - val lineCount: Int = lines.size - val totalLineHeight: Int = (fontSize * lineCount) + ((linePadding + 1) * lineCount) - - GraphicsEnvironment.getLocalGraphicsEnvironment().createGraphics(bufferedImage).apply { - this.setRenderingHint(KEY_TEXT_ANTIALIASING, VALUE_TEXT_ANTIALIAS_ON) - - // Draw background overlay - this.color = backgroundOverlayColor - this.fillRect(0, imgHeight - totalLineHeight, imgWidth, totalLineHeight) - - // Draw each line of text - this.font = Font(Font.SANS_SERIF, Font.PLAIN, fontSize) - this.color = textColor - for ((i, line) in lines.reversed().withIndex()) { - val strWidth = this.fontMetrics.stringWidth(line) - - var x = 0 - if (imgWidth >= strWidth) { - x = ((imgWidth - strWidth) / 2) - } - - val y = imgHeight - (fontSize * i) - ((i + 1) * linePadding) - - this.drawString(line, x, y) - } - } - val destDir = File(CacheUtils.getCacheDir(project, dimension), image.parentFile.name) - if (!destDir.exists() && !destDir.mkdirs()) { - Logger.e("Can not create cache directory for ScratchPaper.") - } - val destImage = File(destDir, image.name) - ImageIO.write(bufferedImage, "png", destImage) - return destImage + vararg lines: String): Array? { + return BaseIconProcessor.getProcessor(project, dimension, image, config, lines)?.process() } /** @@ -184,26 +138,25 @@ class IconOverlayGenerator(private val params: GeneratorParams) { * For now I didn't find an elegant approach to add a cover for xml icon, * so the ScratchPaper provide a temporary function to remove them. * - * @param iconName the icon defined in the AndroidManifest.xml + * @param iconNames the icons defined in the AndroidManifest.xml (icon & roundIcons) * @param mergedResDir it's a directory like /build/intermediates/res/merged/debug */ - private fun removeXmlIconFiles(iconName: String, mergedResDir: File) { + private fun removeXmlIconFiles(iconNames: Array, mergedResDir: File) { if (mergedResDir.isFile) { return } mergedResDir.walk().forEach { file -> - if (file.isFile - && (file.name.contains("$iconName.xml.flat") - || file.name.contains("${iconName}_round.xml.flat"))) { - file.delete() + iconNames.forEach { iconName -> + if (file.isFile && file.name.contains("$iconName.xml.flat")) { + file.delete() + } } + } } private fun isIconFile(namePrefix: String, file: File): Boolean { - return file.isFile - && (file.nameWithoutExtension == namePrefix - || file.nameWithoutExtension == "${namePrefix}_round") + return file.isFile && file.nameWithoutExtension == namePrefix } diff --git a/src/main/kotlin/me/xx2bab/scratchpaper/iconprocessor/AdaptiveIconProcessor.kt b/src/main/kotlin/me/xx2bab/scratchpaper/iconprocessor/AdaptiveIconProcessor.kt new file mode 100644 index 0000000..03cf21d --- /dev/null +++ b/src/main/kotlin/me/xx2bab/scratchpaper/iconprocessor/AdaptiveIconProcessor.kt @@ -0,0 +1,134 @@ +package me.xx2bab.scratchpaper.iconprocessor + +import com.android.ide.common.vectordrawable.Svg2Vector +import me.xx2bab.scratchpaper.ScratchPaperExtension +import me.xx2bab.scratchpaper.utils.CacheUtils +import me.xx2bab.scratchpaper.utils.Logger +import org.gradle.api.Project +import org.jfree.graphics2d.svg.SVGGraphics2D +import org.jfree.graphics2d.svg.SVGUtils +import org.w3c.dom.Document +import org.w3c.dom.Element +import java.awt.Graphics2D +import java.awt.font.TextLayout +import java.io.File +import javax.xml.parsers.DocumentBuilderFactory +import javax.xml.transform.TransformerFactory +import javax.xml.transform.dom.DOMSource +import javax.xml.transform.stream.StreamResult + + +/** + * In this Processor, we generate the SVG for overlay at first. Then we convert it + * to Android Vector Drawable XML file, and merge it with the original icon. To deal + * with SVG stuffs, we use "org.jfree:jfreesvg:3.3" now. If you need more advanced + * functions, can choose "org.apache.xmlgraphics:batik-svgpp:1.10" as well. + * + * @link https://github.com/jfree/jfreesvg + * @link https://github.com/jfree/jfree-demos + * @link https://xmlgraphics.apache.org/batik/using/svg-generator.html + */ +class AdaptiveIconProcessor(project: Project, + dimension: String, + originIcon: File, + config: ScratchPaperExtension, + lines: Array) + : BaseIconProcessor(project, dimension, originIcon, config, lines) { + + override fun getSize(): Pair { + return Pair(width, height) + } + + override fun getGraphic(): Graphics2D { + return graphic + } + + override fun drawText(line: String, x: Int, y: Int) { + // Do not use Graphics2D.drawString(line: String, x: Int, y: Int), it will generates String + // with in SVG file, however Android Vector Converter doesn't support . + // Since we want to get a good compatible experience, so we draw text in to solve it. + // @see com.android.ide.common.vectordrawable.Svg2Vector#unsupportedSvgNodes + // val font = Font(Font.SANS_SERIF, Font.PLAIN, config.textSize / 2) // hack the text size + val tl = TextLayout(line, getGraphic().font, getGraphic().fontRenderContext) + tl.draw(getGraphic(), x.toFloat(), y.toFloat()) + } + + override fun writeIcon(): Array { + // prepare destination file + val destDir = File(CacheUtils.getCacheDir(project, dimension), originIcon.parentFile.name) + if (!destDir.exists() && !destDir.mkdirs()) { + Logger.e("Can not create cache directory for ScratchPaper.") + } + val destIcon = File(destDir, originIcon.name).apply { createNewFile() } + + // generate overlay svg & convert svg to vector drawable xml + val commonDrawableDir = File(destIcon.parentFile.parent + File.separator + "drawable").apply { mkdir() } + val overlaySVG = File(commonDrawableDir, "${destIcon.nameWithoutExtension}_overlay.svg") + val overlayVectorDrawableFileName = "${destIcon.nameWithoutExtension}_overlay.xml" + val overlayVectorDrawable = File(commonDrawableDir, overlayVectorDrawableFileName).apply { createNewFile() } + SVGUtils.writeToSVG(overlaySVG, (getGraphic() as SVGGraphics2D).svgElement) + val out = overlayVectorDrawable.outputStream() + Svg2Vector.parseSvgToXml(overlaySVG, out) + + // append overlay to + val itemDrawableElement = originIconXmlDoc.createElement("item") + val drawableAttr = originIconXmlDoc.createAttribute("android:drawable") + drawableAttr.value = "@drawable/$overlayVectorDrawableFileName".removeSuffix(".xml") + itemDrawableElement.setAttributeNode(drawableAttr) + layerList.appendChild(itemDrawableElement) + + // write to destination + val transformerFactory = TransformerFactory.newInstance() + val transformer = transformerFactory.newTransformer() + val source = DOMSource(originIconXmlDoc) + val result = StreamResult(destIcon) + transformer.transform(source, result) + + + return arrayOf(destIcon, overlayVectorDrawable) + } + + // private val vdTree: VdTree + private val width = 100 + private val height = 100 + private val graphic: Graphics2D + private val originIconXmlDoc: Document = DocumentBuilderFactory.newInstance() + .newDocumentBuilder().parse(originIcon) + private var layerList: Element + + init { + + // parse foreground node & clean all attributes and childs + var drawableInForeground: String? = null + + var foregroundElement = originIconXmlDoc.getElementsByTagName("foreground").item(0) + if (foregroundElement != null) { + if (foregroundElement.attributes.getNamedItem("android:drawable") != null) { + drawableInForeground = foregroundElement.attributes.getNamedItem("android:drawable").nodeValue + foregroundElement.attributes.removeNamedItem("android:drawable") + } + } else { + foregroundElement = originIconXmlDoc.createElement("foreground") + val adaptiveIconNode = originIconXmlDoc.getElementsByTagName("adaptive-icon").item(0) + adaptiveIconNode.appendChild(foregroundElement) + } + + // add a as its only one child + // append all childs & attributes to new + layerList = originIconXmlDoc.createElement("layer-list") + foregroundElement.appendChild(layerList) + + if (drawableInForeground != null) { + val itemDrawableElement = originIconXmlDoc.createElement("item") + val drawableAttr = originIconXmlDoc.createAttribute("android:drawable") + drawableAttr.value = drawableInForeground + itemDrawableElement.setAttributeNode(drawableAttr) + layerList.appendChild(itemDrawableElement) + } + + // init a new Graphic2D object to draw overlay + graphic = SVGGraphics2D(width, height) + } + + +} \ No newline at end of file diff --git a/src/main/kotlin/me/xx2bab/scratchpaper/iconprocessor/BaseIconProcessor.kt b/src/main/kotlin/me/xx2bab/scratchpaper/iconprocessor/BaseIconProcessor.kt new file mode 100644 index 0000000..447c3fc --- /dev/null +++ b/src/main/kotlin/me/xx2bab/scratchpaper/iconprocessor/BaseIconProcessor.kt @@ -0,0 +1,92 @@ +package me.xx2bab.scratchpaper.iconprocessor + +import me.xx2bab.scratchpaper.ScratchPaperExtension +import org.gradle.api.Project +import java.awt.Color +import java.awt.Font +import java.awt.Graphics2D +import java.awt.RenderingHints +import java.io.File + +abstract class BaseIconProcessor(val project: Project, + val dimension: String, + val originIcon: File, + val config: ScratchPaperExtension = ScratchPaperExtension.DEFAULT_CONFIG, + private val lines: Array) { + + companion object { + + fun getProcessor(project: Project, + dimension: String, + image: File, + config: ScratchPaperExtension = ScratchPaperExtension.DEFAULT_CONFIG, + lines: Array): BaseIconProcessor? { + if (!image.exists()) { + return null + } + return if (image.extension == "xml") { + AdaptiveIconProcessor(project, dimension, image, config, lines) + } else { + RegularIconProcessor(project, dimension, image, config, lines) + } + } + + } + + // to make sure the fontSize can be a regular number (like 11 12 14 that developers usually use) + // I test 14 on all dpi generating, and found 96 (which is the xhdpi icon size) can fits it well + // so we just make it as a standard size and compute the ratio for others to scale + private val prettyImageSizeFits14FontSize = 96 + + fun process(): Array { + val backgroundOverlayColor: Color = config.getBackgroundColor() + val textColor: Color = config.getTextColor() + + val size = getSize() + val imgWidth: Int = size.first + val imgHeight: Int = size.second + val ratio = imgWidth * 1.0 / prettyImageSizeFits14FontSize + + val fontSize: Int = (config.textSize * ratio).toInt() + val linePadding: Int = (config.verticalLinePadding * ratio).toInt() + val lineCount: Int = lines.size + val totalLineHeight: Int = (fontSize * lineCount) + ((linePadding + 1) * lineCount) + + getGraphic().apply { + this.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON) + + // Draw background overlay + val marginTop = (imgHeight - totalLineHeight) / 2 + color = backgroundOverlayColor + fillRect(0, marginTop, imgWidth, totalLineHeight) + + // Draw each line of text + font = Font(Font.SANS_SERIF, Font.PLAIN, fontSize) + color = textColor + for ((i, line) in lines.reversed().withIndex()) { + val strWidth = this.fontMetrics.stringWidth(line) + + var x = 0 + if (imgWidth >= strWidth) { + x = ((imgWidth - strWidth) / 2) + } + + val y = imgHeight - (fontSize * i) - ((i + 1) * linePadding) - marginTop + + // drawString(line, x, y) + drawText(line, x, y) + } + } + return writeIcon() + } + + abstract fun getSize(): Pair + + abstract fun getGraphic(): Graphics2D + + abstract fun drawText(line: String, x: Int, y: Int) + + abstract fun writeIcon(): Array + + +} \ No newline at end of file diff --git a/src/main/kotlin/me/xx2bab/scratchpaper/iconprocessor/RegularIconProcessor.kt b/src/main/kotlin/me/xx2bab/scratchpaper/iconprocessor/RegularIconProcessor.kt new file mode 100644 index 0000000..012399d --- /dev/null +++ b/src/main/kotlin/me/xx2bab/scratchpaper/iconprocessor/RegularIconProcessor.kt @@ -0,0 +1,53 @@ +package me.xx2bab.scratchpaper.iconprocessor + +import me.xx2bab.scratchpaper.ScratchPaperExtension +import me.xx2bab.scratchpaper.utils.CacheUtils +import me.xx2bab.scratchpaper.utils.Logger +import org.gradle.api.Project +import java.awt.Graphics2D +import java.awt.GraphicsEnvironment +import java.awt.image.BufferedImage +import java.io.File +import javax.imageio.ImageIO + +class RegularIconProcessor(project: Project, + dimension: String, + originIcon: File, + config: ScratchPaperExtension, + lines: Array) + : BaseIconProcessor(project, dimension, originIcon, config, lines) { + + + + + + override fun getSize(): Pair { + return Pair(bufferedImage.width, bufferedImage.height) + } + + override fun getGraphic(): Graphics2D { + return graphic + } + + override fun drawText(line: String, x: Int, y: Int) { + getGraphic().drawString(line, x, y) + } + + override fun writeIcon(): Array { + val destDir = File(CacheUtils.getCacheDir(project, dimension), originIcon.parentFile.name) + if (!destDir.exists() && !destDir.mkdirs()) { + Logger.e("Can not create cache directory for ScratchPaper.") + } + val destIcon = File(destDir, originIcon.name) + ImageIO.write(bufferedImage, "png", destIcon) + return arrayOf(destIcon) + } + + private val bufferedImage: BufferedImage = ImageIO.read(originIcon) + private val graphic: Graphics2D + + init { + graphic = GraphicsEnvironment.getLocalGraphicsEnvironment().createGraphics(bufferedImage) + } + +} \ No newline at end of file From 7ebfb2f1e7f5d6f358c301c7f318d7b98a4efdbe Mon Sep 17 00:00:00 2001 From: 2BAB Date: Sun, 30 Sep 2018 20:12:23 +0800 Subject: [PATCH 3/7] extract strings to constants --- .../scratchpaper/IconOverlayGenerator.kt | 11 +++++--- .../iconprocessor/AdaptiveIconProcessor.kt | 28 +++++++++++-------- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/src/main/kotlin/me/xx2bab/scratchpaper/IconOverlayGenerator.kt b/src/main/kotlin/me/xx2bab/scratchpaper/IconOverlayGenerator.kt index 9f5e49f..6b47eb3 100644 --- a/src/main/kotlin/me/xx2bab/scratchpaper/IconOverlayGenerator.kt +++ b/src/main/kotlin/me/xx2bab/scratchpaper/IconOverlayGenerator.kt @@ -14,6 +14,9 @@ class IconOverlayGenerator(private val params: GeneratorParams) { // default icon name of Android is ic_launcher private val defaultIconName = "ic_launcher" + private val tagApplication = "application" + private val attrIcon = "android:icon" + private val attrRoundIcon = "android:roundIcon" fun process() { setAwtEnv() @@ -83,10 +86,10 @@ class IconOverlayGenerator(private val params: GeneratorParams) { return arrayOf() } val manifestXml = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(manifestFile) - var regularIconName = manifestXml.getElementsByTagName("application").item(0) - .attributes.getNamedItem("android:icon")?.nodeValue - var roundIconName = manifestXml.getElementsByTagName("application").item(0) - .attributes.getNamedItem("android:roundIcon")?.nodeValue + var regularIconName = manifestXml.getElementsByTagName(tagApplication).item(0) + .attributes.getNamedItem(attrIcon)?.nodeValue + var roundIconName = manifestXml.getElementsByTagName(tagApplication).item(0) + .attributes.getNamedItem(attrRoundIcon)?.nodeValue regularIconName = regularIconName?.split("/")?.get(1) ?: defaultIconName roundIconName = roundIconName?.split("/")?.get(1) ?: defaultIconName + "_round" return arrayOf(regularIconName, roundIconName) diff --git a/src/main/kotlin/me/xx2bab/scratchpaper/iconprocessor/AdaptiveIconProcessor.kt b/src/main/kotlin/me/xx2bab/scratchpaper/iconprocessor/AdaptiveIconProcessor.kt index 03cf21d..4fbc2d3 100644 --- a/src/main/kotlin/me/xx2bab/scratchpaper/iconprocessor/AdaptiveIconProcessor.kt +++ b/src/main/kotlin/me/xx2bab/scratchpaper/iconprocessor/AdaptiveIconProcessor.kt @@ -35,6 +35,12 @@ class AdaptiveIconProcessor(project: Project, lines: Array) : BaseIconProcessor(project, dimension, originIcon, config, lines) { + private val attrDrawable = "android:drawable" + private val tagForeground = "foreground" + private val tagLayerList = "layer-list" + private val tagItem = "item" + private val tagAdaptiveIcon = "adaptive-icon" + override fun getSize(): Pair { return Pair(width, height) } @@ -71,8 +77,8 @@ class AdaptiveIconProcessor(project: Project, Svg2Vector.parseSvgToXml(overlaySVG, out) // append overlay to - val itemDrawableElement = originIconXmlDoc.createElement("item") - val drawableAttr = originIconXmlDoc.createAttribute("android:drawable") + val itemDrawableElement = originIconXmlDoc.createElement(tagItem) + val drawableAttr = originIconXmlDoc.createAttribute(attrDrawable) drawableAttr.value = "@drawable/$overlayVectorDrawableFileName".removeSuffix(".xml") itemDrawableElement.setAttributeNode(drawableAttr) layerList.appendChild(itemDrawableElement) @@ -101,26 +107,26 @@ class AdaptiveIconProcessor(project: Project, // parse foreground node & clean all attributes and childs var drawableInForeground: String? = null - var foregroundElement = originIconXmlDoc.getElementsByTagName("foreground").item(0) + var foregroundElement = originIconXmlDoc.getElementsByTagName(tagForeground).item(0) if (foregroundElement != null) { - if (foregroundElement.attributes.getNamedItem("android:drawable") != null) { - drawableInForeground = foregroundElement.attributes.getNamedItem("android:drawable").nodeValue - foregroundElement.attributes.removeNamedItem("android:drawable") + if (foregroundElement.attributes.getNamedItem(attrDrawable) != null) { + drawableInForeground = foregroundElement.attributes.getNamedItem(attrDrawable).nodeValue + foregroundElement.attributes.removeNamedItem(attrDrawable) } } else { - foregroundElement = originIconXmlDoc.createElement("foreground") - val adaptiveIconNode = originIconXmlDoc.getElementsByTagName("adaptive-icon").item(0) + foregroundElement = originIconXmlDoc.createElement(tagForeground) + val adaptiveIconNode = originIconXmlDoc.getElementsByTagName(tagAdaptiveIcon).item(0) adaptiveIconNode.appendChild(foregroundElement) } // add a as its only one child // append all childs & attributes to new - layerList = originIconXmlDoc.createElement("layer-list") + layerList = originIconXmlDoc.createElement(tagLayerList) foregroundElement.appendChild(layerList) if (drawableInForeground != null) { - val itemDrawableElement = originIconXmlDoc.createElement("item") - val drawableAttr = originIconXmlDoc.createAttribute("android:drawable") + val itemDrawableElement = originIconXmlDoc.createElement(tagItem) + val drawableAttr = originIconXmlDoc.createAttribute(attrDrawable) drawableAttr.value = drawableInForeground itemDrawableElement.setAttributeNode(drawableAttr) layerList.appendChild(itemDrawableElement) From d5e4083371ed71b10c3b1674f89ac5e2a6fc0fcd Mon Sep 17 00:00:00 2001 From: 2BAB Date: Sun, 30 Sep 2018 20:17:32 +0800 Subject: [PATCH 4/7] update build version & remove some useless annotations --- build.gradle | 2 +- .../xx2bab/scratchpaper/iconprocessor/AdaptiveIconProcessor.kt | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index 4643761..db1fe06 100644 --- a/build.gradle +++ b/build.gradle @@ -57,6 +57,6 @@ if (project.extensions.findByName("buildScan") != null) { // publish group 'me.2bab' -version '2.2.1-SNAPSHOT' +version '2.3.0' apply from: 'bintray.gradle' apply from: 'mavenlocal.gradle' \ No newline at end of file diff --git a/src/main/kotlin/me/xx2bab/scratchpaper/iconprocessor/AdaptiveIconProcessor.kt b/src/main/kotlin/me/xx2bab/scratchpaper/iconprocessor/AdaptiveIconProcessor.kt index 4fbc2d3..b250423 100644 --- a/src/main/kotlin/me/xx2bab/scratchpaper/iconprocessor/AdaptiveIconProcessor.kt +++ b/src/main/kotlin/me/xx2bab/scratchpaper/iconprocessor/AdaptiveIconProcessor.kt @@ -54,7 +54,6 @@ class AdaptiveIconProcessor(project: Project, // with in SVG file, however Android Vector Converter doesn't support . // Since we want to get a good compatible experience, so we draw text in to solve it. // @see com.android.ide.common.vectordrawable.Svg2Vector#unsupportedSvgNodes - // val font = Font(Font.SANS_SERIF, Font.PLAIN, config.textSize / 2) // hack the text size val tl = TextLayout(line, getGraphic().font, getGraphic().fontRenderContext) tl.draw(getGraphic(), x.toFloat(), y.toFloat()) } @@ -94,7 +93,6 @@ class AdaptiveIconProcessor(project: Project, return arrayOf(destIcon, overlayVectorDrawable) } - // private val vdTree: VdTree private val width = 100 private val height = 100 private val graphic: Graphics2D From 694dd6c27a6782b49ab28ba326075dc88174b58c Mon Sep 17 00:00:00 2001 From: 2BAB Date: Sun, 30 Sep 2018 21:56:40 +0800 Subject: [PATCH 5/7] use object to replace class declaring --- .../xx2bab/scratchpaper/utils/Aapt2Utils.kt | 35 +++++++--------- .../xx2bab/scratchpaper/utils/CacheUtils.kt | 24 +++++------ .../xx2bab/scratchpaper/utils/CommandUtils.kt | 40 +++++++++---------- .../me/xx2bab/scratchpaper/utils/Logger.kt | 33 +++++++-------- 4 files changed, 58 insertions(+), 74 deletions(-) diff --git a/src/main/kotlin/me/xx2bab/scratchpaper/utils/Aapt2Utils.kt b/src/main/kotlin/me/xx2bab/scratchpaper/utils/Aapt2Utils.kt index d3ec9a9..2f23274 100644 --- a/src/main/kotlin/me/xx2bab/scratchpaper/utils/Aapt2Utils.kt +++ b/src/main/kotlin/me/xx2bab/scratchpaper/utils/Aapt2Utils.kt @@ -4,28 +4,23 @@ import com.android.sdklib.BuildToolInfo import org.gradle.api.Project import java.io.File -class Aapt2Utils { - - companion object { - - fun compileResDir(project: Project, targetDir: File, resFiles: List) { - val androidPluginUtils = AndroidPluginUtils(project) - val androidBuilder = androidPluginUtils.getAndroidBuilder() - val aapt2ExecutorPath = androidBuilder?.buildToolInfo?.getPath(BuildToolInfo.PathId.AAPT2) - - project.exec { execSpec -> - execSpec.executable(aapt2ExecutorPath) - execSpec.args("compile") - execSpec.args("--legacy") - execSpec.args("-o") - execSpec.args(targetDir.absolutePath) - resFiles.forEach { - execSpec.args(it.absolutePath) - } +object Aapt2Utils { + + fun compileResDir(project: Project, targetDir: File, resFiles: List) { + val androidPluginUtils = AndroidPluginUtils(project) + val androidBuilder = androidPluginUtils.getAndroidBuilder() + val aapt2ExecutorPath = androidBuilder?.buildToolInfo?.getPath(BuildToolInfo.PathId.AAPT2) + + project.exec { execSpec -> + execSpec.executable(aapt2ExecutorPath) + execSpec.args("compile") + execSpec.args("--legacy") + execSpec.args("-o") + execSpec.args(targetDir.absolutePath) + resFiles.forEach { + execSpec.args(it.absolutePath) } - } - } } \ No newline at end of file diff --git a/src/main/kotlin/me/xx2bab/scratchpaper/utils/CacheUtils.kt b/src/main/kotlin/me/xx2bab/scratchpaper/utils/CacheUtils.kt index 2cb02ab..a6ad728 100644 --- a/src/main/kotlin/me/xx2bab/scratchpaper/utils/CacheUtils.kt +++ b/src/main/kotlin/me/xx2bab/scratchpaper/utils/CacheUtils.kt @@ -4,24 +4,20 @@ import com.android.build.gradle.api.BaseVariant import org.gradle.api.Project import java.io.File -class CacheUtils { +object CacheUtils { - companion object { - - fun mkdir(project: Project, variant: BaseVariant, dimension: String) { - variant.preBuild.doLast { - val cacheDir = getCacheDir(project, dimension) - if (!cacheDir.exists() && !cacheDir.mkdirs()) { - Logger.e("Can not create cache directory for ScratchPaper.") - } + fun mkdir(project: Project, variant: BaseVariant, dimension: String) { + variant.preBuild.doLast { + val cacheDir = getCacheDir(project, dimension) + if (!cacheDir.exists() && !cacheDir.mkdirs()) { + Logger.e("Can not create cache directory for ScratchPaper.") } } + } - fun getCacheDir(project: Project, dimension: String): File { - return File(project.buildDir, "intermediates" + File.separator + "scratch-paper" - + File.separator + dimension.trim()) - } - + fun getCacheDir(project: Project, dimension: String): File { + return File(project.buildDir, "intermediates" + File.separator + "scratch-paper" + + File.separator + dimension.trim()) } } \ No newline at end of file diff --git a/src/main/kotlin/me/xx2bab/scratchpaper/utils/CommandUtils.kt b/src/main/kotlin/me/xx2bab/scratchpaper/utils/CommandUtils.kt index 9990e1f..72fe726 100644 --- a/src/main/kotlin/me/xx2bab/scratchpaper/utils/CommandUtils.kt +++ b/src/main/kotlin/me/xx2bab/scratchpaper/utils/CommandUtils.kt @@ -4,29 +4,25 @@ import java.io.File import java.io.IOException import java.util.concurrent.TimeUnit -class CommandUtils { - - companion object { - - private var workingDir = File("./") - - fun runCommand(command: String, workingDir: File = CommandUtils.workingDir): String? { - return try { - val parts = command.split("\\s".toRegex()) - val proc = ProcessBuilder(*parts.toTypedArray()) - .directory(workingDir) - .redirectOutput(ProcessBuilder.Redirect.PIPE) - .redirectError(ProcessBuilder.Redirect.PIPE) - .start() - - proc.waitFor(1500, TimeUnit.MILLISECONDS) - proc.inputStream.bufferedReader().readText() - } catch (e: IOException) { - e.printStackTrace() - null - } +object CommandUtils { + + private var workingDir = File("./") + + fun runCommand(command: String, workingDir: File = CommandUtils.workingDir): String? { + return try { + val parts = command.split("\\s".toRegex()) + val proc = ProcessBuilder(*parts.toTypedArray()) + .directory(workingDir) + .redirectOutput(ProcessBuilder.Redirect.PIPE) + .redirectError(ProcessBuilder.Redirect.PIPE) + .start() + + proc.waitFor(1500, TimeUnit.MILLISECONDS) + proc.inputStream.bufferedReader().readText() + } catch (e: IOException) { + e.printStackTrace() + null } - } } \ No newline at end of file diff --git a/src/main/kotlin/me/xx2bab/scratchpaper/utils/Logger.kt b/src/main/kotlin/me/xx2bab/scratchpaper/utils/Logger.kt index 4d0c6ac..e038894 100644 --- a/src/main/kotlin/me/xx2bab/scratchpaper/utils/Logger.kt +++ b/src/main/kotlin/me/xx2bab/scratchpaper/utils/Logger.kt @@ -3,30 +3,27 @@ package me.xx2bab.scratchpaper.utils import org.gradle.api.Project import org.gradle.api.logging.Logger -class Logger { +object Logger { - companion object { + private const val TAG = "[ScratchPaper]: " - private const val TAG = "[ScratchPaper]: " + private lateinit var logUtil: Logger - private var logUtil: Logger? = null - - fun init(project: Project) { - logUtil = project.logger - } - - fun d(message: String) { - logUtil?.debug(TAG + message) - } + fun init (project: Project){ + logUtil = project.logger + } - fun i(message: String) { - logUtil?.info(TAG + message) - } + fun d(message: String) { + logUtil.debug(TAG + message) + } - fun e(message: String) { - logUtil?.error(TAG + message) - } + fun i(message: String) { + logUtil.info(TAG + message) + } + fun e(message: String) { + logUtil.error(TAG + message) } + } \ No newline at end of file From 4285566d024bf0b9bfce3b48bfcb1ce8c9347dd3 Mon Sep 17 00:00:00 2001 From: 2BAB Date: Sun, 30 Sep 2018 22:51:24 +0800 Subject: [PATCH 6/7] Fixes #2 --- .../me/xx2bab/scratchpaper/BuildInfoGenerator.kt | 11 ++--------- .../me/xx2bab/scratchpaper/ScratchPaperExtension.kt | 11 ++++++++--- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/main/kotlin/me/xx2bab/scratchpaper/BuildInfoGenerator.kt b/src/main/kotlin/me/xx2bab/scratchpaper/BuildInfoGenerator.kt index 25419ab..e54a539 100644 --- a/src/main/kotlin/me/xx2bab/scratchpaper/BuildInfoGenerator.kt +++ b/src/main/kotlin/me/xx2bab/scratchpaper/BuildInfoGenerator.kt @@ -33,13 +33,6 @@ class BuildInfoGenerator(private val params: GeneratorParams) { writeText(root.toJSONString()) } -// params.project.tasks.getByName("merge${params.variantCapedName}Assets").doLast( -// "generate${params.variantCapedName}BuildInfoByScratchPaper") { assetsTask -> -// val mergedAssetsDir = (assetsTask as MergeSourceSetFolders).outputDir -// val targetBIFile = File(mergedAssetsDir, buildInfoFileName) -// buildInfoFile.copyTo(targetBIFile) -// } - } } @@ -53,8 +46,8 @@ class BuildInfoGenerator(private val params: GeneratorParams) { private fun generateGitInfo(): Pair { val git = JSONObject() - git["branch"] = CommandUtils.runCommand("git rev-parse --abbrev-ref HEAD")?.trim() - git["latestCommit"] = CommandUtils.runCommand("git rev-parse HEAD")?.trim() + git["branch"] = CommandUtils.runCommand("git rev-parse --abbrev-ref HEAD").let { it?.trim() ?: "" } + git["latestCommit"] = CommandUtils.runCommand("git rev-parse HEAD").let { it?.trim() ?: "" } return Pair("git", git) } diff --git a/src/main/kotlin/me/xx2bab/scratchpaper/ScratchPaperExtension.kt b/src/main/kotlin/me/xx2bab/scratchpaper/ScratchPaperExtension.kt index e626909..15201fc 100644 --- a/src/main/kotlin/me/xx2bab/scratchpaper/ScratchPaperExtension.kt +++ b/src/main/kotlin/me/xx2bab/scratchpaper/ScratchPaperExtension.kt @@ -50,7 +50,13 @@ open class ScratchPaperExtension { val octetHexColor: String octetHexColor = when (colorLength) { - 3 -> "FF" + processedHexColor.substring(0, 1) + processedHexColor.substring(0, 1) + processedHexColor.substring(1, 2) + processedHexColor.substring(1, 2) + processedHexColor.substring(2, 3) + processedHexColor.substring(2, 3) + 3 -> "FF" + processedHexColor.let { + val builder = StringBuilder() + for (i in 0..2) { + builder.append(it.substring(i, i + 1)).append(it.substring(i, i + 1)) + } + builder.toString() + } 6 -> "FF$processedHexColor" @@ -62,8 +68,7 @@ open class ScratchPaperExtension { } for (i in 0..3) { - argbIntArray[i] = Integer.parseInt(octetHexColor.substring(2 * i, 2 * i + 1) - + octetHexColor.substring(2 * i + 1, 2 * i + 2), 16) + argbIntArray[i] = octetHexColor.substring(2 * i, 2 * i + 2).toInt(16) } return argbIntArray From 1e0a945c6855c705399525373f91c5eca1741fd5 Mon Sep 17 00:00:00 2001 From: 2BAB Date: Sun, 30 Sep 2018 22:51:39 +0800 Subject: [PATCH 7/7] update README, add CHANGELOG --- CHANGELOG.md | 21 +++++++++++++++++++++ README.md | 13 +++++++++---- README_zh.md | 13 +++++++++---- sample/build.gradle | 2 +- 4 files changed, 40 insertions(+), 9 deletions(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..e6b6994 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,21 @@ +Only place important and recommended versions here. + +## 2.3.0 + +- Supported adaptive-icon +- Supported android:roundIcon correctly (instead of hardcode) +- Bumped Gradle version to 4.10.2 (latest stable) +- Bugs fixed +- Code Review + +## 2.2.0 + +- Fixed the wrong usage of buildType and flavor, now we use dimension for the combination name +- Fixed deploy script +- Bumped Gradle version to 4.6, Android Gradle Plugin to 3.2.0 + +## 2.1.1 + +- Bugs fixed +- Integrated Travis + diff --git a/README.md b/README.md index e1292c3..5a972c8 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,10 @@ ScatchPaper can add a overlay on your icon, and put some given information on it. +- Supported regular & round Icons +- Supported adaptive-icon +- Supported AAPT2 + > If you have more than one staging App for QA or other colleagues, when they found some issues you may don't know how to match the App to your commit, because all of them share the same versions like "2.1.0-SNAPSHOT". ScatchPaper supports generating build information into your artifact (which can read from /assets/scratch-paper.json) and also `/intermedias/scratch-paper/assets` directory including: @@ -29,8 +33,8 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.1.4' - classpath 'me.2bab:scratch-paper:2.2.0' + classpath 'com.android.tools.build:gradle:3.2.0' + classpath 'me.2bab:scratch-paper:2.3.0' } } ``` @@ -54,11 +58,11 @@ apply plugin: 'me.2bab.scratchpaper' ``` gradle scratchPaper { - textSize = 12 + textSize = 11 textColor = "#FFFFFFFF" verticalLinePadding = 4 backgroundColor = "#99000000" - extraInfo = "This is a sample!" + extraInfo = new Date().format("MM-dd,HH:mm") enableGenerateIconOverlay = true enableGenerateBuildInfo = true @@ -74,6 +78,7 @@ ScratchPaper only tests in Latest TWO Minor versions of Android Gradle Plugin. AGP Version|Compatible Status -----------|----------------- +3.2.x (Aapt2) | Support 3.1.x (Aapt2) | Support 3.0.x (Aapt2) | Support 2.3.x (Aapt2) | Never Tested diff --git a/README_zh.md b/README_zh.md index 61801d0..1f41177 100644 --- a/README_zh.md +++ b/README_zh.md @@ -10,6 +10,10 @@ ScatchPaper 可以在你的 App icon 上加一个蒙层用以区分出各个 BuildType 的 App,并且承载了版本信息等附加文字。 +- 支持 常规 和 圆形 的图标 +- 支持 adaptive-icon +- 支持 AAPT2 + > 如果你同时打了多个测试包给测试或者产品(例如基于多个复合分支),当他们给你反馈的问题时候你和他们可能都很难分别出每个 App 对应的具体的分支或者 commit 节点。 ScatchPaper 支持生成编译信息并打包到你的 Apk 中(从 assets 中读取),以及输出一份拷贝到 `/intermedias/scratch-paper/assets` 文件夹,包括: @@ -29,8 +33,8 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.1.4' - classpath 'me.2bab:scratch-paper:2.2.0' + classpath 'com.android.tools.build:gradle:3.2.0' + classpath 'me.2bab:scratch-paper:2.3.0' } } ``` @@ -54,11 +58,11 @@ apply plugin: 'me.2bab.scratchpaper' ``` gradle scratchPaper { - textSize = 12 + textSize = 11 textColor = "#FFFFFFFF" verticalLinePadding = 4 backgroundColor = "#99000000" - extraInfo = "This is a sample!" + extraInfo = new Date().format("MM-dd,HH:mm") enableGenerateIconOverlay = true enableGenerateBuildInfo = true @@ -74,6 +78,7 @@ ScratchPaper 只会支持最新两个 Minor 版本的 Android Gradle Plugin: AGP Version|Compatible Status -----------|----------------- +3.2.x (Aapt2) | Support 3.1.x (Aapt2) | Support 3.0.x (Aapt2) | Support 2.3.x (Aapt2) | Never Tested diff --git a/sample/build.gradle b/sample/build.gradle index 74720f9..502d86e 100644 --- a/sample/build.gradle +++ b/sample/build.gradle @@ -8,7 +8,7 @@ buildscript { dependencies { classpath 'com.android.tools.build:gradle:3.2.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath 'me.2bab:scratch-paper:2.2.1-SNAPSHOT' + classpath 'me.2bab:scratch-paper:2.3.0-SNAPSHOT' } }