diff --git a/.travis.yml b/.travis.yml index 3aa0b79..6e6599c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,15 @@ language: android android: - components: - - build-tools-21.1.2 - - android-21 + components: + - build-tools-27.0.3 + - android-27 - extra-android-m2repository + licenses: + - 'android-sdk-license-.+' + +jdk: oraclejdk8 -jdk: oraclejdk7 +before_install: + - yes | sdkmanager "platforms;android-27" -script: ./gradlew build +script: ./gradlew assembleDebug testDebugUnitTest diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..4a202dd --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,84 @@ +## Change Log + +### Version 2.0.0 (unreleased) +**Breaking change!** Change log files are now located in `raw` resource directories rather than the `xml` ones +(see issue #48). + +The library was split into a core library (`ckchangelog-core`) and a library for the UI part +(`ckchangelog-legacy-dialog`). + +The core library provides the base functionality like parsing the XML file and remembering the version code of the last +app version. This allows users of the core library to provide their own visualization of the change log. + +The `ckchangelog-legacy-dialog` library provides the simple dialog from ckChangeLog 1.x that renders the change log in a +`WebView` inside an `AlertDialog`. However, users are strongly encouraged to write their own UI to display the change +log. It is the author's belief that `ckchangelog-legacy-dialog` should be avoided because of the following issues: +* Displaying a dialog on app startup is quite obtrusive. +* WebView is a very resource-intensive UI widget. +* Creating a ChangeLog instance triggers a read from `SharedPreferences` and a query to `PackageManager`. Both + operations that should be performed in a background thread, but aren't if you're using `ckchangelog-legacy-dialog`. + Similarly, reading the change log resources are operations that should be performed in the background. + +Support for a `date` attribute on the `release` element has been added to `ckchangelog-core`. It's an optional attribute +that accepts arbitrary strings. It's up the user of the library whether to parse the string as a date in a certain +format, output the string as-is, or ignore the value. + +Example: +```xml + + First release + +``` +**Note:** `ckchangelog-legacy-dialog` does not support this new attribute. + +#### Update from ckChangeLog 1.x + +1. Replace the old entry in the dependency block with this: + + ```groovy + dependencies { + compile 'de.cketti.library.changelog:ckchangelog-legacy-dialog:2.0.0' + } + ``` + +2. Move the change log files from `res/xml/` and `res/xml-*` to `res/raw/` and `res/raw-*` respectively. + +3. Then replace the old ckChangeLog code in your Activity's `onCreate()` method with this: + + ```java + DialogChangeLog changeLog = DialogChangeLog.newInstance(this); + if (changeLog.isFirstRun()) { + changeLog.getLogDialog().show(); + } + ``` + +Advanced functionality like getting the last version code is available via the `ChangeLog` instance that can be +retrieved by using `DialogChangeLog#getChangeLog()`. +Example: `dialogChangeLog.getChangeLog().isFirstRunEver()` + + +### Version 1.2.2 (2015-01-09) +* Added Ukrainian translation + +### Version 1.2.1 +* Added support for [AboutLibraries](https://github.com/mikepenz/AboutLibraries) +* Fixed build scripts so Javadoc JAR is properly created + +### Version 1.2.0 +* Made constant `DEFAULT_CSS` public +* Changed internals to make it easier to read the change log from different sources +* Added public method `getChangeLog(boolean)` that returns a list of `ReleaseItem`s +* Changed minSdkVersion to 4 +* Switched to Gradle as build system +* Added Greek, Spanish, Polish, and Russian translation + +### Version 1.1.0 +* Added method `skipLogDialog()` +* Added Slovak and German translation + +### Version 1.0.0 +* **Breaking change!** Moved master translation from `res/raw/changelog.xml` to `res/xml/changelog_master.xml` +* Added German translation of the sample app + +### Version 0.1 +* Initial release diff --git a/NOTICE b/NOTICE new file mode 100644 index 0000000..4fac050 --- /dev/null +++ b/NOTICE @@ -0,0 +1,31 @@ +Copyright (C) 2012-2018 cketti and contributors +https://github.com/cketti/ckChangeLog/graphs/contributors + +Portions Copyright (C) 2012 Martin van Zuilekom (http://martin.cubeactive.com) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +Based on android-change-log: + +Copyright (C) 2011, Karsten Priegnitz + +Permission to use, copy, modify, and distribute this piece of software +for any purpose with or without fee is hereby granted, provided that +the above copyright notice and this permission notice appear in the +source code of all copies. + +It would be appreciated if you mention the author in your change log, +contributors list or the like. + +http://code.google.com/p/android-change-log/ diff --git a/README.md b/README.md index 09b29bd..034ebff 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # ckChangeLog - An Android Library to display a Change Log [![Build status](https://api.travis-ci.org/cketti/ckChangeLog.svg)](https://travis-ci.org/cketti/ckChangeLog) -[![Maven Central](https://maven-badges.herokuapp.com/maven-central/de.cketti.library.changelog/ckchangelog/badge.svg)](https://maven-badges.herokuapp.com/maven-central/de.cketti.library.changelog/ckchangelog) +[![Maven Central](https://maven-badges.herokuapp.com/maven-central/de.cketti.library.changelog/ckchangelog-dialog/badge.svg)](https://maven-badges.herokuapp.com/maven-central/de.cketti.library.changelog/ckchangelog-dialog) ![Screenshot](screenshot_1.png) ![Screenshot](screenshot_2.png) @@ -14,14 +14,14 @@ This library provides an easy way to display a change log in your app. * Can display the complete change log history * Uses a simple XML file as source * Supports partial translations - * Easily extendable to use something other than a dialog + * Easily extensible to use something other than a dialog Repository at . ## Usage -1. Create the master change log in `res/xml/changelog_master.xml`. Formatted like this: +1. Create the master change log in `res/raw/changelog_master.xml`. Formatted like this: ```xml @@ -40,34 +40,29 @@ Repository at . ``` 2. Create translations of this `changelog_master.xml` file in files named `changelog.xml` under -language-specific versions of `res/xml/`, e.g. `res/xml-de/changelog.xml`. +language-specific versions of `res/raw/`, e.g. `res/raw-de/changelog.xml`. -3. Display the change log dialog by putting the following code in your activity's `onCreate()` method: +3. Display the change log dialog by putting the following code in your Activity's `onCreate()` method: ```java - ChangeLog cl = new ChangeLog(this); - if (cl.isFirstRun()) { - cl.getLogDialog().show(); + DialogChangeLog changeLog = DialogChangeLog.newInstance(this); + if (changeLog.isFirstRun()) { + changeLog.getLogDialog().show(); } ``` ## Include the library -The easiest way to add ckChangeLog to your project is via Gradle. Just add the following lines to your `build.gradle`: +Add the following lines to your `build.gradle` file: ```groovy dependencies { - compile 'de.cketti.library.changelog:ckchangelog:1.2.2' + compile 'de.cketti.library.changelog:ckchangelog-legacy-dialog:2.0.0' } ``` -To tell Gradle where to find the library, make sure `build.gradle` also contains this: - -```groovy -repositories { - mavenCentral() -} -``` +The library is uploaded to Maven Central and should be mirrored by JCenter. Make sure your `repositories` block +contains either `mavenCentral()` or `jcenter()`. ## Customize labels @@ -78,37 +73,9 @@ In order to change the labels of the dialog add the following items to your `str What\'s New OK More… +Version %s ``` -## Changelog - -### Version 1.2.2 (2015-01-09) -* Added Ukrainian translation - -### Version 1.2.1 -* Add support for [AboutLibraries](https://github.com/mikepenz/AboutLibraries) -* Fix build scripts so Javadoc JAR is properly created - -### Version 1.2.0 -* Made constant `DEFAULT_CSS` public -* Changed internals to make it easier to read the change log from different sources -* Added public method `getChangeLog(boolean)` that returns a list of `ReleaseItem`s -* Changed minSdkVersion to 4 -* Switched to Gradle as build system -* Added Greek, Spanish, Polish, and Russian translation - -### Version 1.1.0 -* Added method `skipLogDialog()` -* Added Slovak and German translation - -### Version 1.0.0 -* **Breaking change!** Moved master translation from `res/raw/changelog.xml` to `res/xml/changelog_master.xml` -* Added German translation of the sample app - -### Version 0.1 -* Initial release - - ## Acknowledgments This library is based on: @@ -117,12 +84,12 @@ This library is based on: Other contributors: * [See here](https://github.com/cketti/ckChangeLog/graphs/contributors) -* You? Please create pull requests against the [dev](https://github.com/cketti/ckChangeLog/tree/dev) branch +* You? Create a [pull request](https://github.com/cketti/ckChangeLog/pulls). ## License - Copyright (C) 2012-2015 cketti and contributors + Copyright (C) 2012-2018 cketti and contributors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/android-mvn-push.gradle b/android-mvn-push.gradle index f49a930..85b5529 100644 --- a/android-mvn-push.gradle +++ b/android-mvn-push.gradle @@ -1,5 +1,5 @@ if (!(hasProperty("nexusUsername") && hasProperty("nexusPassword"))) { - return; + return } apply plugin: 'maven' @@ -19,6 +19,9 @@ afterEvaluate { project -> repository(url: "https://oss.sonatype.org/service/local/staging/deploy/maven2/") { authentication(userName: nexusUsername, password: nexusPassword) } + snapshotRepository(url: "https://oss.sonatype.org/content/repositories/snapshots") { + authentication(userName: nexusUsername, password: nexusPassword) + } pom.project { name project.pom.name diff --git a/build.gradle b/build.gradle index a26292d..ae2e9bf 100644 --- a/build.gradle +++ b/build.gradle @@ -1,14 +1,41 @@ buildscript { repositories { - mavenCentral() + google() + jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:1.0.0' + classpath 'com.android.tools.build:gradle:3.0.1' + } +} + +allprojects { + repositories { + google() + mavenCentral() } } ext { - propBuildToolsVersion = "21.1.2" - propCompileSdkVersion = 21 + propBuildToolsVersion = "27.0.3" + propCompileSdkVersion = 27 + libraryVersion = "2.0.0-SNAPSHOT" + + pom = [ + group: "de.cketti.library.changelog", + name: "ckChangeLog Library", + description: "An Android Library to display a Change Log", + url: "https://github.com/cketti/ckChangeLog", + + scmUrl: "https://github.com/cketti/ckChangeLog", + scmConnection: "scm:git@github.com:cketti/ckChangeLog.git", + scmDevConnection: "scm:git@github.com:cketti/ckChangeLog.git", + + licenseName: "The Apache Software License, Version 2.0", + licenseUrl: "http://www.apache.org/licenses/LICENSE-2.0.txt", + licenseDist: "repo", + + developerId: "cketti", + developerName: "cketti" + ] } diff --git a/ckChangeLog-core/build.gradle b/ckChangeLog-core/build.gradle new file mode 100644 index 0000000..8f08be3 --- /dev/null +++ b/ckChangeLog-core/build.gradle @@ -0,0 +1,27 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion propCompileSdkVersion + buildToolsVersion propBuildToolsVersion + + defaultConfig { + versionName libraryVersion + minSdkVersion 14 + + resValue "string", "library_ckChangeLog_libraryVersion", libraryVersion + } +} + +dependencies { + implementation 'com.android.support:support-annotations:27.1.1' + + testImplementation 'org.robolectric:robolectric:3.7.1' + testImplementation 'junit:junit:4.12' + testImplementation 'org.mockito:mockito-core:2.18.0' +} + +project.ext.pom = rootProject.pom +project.ext.pom['artifactId'] = "ckchangelog-core" + +apply from: '../android-mvn-push.gradle' + diff --git a/ckChangeLog-core/src/main/AndroidManifest.xml b/ckChangeLog-core/src/main/AndroidManifest.xml new file mode 100644 index 0000000..5a84ed3 --- /dev/null +++ b/ckChangeLog-core/src/main/AndroidManifest.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/ckChangeLog-core/src/main/java/de/cketti/changelog/ChangeLog.java b/ckChangeLog-core/src/main/java/de/cketti/changelog/ChangeLog.java new file mode 100644 index 0000000..c845f73 --- /dev/null +++ b/ckChangeLog-core/src/main/java/de/cketti/changelog/ChangeLog.java @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2012-2018 cketti and contributors + * https://github.com/cketti/ckChangeLog/graphs/contributors + * + * Portions Copyright (C) 2012 Martin van Zuilekom (http://martin.cubeactive.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * + * Based on android-change-log: + * + * Copyright (C) 2011, Karsten Priegnitz + * + * Permission to use, copy, modify, and distribute this piece of software + * for any purpose with or without fee is hereby granted, provided that + * the above copyright notice and this permission notice appear in the + * source code of all copies. + * + * It would be appreciated if you mention the author in your change log, + * contributors list or the like. + * + * http://code.google.com/p/android-change-log/ + */ +package de.cketti.changelog; + + +import java.util.List; + +import android.content.Context; +import android.content.SharedPreferences; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.res.Resources; +import android.preference.PreferenceManager; +import android.util.Log; + + +/** + * Generate a full or partial (What's New) Change Log. + */ +public final class ChangeLog { + private static final String LOG_TAG = "ckChangeLog"; + private static final String VERSION_KEY = "ckChangeLog_last_version_code"; + private static final int NO_VERSION = -1; + + + private final Context context; + private final SharedPreferences preferences; + private final ChangeLogProvider changeLogProvider; + private int lastVersionCode; + private int currentVersionCode; + private String currentVersionName; + + + /** + * Create a {@code ChangeLog} instance using the default {@link SharedPreferences} file. + * + * @param context + * Context that is used to access resources. + */ + public static ChangeLog newInstance(Context context) { + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); + return newInstance(context, preferences); + } + + /** + * Create a {@code ChangeLog} instance using the supplied {@code SharedPreferences} instance. + * + * @param context + * Context that is used to access resources. + * @param preferences + * {@code SharedPreferences} instance that is used to persist the last version code. + * + */ + public static ChangeLog newInstance(Context context, SharedPreferences preferences) { + Resources resources = context.getResources(); + ChangeLogProvider masterChangeLogProvider = new ResourceChangeLogProvider(resources, R.raw.changelog_master); + ChangeLogProvider localizedChangeLogProvider = new ResourceChangeLogProvider(resources, R.raw.changelog); + ChangeLogProvider changeLogProvider = new MergedChangeLogProvider( + masterChangeLogProvider, localizedChangeLogProvider); + + return newInstance(context, preferences, changeLogProvider); + } + + /** + * Create a {@code ChangeLog} instance using the supplied {@code SharedPreferences} and {@code ChangeLogProvider} + * instances. + * + * @param context + * Context that is used to access resources. + * @param preferences + * {@code SharedPreferences} instance that is used to persist the last version code. + * @param changeLogProvider + * {@code ChangeLogProvider} instance that is used to retrieve the Change Log. + */ + public static ChangeLog newInstance(Context context, SharedPreferences preferences, + ChangeLogProvider changeLogProvider) { + ChangeLog changeLog = new ChangeLog(context, preferences, changeLogProvider); + changeLog.init(); + + return changeLog; + } + + private ChangeLog(Context context, SharedPreferences preferences, ChangeLogProvider changeLogProvider) { + this.context = context; + this.preferences = preferences; + this.changeLogProvider = changeLogProvider; + } + + private void init() { + lastVersionCode = preferences.getInt(VERSION_KEY, NO_VERSION); + + try { + PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0); + + currentVersionCode = packageInfo.versionCode; + currentVersionName = packageInfo.versionName; + } catch (NameNotFoundException e) { + currentVersionCode = NO_VERSION; + Log.e(LOG_TAG, "Could not get version information from manifest!", e); + } + } + + /** + * Get version code of last installation. + * + * @return The version code of the last installation of this app (as described in the former + * manifest). This will be the same as returned by {@link #getCurrentVersionCode()} the + * second time this version of the app is launched (more precisely: the second time + * {@code ChangeLog} is instantiated). + * + * @see android:versionCode + */ + public int getLastVersionCode() { + return lastVersionCode; + } + + /** + * Get version code of current installation. + * + * @return The version code of this app as described in the manifest. + * + * @see android:versionCode + */ + public int getCurrentVersionCode() { + return currentVersionCode; + } + + /** + * Get version name of current installation. + * + * @return The version name of this app as described in the manifest. + * + * @see android:versionName + */ + public String getCurrentVersionName() { + return currentVersionName; + } + + /** + * Check if this is the first execution of this app version. + * + * @return {@code true} if this version of your app is started the first time. + */ + public boolean isFirstRun() { + return lastVersionCode < currentVersionCode; + } + + /** + * Check if this is a new installation. + * + * @return {@code true} if your app including {@code ChangeLog} is started the first time ever. + * Also {@code true} if your app was uninstalled and installed again. + */ + public boolean isFirstRunEver() { + return lastVersionCode == NO_VERSION; + } + + /** + * Write current version code to the preferences. + * + *

+ * Future calls to {@link #isFirstRun()} and {@link #isFirstRunEver()} will return {@code false} + * for the current app version. + *

+ */ + public void writeCurrentVersion() { + lastVersionCode = currentVersionCode; + + SharedPreferences.Editor editor = preferences.edit(); + editor.putInt(VERSION_KEY, currentVersionCode); + editor.apply(); + } + + /** + * Returns the full Change Log. + * + * @return A sorted {@code List} containing {@link ReleaseItem}s representing the full Change Log. + */ + public List getChangeLog() { + return changeLogProvider.getChangeLog(); + } + + /** + * Returns the list of changes for versions newer than the last version ("What's New"). + * + * @return A sorted {@code List} containing {@link ReleaseItem}s representing the recent changes. + */ + public List getRecentChanges() { + return changeLogProvider.getChangeLogSince(lastVersionCode); + } +} diff --git a/ckChangeLog-core/src/main/java/de/cketti/changelog/ChangeLogProvider.java b/ckChangeLog-core/src/main/java/de/cketti/changelog/ChangeLogProvider.java new file mode 100644 index 0000000..8041e96 --- /dev/null +++ b/ckChangeLog-core/src/main/java/de/cketti/changelog/ChangeLogProvider.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2012-2018 cketti and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.cketti.changelog; + + +import java.util.List; + + +/** + * A Change Log provider that can return information about all versions or only ones newer than a given version code. + */ +public interface ChangeLogProvider { + /** + * Get all {@code ReleaseItem} entries of this Change Log. + */ + List getChangeLog(); + + /** + * Get only {@code ReleaseItem} entries newer than the given version code. + * + * @param lastVersionCode + * {@code ReleaseItem} entries with a version code lower than or equal to this value won't be returned. + */ + List getChangeLogSince(int lastVersionCode); +} diff --git a/ckChangeLog-core/src/main/java/de/cketti/changelog/InvalidChangeLogException.java b/ckChangeLog-core/src/main/java/de/cketti/changelog/InvalidChangeLogException.java new file mode 100644 index 0000000..f7e3e6b --- /dev/null +++ b/ckChangeLog-core/src/main/java/de/cketti/changelog/InvalidChangeLogException.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2012-2018 cketti and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.cketti.changelog; + + +/** + * Thrown by {@link XmlParser} when reading the Change Log XML fails. + */ +public class InvalidChangeLogException extends RuntimeException { + public InvalidChangeLogException(String detailMessage) { + super(detailMessage); + } + + public InvalidChangeLogException(String detailMessage, Throwable throwable) { + super(detailMessage, throwable); + } +} diff --git a/ckChangeLog-core/src/main/java/de/cketti/changelog/MergedChangeLogProvider.java b/ckChangeLog-core/src/main/java/de/cketti/changelog/MergedChangeLogProvider.java new file mode 100644 index 0000000..40274ca --- /dev/null +++ b/ckChangeLog-core/src/main/java/de/cketti/changelog/MergedChangeLogProvider.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2012-2018 cketti and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.cketti.changelog; + + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import android.util.SparseArray; + +import static de.cketti.changelog.Preconditions.checkNotNull; + + +/** + * {@link ChangeLogProvider} that merges the data from two {@code ChangeLogProvider}s. + * + *

+ * Change Log entries from the provider with localized data are favored over the entries from the master provider. + * However, when a specific entry is missing in the localized provider the entry from the master provider will be used. + *

+ */ +public final class MergedChangeLogProvider implements ChangeLogProvider { + private final ChangeLogProvider masterChangeLogProvider; + private final ChangeLogProvider localizedChangeLogProvider; + + + public MergedChangeLogProvider(ChangeLogProvider masterChangeLogProvider, + ChangeLogProvider localizedChangeLogProvider) { + this.masterChangeLogProvider = checkNotNull(masterChangeLogProvider, "masterChangeLogProvider == null"); + this.localizedChangeLogProvider = checkNotNull(localizedChangeLogProvider, + "localizedChangeLogProvider == null"); + } + + @Override + public List getChangeLog() { + List masterChangeLog = masterChangeLogProvider.getChangeLog(); + List localizedChangeLog = localizedChangeLogProvider.getChangeLog(); + return merge(masterChangeLog, localizedChangeLog); + } + + @Override + public List getChangeLogSince(int lastVersionCode) { + List masterChangeLog = masterChangeLogProvider.getChangeLogSince(lastVersionCode); + List localizedChangeLog = localizedChangeLogProvider.getChangeLogSince(lastVersionCode); + return merge(masterChangeLog, localizedChangeLog); + } + + private List merge(List masterChangeLog, List localizedChangeLog) { + SparseArray localizedChangeLogMap = new SparseArray<>(); + for (ReleaseItem releaseItem : localizedChangeLog) { + localizedChangeLogMap.put(releaseItem.versionCode, releaseItem); + } + + List mergedChangeLog = new ArrayList<>(masterChangeLog.size()); + for (int i = 0, len = masterChangeLog.size(); i < len; i++) { + ReleaseItem masterReleaseItem = masterChangeLog.get(i); + int key = masterReleaseItem.versionCode; + + // Use release information from localized change log and fall back to the master file if necessary. + ReleaseItem release = localizedChangeLogMap.get(key, masterReleaseItem); + + mergedChangeLog.add(release); + } + + Collections.sort(mergedChangeLog, new VersionCodeComparator()); + + return mergedChangeLog; + } + + + class VersionCodeComparator implements Comparator { + @Override + public int compare(ReleaseItem lhs, ReleaseItem rhs) { + if (lhs.versionCode < rhs.versionCode) { + return 1; + } else if (lhs.versionCode > rhs.versionCode) { + return -1; + } else { + return 0; + } + } + } +} diff --git a/ckChangeLog-core/src/main/java/de/cketti/changelog/Preconditions.java b/ckChangeLog-core/src/main/java/de/cketti/changelog/Preconditions.java new file mode 100644 index 0000000..925ae5e --- /dev/null +++ b/ckChangeLog-core/src/main/java/de/cketti/changelog/Preconditions.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2012-2018 cketti and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.cketti.changelog; + + +class Preconditions { + static T checkNotNull(T value, String message) { + if (value == null) { + throw new NullPointerException(message); + } + + return value; + } +} diff --git a/ckChangeLog-core/src/main/java/de/cketti/changelog/ReleaseItem.java b/ckChangeLog-core/src/main/java/de/cketti/changelog/ReleaseItem.java new file mode 100644 index 0000000..e453934 --- /dev/null +++ b/ckChangeLog-core/src/main/java/de/cketti/changelog/ReleaseItem.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2012-2018 cketti and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.cketti.changelog; + + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import android.support.annotation.Nullable; + +import static de.cketti.changelog.Preconditions.checkNotNull; + + +/** + * Container used to store information about a release/version. + */ +public final class ReleaseItem { + /** + * Version code of the release. + */ + public final int versionCode; + + /** + * Version name of the release. + */ + public final String versionName; + + /** + * Date of the release (optional). + */ + @Nullable + public final String date; + + /** + * List of changes introduced with that release. + */ + public final List changes; + + + public static ReleaseItem newInstance(int versionCode, String versionName, @Nullable String date, + List changes) { + List copiedChanges = new ArrayList<>(changes); + return new ReleaseItem(versionCode, versionName, date, copiedChanges); + } + + ReleaseItem(int versionCode, String versionName, @Nullable String date, List changes) { + this.versionCode = versionCode; + this.versionName = checkNotNull(versionName, "versionName == null"); + this.date = date; + this.changes = Collections.unmodifiableList(checkNotNull(changes, "changes == null")); + } + + @Override + public String toString() { + return "ReleaseItem{" + + "versionCode=" + versionCode + + ", versionName='" + versionName + '\'' + + ", date='" + date + '\'' + + ", changes=" + changes + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + ReleaseItem that = (ReleaseItem) o; + + if (versionCode != that.versionCode) { + return false; + } + if (!versionName.equals(that.versionName)) { + return false; + } + if (date != null ? !date.equals(that.date) : that.date != null) { + return false; + } + return changes.equals(that.changes); + } + + @Override + public int hashCode() { + int result = versionCode; + result = 31 * result + versionName.hashCode(); + result = 31 * result + (date != null ? date.hashCode() : 0); + result = 31 * result + changes.hashCode(); + return result; + } +} diff --git a/ckChangeLog-core/src/main/java/de/cketti/changelog/ResourceChangeLogProvider.java b/ckChangeLog-core/src/main/java/de/cketti/changelog/ResourceChangeLogProvider.java new file mode 100644 index 0000000..b44213a --- /dev/null +++ b/ckChangeLog-core/src/main/java/de/cketti/changelog/ResourceChangeLogProvider.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2012-2018 cketti and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.cketti.changelog; + + +import java.io.IOException; +import java.io.InputStream; +import java.util.List; + +import android.content.res.Resources; +import android.support.annotation.IdRes; +import android.support.annotation.RawRes; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlPullParserFactory; + + +/** + * A {@link ChangeLogProvider} that reads data from an Android XML resource. + */ +class ResourceChangeLogProvider implements ChangeLogProvider { + private final Resources resources; + private final int resourceId; + + + ResourceChangeLogProvider(Resources resources, @RawRes int resourceId) { + this.resources = resources; + this.resourceId = resourceId; + } + + @Override + public List getChangeLog() { + try { + XmlPullParser xmlPullParser = XmlPullParserFactory.newInstance().newPullParser(); + InputStream inputStream = resources.openRawResource(resourceId); + try { + xmlPullParser.setInput(inputStream, null); + return XmlParser.parse(xmlPullParser); + } finally { + inputStream.close(); + } + } catch (XmlPullParserException | IOException e) { + throw new InvalidChangeLogException("Error parsing XML resource: " + getResourceName(resourceId), e); + } + } + + @Override + public List getChangeLogSince(int lastVersionCode) { + try { + XmlPullParser xmlPullParser = XmlPullParserFactory.newInstance().newPullParser(); + InputStream inputStream = resources.openRawResource(resourceId); + try { + xmlPullParser.setInput(inputStream, null); + return XmlParser.parse(xmlPullParser, lastVersionCode); + } finally { + inputStream.close(); + } + } catch (XmlPullParserException | IOException e) { + throw new InvalidChangeLogException("Error parsing XML resource: " + getResourceName(resourceId), e); + } + } + + private String getResourceName(@IdRes int resourceId) { + try { + return resources.getResourceName(resourceId); + } catch (Resources.NotFoundException e) { + return Integer.toString(resourceId); + } + } +} diff --git a/ckChangeLog-core/src/main/java/de/cketti/changelog/XmlParser.java b/ckChangeLog-core/src/main/java/de/cketti/changelog/XmlParser.java new file mode 100644 index 0000000..fca69b3 --- /dev/null +++ b/ckChangeLog-core/src/main/java/de/cketti/changelog/XmlParser.java @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2012-2018 cketti and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.cketti.changelog; + + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + + +/** + * The parser for ckChangeLog's XML file format. + */ +public final class XmlParser { + private static final int NO_VERSION = -1; + private static final String TAG_CHANGELOG = "changelog"; + private static final String TAG_RELEASE = "release"; + private static final String ATTRIBUTE_VERSION = "version"; + private static final String ATTRIBUTE_VERSION_CODE = "versioncode"; + private static final String ATTRIBUTE_DATE = "date"; + private static final String TAG_CHANGE = "change"; + + + private final XmlPullParser xmlPullParser; + private final int lastVersionCode; + private final List result; + + + public static List parse(XmlPullParser xmlPullParser) { + XmlParser xmlParser = new XmlParser(xmlPullParser, NO_VERSION); + return xmlParser.readChangeLog(); + } + + public static List parse(XmlPullParser xmlPullParser, int lastVersionCode) { + XmlParser xmlParser = new XmlParser(xmlPullParser, lastVersionCode); + return xmlParser.readChangeLog(); + } + + private XmlParser(XmlPullParser xmlPullParser, int lastVersionCode) { + this.xmlPullParser = xmlPullParser; + this.lastVersionCode = lastVersionCode; + result = new ArrayList<>(); + } + + private List readChangeLog() { + try { + while (xmlPullParser.getEventType() != XmlPullParser.START_TAG) { + xmlPullParser.next(); + } + + assertElementStart(TAG_CHANGELOG); + + int eventType; + do { + eventType = xmlPullParser.next(); + if (eventType == XmlPullParser.START_TAG) { + if (parseReleaseElement()) { + // Stop reading more elements if this entry is not newer than the last version. + break; + } + } + } while (eventType != XmlPullParser.END_DOCUMENT); + } catch (XmlPullParserException | IOException e) { + throw new IllegalStateException(e); + } + + return Collections.unmodifiableList(result); + } + + private boolean parseReleaseElement() throws XmlPullParserException, IOException { + assertElementStart(TAG_RELEASE); + + String versionName = parseVersionAttribute(); + String date = parseDateAttribute(); + int versionCode = parseVersionCodeAttribute(); + + if (lastVersionCode != NO_VERSION && versionCode <= lastVersionCode) { + return true; + } + + List changes = new ArrayList<>(); + int eventType; + do { + eventType = xmlPullParser.next(); + if (eventType == XmlPullParser.START_TAG) { + String text = parseChangeElement(); + changes.add(text); + } + } while (eventType != XmlPullParser.END_TAG); + + if (changes.isEmpty()) { + throw new InvalidChangeLogException(" tag must contain at least one element"); + } + + ReleaseItem release = new ReleaseItem(versionCode, versionName, date, changes); + result.add(release); + + return false; + } + + private String parseVersionAttribute() { + assertAttributePresent(ATTRIBUTE_VERSION); + return xmlPullParser.getAttributeValue(null, ATTRIBUTE_VERSION); + } + + private String parseDateAttribute() { + return xmlPullParser.getAttributeValue(null, ATTRIBUTE_DATE); + } + + private int parseVersionCodeAttribute() { + assertAttributePresent(ATTRIBUTE_VERSION_CODE); + + String versionCodeStr = xmlPullParser.getAttributeValue(null, ATTRIBUTE_VERSION_CODE); + try { + return Integer.parseInt(versionCodeStr); + } catch (NumberFormatException e) { + throw new InvalidChangeLogException("Invalid version code value: " + versionCodeStr); + } + } + + private String parseChangeElement() throws XmlPullParserException, IOException { + assertElementStart(TAG_CHANGE); + + assertNextEventType(XmlPullParser.TEXT, "Expected text"); + String text = cleanText(xmlPullParser.getText()); + + assertNextEventType(XmlPullParser.END_TAG, "Expected "); + + return text; + } + + private String cleanText(String text) { + return text.trim().replaceAll("\\s+", " "); + } + + private void assertNextEventType(int expectedEventType, String message) throws XmlPullParserException, IOException { + xmlPullParser.next(); + + int eventType = xmlPullParser.getEventType(); + if (eventType != expectedEventType) { + throw new InvalidChangeLogException(message); + } + } + + private void assertElementStart(String expectedElementName) { + String tagName = xmlPullParser.getName(); + if (!expectedElementName.equals(tagName)) { + throw new InvalidChangeLogException( + "Unexpected tag: " + tagName + " (wanted: " + expectedElementName + ")"); + } + } + + private void assertAttributePresent(String attributeName) { + String attributeValue = xmlPullParser.getAttributeValue(null, attributeName); + if (attributeValue == null) { + throw new InvalidChangeLogException("Missing attribute: " + attributeName); + } + } +} diff --git a/ckChangeLog/src/main/res/xml/changelog.xml b/ckChangeLog-core/src/main/res/raw/changelog.xml similarity index 100% rename from ckChangeLog/src/main/res/xml/changelog.xml rename to ckChangeLog-core/src/main/res/raw/changelog.xml diff --git a/ckChangeLog-core/src/main/res/raw/changelog_master.xml b/ckChangeLog-core/src/main/res/raw/changelog_master.xml new file mode 100644 index 0000000..3244cda --- /dev/null +++ b/ckChangeLog-core/src/main/res/raw/changelog_master.xml @@ -0,0 +1,6 @@ + + + diff --git a/ckChangeLog/src/main/res/values/ckChangeLog_strings.xml b/ckChangeLog-core/src/main/res/values/ckChangeLog_strings.xml similarity index 93% rename from ckChangeLog/src/main/res/values/ckChangeLog_strings.xml rename to ckChangeLog-core/src/main/res/values/ckChangeLog_strings.xml index f79fd0d..ad6215e 100644 --- a/ckChangeLog/src/main/res/values/ckChangeLog_strings.xml +++ b/ckChangeLog-core/src/main/res/values/ckChangeLog_strings.xml @@ -7,7 +7,6 @@ http://cketti.de/ ckChangeLog An Android Library to display a Change Log - 1.2.2 https://github.com/cketti/ckChangeLog Apache Version 2.0 apache_2_0 diff --git a/ckChangeLog-core/src/test/java/de/cketti/changelog/ChangeLogTest.java b/ckChangeLog-core/src/test/java/de/cketti/changelog/ChangeLogTest.java new file mode 100644 index 0000000..1634e54 --- /dev/null +++ b/ckChangeLog-core/src/test/java/de/cketti/changelog/ChangeLogTest.java @@ -0,0 +1,199 @@ +package de.cketti.changelog; + + +import java.util.ArrayList; +import java.util.List; + +import android.content.Context; +import android.content.SharedPreferences; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + + +@RunWith(RobolectricTestRunner.class) +public class ChangeLogTest { + private static final int APP_VERSION_CODE = 3; + private static final String APP_VERSION_NAME = "1.2"; + private static final String APP_PACKAGE_NAME = "org.example.app"; + + + private Context context; + private SharedPreferences preferences; + private ChangeLogProvider changeLogProvider; + + + @Before + public void setUp() throws Exception { + context = createContext(); + preferences = createSharedPreferences(); + changeLogProvider = createChangeLogProvider(); + } + + @Test + public void getLastVersionCode() { + setLastVersionCode(2); + ChangeLog changeLog = ChangeLog.newInstance(context, preferences, changeLogProvider); + + int lastVersionCode = changeLog.getLastVersionCode(); + + assertEquals(2, lastVersionCode); + } + + @Test + public void getCurrentVersionCode() { + ChangeLog changeLog = ChangeLog.newInstance(context, preferences, changeLogProvider); + + int currentVersionCode = changeLog.getCurrentVersionCode(); + + assertEquals(APP_VERSION_CODE, currentVersionCode); + } + + @Test + public void getCurrentVersionName() { + ChangeLog changeLog = ChangeLog.newInstance(context, preferences, changeLogProvider); + + String currentVersionName = changeLog.getCurrentVersionName(); + + assertEquals(APP_VERSION_NAME, currentVersionName); + } + + @Test + public void isFirstRun_withLastVersionCodeSmallerThanCurrentVersion_shouldReturnTrue() { + setLastVersionCode(1); + ChangeLog changeLog = ChangeLog.newInstance(context, preferences, changeLogProvider); + + boolean firstRun = changeLog.isFirstRun(); + + assertTrue(firstRun); + } + + @Test + public void isFirstRun_withLastVersionCodeEqualToCurrentVersion_shouldReturnFalse() { + setLastVersionCode(APP_VERSION_CODE); + ChangeLog changeLog = ChangeLog.newInstance(context, preferences, changeLogProvider); + + boolean firstRun = changeLog.isFirstRun(); + + assertFalse(firstRun); + } + + @Test + public void isFirstRun_afterCallingWriteCurrentVersion_shouldReturnFalse() { + setLastVersionCode(1); + ChangeLog changeLog = ChangeLog.newInstance(context, preferences, changeLogProvider); + changeLog.writeCurrentVersion(); + + boolean firstRun = changeLog.isFirstRun(); + + assertFalse(firstRun); + } + + @Test + public void isFirstRunEver_withLastVersionCodeSet_shouldReturnFalse() { + setLastVersionCode(1); + ChangeLog changeLog = ChangeLog.newInstance(context, preferences, changeLogProvider); + + boolean firstRunEver = changeLog.isFirstRunEver(); + + assertFalse(firstRunEver); + } + + @Test + public void isFirstRunEver_withLastVersionCodeUnset_shouldReturnTrue() { + setLastVersionCode(-1); + ChangeLog changeLog = ChangeLog.newInstance(context, preferences, changeLogProvider); + + boolean firstRunEver = changeLog.isFirstRunEver(); + + assertTrue(firstRunEver); + } + + @Test + public void isFirstRunEver_afterCallingWriteCurrentVersion_shouldReturnFalse() { + setLastVersionCode(-1); + ChangeLog changeLog = ChangeLog.newInstance(context, preferences, changeLogProvider); + changeLog.writeCurrentVersion(); + + boolean firstRunEver = changeLog.isFirstRunEver(); + + assertFalse(firstRunEver); + } + + @Test + public void writeCurrentVersion() { + ChangeLog changeLog = ChangeLog.newInstance(context, preferences, changeLogProvider); + + changeLog.writeCurrentVersion(); + + int lastVersionCode = preferences.getInt("ckChangeLog_last_version_code", -1); + assertEquals(APP_VERSION_CODE, lastVersionCode); + } + + @Test + public void getChangeLog_shouldReturnDataFromChangeLogProvider() { + ChangeLog changeLog = ChangeLog.newInstance(context, preferences, changeLogProvider); + List releaseItemsToReturn = new ArrayList<>(); + when(changeLogProvider.getChangeLog()).thenReturn(releaseItemsToReturn); + + List releaseItems = changeLog.getChangeLog(); + + assertSame(releaseItemsToReturn, releaseItems); + } + + @Test + public void getRecentChanges_shouldReturnDataFromChangeLogProvider() { + setLastVersionCode(2); + ChangeLog changeLog = ChangeLog.newInstance(context, preferences, changeLogProvider); + List releaseItemsToReturn = new ArrayList<>(); + when(changeLogProvider.getChangeLogSince(2)).thenReturn(releaseItemsToReturn); + + List releaseItems = changeLog.getRecentChanges(); + + assertSame(releaseItemsToReturn, releaseItems); + } + + private void setLastVersionCode(int lastVersionCode) { + preferences.edit().putInt("ckChangeLog_last_version_code", lastVersionCode).apply(); + } + + private Context createContext() throws Exception { + PackageInfo packageInfo = new PackageInfo(); + packageInfo.versionCode = APP_VERSION_CODE; + packageInfo.versionName = APP_VERSION_NAME; + + PackageManager packageManager = mock(PackageManager.class); + //noinspection WrongConstant + when(packageManager.getPackageInfo(anyString(), anyInt())).thenReturn(packageInfo); + + Context context = mock(Context.class); + when(context.getPackageName()).thenReturn(APP_PACKAGE_NAME); + when(context.getPackageManager()).thenReturn(packageManager); + return context; + } + + private SharedPreferences createSharedPreferences() { + Context context = RuntimeEnvironment.application; + SharedPreferences sharedPreferences = context.getSharedPreferences("test", Context.MODE_PRIVATE); + sharedPreferences.edit().clear().apply(); + return sharedPreferences; + } + + private ChangeLogProvider createChangeLogProvider() { + return mock(ChangeLogProvider.class); + } +} diff --git a/ckChangeLog-core/src/test/java/de/cketti/changelog/MergedChangeLogProviderTest.java b/ckChangeLog-core/src/test/java/de/cketti/changelog/MergedChangeLogProviderTest.java new file mode 100644 index 0000000..6d4a94b --- /dev/null +++ b/ckChangeLog-core/src/test/java/de/cketti/changelog/MergedChangeLogProviderTest.java @@ -0,0 +1,87 @@ +package de.cketti.changelog; + + +import java.util.List; + +import de.cketti.changelog.helper.ChangeLogBuilder; +import de.cketti.changelog.helper.ChangeLogProviderBuilder; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; + + +@RunWith(RobolectricTestRunner.class) +public class MergedChangeLogProviderTest { + @Test + public void getChangeLog_shouldReturnMergedChangeLog() { + ChangeLogProvider masterChangeLogProvider = new ChangeLogProviderBuilder() + .addVersion(1, "1.0", null, "First version") + .addVersion(2, "1.1", "2000-01-01", "Some new feature", "Small bugfix") + .addVersion(3, "1.2", "2010-02-01", "The latest change") + .build(); + ChangeLogProvider localizedChangeLogProvider = new ChangeLogProviderBuilder() + .addVersion(1, "1.0", null, "Erste Version") + .addVersion(2, "1.1", "2000-01-01", "Neue Funktionalität", "Kleiner Fehlerbehebung") + .build(); + MergedChangeLogProvider changeLogProvider = + new MergedChangeLogProvider(masterChangeLogProvider, localizedChangeLogProvider); + + List changeLog = changeLogProvider.getChangeLog(); + + assertEquals(changeLog, new ChangeLogBuilder() + .addVersion(1, "1.0", null, "Erste Version") + .addVersion(2, "1.1", "2000-01-01", "Neue Funktionalität", "Kleiner Fehlerbehebung") + .addVersion(3, "1.2", "2010-02-01", "The latest change") + .build()); + } + + @Test + public void getChangeLogSince_shouldReturnMergedChangeLog() { + ChangeLogProvider masterChangeLogProvider = new ChangeLogProviderBuilder() + .addVersion(1, "1.0", null, "First version") + .addVersion(2, "1.1", null, "Some new feature", "Small bugfix") + .addVersion(3, "1.2", null, "The latest change") + .build(); + ChangeLogProvider localizedChangeLogProvider = new ChangeLogProviderBuilder() + .addVersion(1, "1.0", null, "Erste Version") + .addVersion(2, "1.1", null, "Neue Funktionalität", "Kleiner Fehlerbehebung") + .build(); + MergedChangeLogProvider changeLogProvider = + new MergedChangeLogProvider(masterChangeLogProvider, localizedChangeLogProvider); + + List changeLog = changeLogProvider.getChangeLogSince(1); + + assertEquals(changeLog, new ChangeLogBuilder() + .addVersion(2, "1.1", null, "Neue Funktionalität", "Kleiner Fehlerbehebung") + .addVersion(3, "1.2", null, "The latest change") + .build()); + } + + @Test + public void constructor_withFirstArgumentNull_shouldThrow() { + ChangeLogProvider changeLogProvider = mock(ChangeLogProvider.class); + + try { + new MergedChangeLogProvider(null, changeLogProvider); + fail(); + } catch (NullPointerException e) { + assertEquals("masterChangeLogProvider == null", e.getMessage()); + } + } + + @Test + public void constructor_withSecondArgumentNull_shouldThrow() { + ChangeLogProvider changeLogProvider = mock(ChangeLogProvider.class); + + try { + new MergedChangeLogProvider(changeLogProvider, null); + fail(); + } catch (NullPointerException e) { + assertEquals("localizedChangeLogProvider == null", e.getMessage()); + } + } +} diff --git a/ckChangeLog-core/src/test/java/de/cketti/changelog/XmlParserTest.java b/ckChangeLog-core/src/test/java/de/cketti/changelog/XmlParserTest.java new file mode 100644 index 0000000..8886631 --- /dev/null +++ b/ckChangeLog-core/src/test/java/de/cketti/changelog/XmlParserTest.java @@ -0,0 +1,162 @@ +package de.cketti.changelog; + + +import java.io.InputStream; +import java.util.List; + +import de.cketti.changelog.helper.ChangeLogBuilder; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlPullParserFactory; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + + +@RunWith(RobolectricTestRunner.class) +public class XmlParserTest { + @Test + public void parse_withEmptyChangeLog() throws Exception { + XmlPullParser xmlPullParser = createParserForFile("/valid_empty_changelog.xml"); + + List releaseItems = XmlParser.parse(xmlPullParser); + + assertTrue(releaseItems.isEmpty()); + } + + @Test + public void parse_withValidChangeLog() throws Exception { + XmlPullParser xmlPullParser = createParserForFile("/valid_changelog_with_version_dates.xml"); + + List releaseItems = XmlParser.parse(xmlPullParser); + + assertEquals(new ChangeLogBuilder() + .addVersion(1, "1.0.0", "2018-01-01", "First release") + .addVersion(2, "2.0.0", "2018-02-01", "Second release") + .addVersion(3, "3.0.0", "2018-03-01", "Third release") + .build(), + releaseItems); + } + + @Test + public void parse_withValidChangeLogNotContainingDates() throws Exception { + XmlPullParser xmlPullParser = createParserForFile("/valid_changelog_without_version_dates.xml"); + + List releaseItems = XmlParser.parse(xmlPullParser); + + assertEquals(new ChangeLogBuilder() + .addVersion(1, "1.0", null, "First release") + .addVersion(10, "2.0", null, "Fixed: A bug fix", "Some other changes I can't quite remember") + .addVersion(11, "2.1", null, "Totally new and shiny version") + .build(), + releaseItems); + } + + @Test + public void parse_withWrongRootElement_shouldThrow() throws Exception { + XmlPullParser xmlPullParser = createParserForFile("/invalid_wrong_root_element.xml"); + + try { + XmlParser.parse(xmlPullParser); + fail(); + } catch (InvalidChangeLogException e) { + assertEquals("Unexpected tag: random (wanted: changelog)", e.getMessage()); + } + } + + @Test + public void parse_withWrongReleaseElement_shouldThrow() throws Exception { + XmlPullParser xmlPullParser = createParserForFile("/invalid_wrong_release_element.xml"); + + try { + XmlParser.parse(xmlPullParser); + fail(); + } catch (InvalidChangeLogException e) { + assertEquals("Unexpected tag: random (wanted: release)", e.getMessage()); + } + } + + @Test + public void parse_withReleaseElementMissingVersionAttribute_shouldThrow() throws Exception { + XmlPullParser xmlPullParser = createParserForFile("/invalid_release_element_with_missing_version_attribute.xml"); + + try { + XmlParser.parse(xmlPullParser); + fail(); + } catch (InvalidChangeLogException e) { + assertEquals("Missing attribute: version", e.getMessage()); + } + } + + @Test + public void parse_withReleaseElementMissingVersioncodeAttribute_shouldThrow() throws Exception { + XmlPullParser xmlPullParser = createParserForFile( + "/invalid_release_element_with_missing_versioncode_attribute.xml"); + + try { + XmlParser.parse(xmlPullParser); + fail(); + } catch (InvalidChangeLogException e) { + assertEquals("Missing attribute: version", e.getMessage()); + } + } + + @Test + public void parse_withEmptyReleaseElement_shouldThrow() throws Exception { + XmlPullParser xmlPullParser = createParserForFile("/invalid_empty_release_element.xml"); + + try { + XmlParser.parse(xmlPullParser); + fail(); + } catch (InvalidChangeLogException e) { + assertEquals(" tag must contain at least one element", e.getMessage()); + } + } + + @Test + public void parse_withEmptyChangeElement_shouldThrow() throws Exception { + XmlPullParser xmlPullParser = createParserForFile("/invalid_empty_change_element.xml"); + + try { + XmlParser.parse(xmlPullParser); + fail(); + } catch (InvalidChangeLogException e) { + assertEquals("Expected text", e.getMessage()); + } + } + + @Test + public void parse_withWrongChangeElement_shouldThrow() throws Exception { + XmlPullParser xmlPullParser = createParserForFile("/invalid_wrong_change_element.xml"); + + try { + XmlParser.parse(xmlPullParser); + fail(); + } catch (InvalidChangeLogException e) { + assertEquals("Unexpected tag: random (wanted: change)", e.getMessage()); + } + } + + @Test + public void parse_withChangeElementContainingElement_shouldThrow() throws Exception { + XmlPullParser xmlPullParser = createParserForFile("/invalid_change_element_contains_element.xml"); + + try { + XmlParser.parse(xmlPullParser); + fail(); + } catch (InvalidChangeLogException e) { + assertEquals("Expected ", e.getMessage()); + } + } + + private XmlPullParser createParserForFile(String resourceName) throws XmlPullParserException { + XmlPullParser xmlPullParser = XmlPullParserFactory.newInstance().newPullParser(); + InputStream inputStream = getClass().getResourceAsStream(resourceName); + xmlPullParser.setInput(inputStream, "utf-8"); + return xmlPullParser; + } +} diff --git a/ckChangeLog-core/src/test/java/de/cketti/changelog/helper/ChangeLogBuilder.java b/ckChangeLog-core/src/test/java/de/cketti/changelog/helper/ChangeLogBuilder.java new file mode 100644 index 0000000..a1be405 --- /dev/null +++ b/ckChangeLog-core/src/test/java/de/cketti/changelog/helper/ChangeLogBuilder.java @@ -0,0 +1,28 @@ +package de.cketti.changelog.helper; + + +import java.util.ArrayList; +import java.util.List; + +import de.cketti.changelog.ReleaseItem; +import edu.emory.mathcs.backport.java.util.Collections; + +import static java.util.Arrays.asList; + + +public class ChangeLogBuilder { + private List releaseItems = new ArrayList<>(); + + + public ChangeLogBuilder addVersion(int versionCode, String versionName, String date, String... changes) { + ReleaseItem releaseItem = ReleaseItem.newInstance(versionCode, versionName, date, asList(changes)); + releaseItems.add(releaseItem); + return this; + } + + public List build() { + List items = new ArrayList<>(releaseItems); + Collections.reverse(items); + return items; + } +} diff --git a/ckChangeLog-core/src/test/java/de/cketti/changelog/helper/ChangeLogProviderBuilder.java b/ckChangeLog-core/src/test/java/de/cketti/changelog/helper/ChangeLogProviderBuilder.java new file mode 100644 index 0000000..32643b4 --- /dev/null +++ b/ckChangeLog-core/src/test/java/de/cketti/changelog/helper/ChangeLogProviderBuilder.java @@ -0,0 +1,46 @@ +package de.cketti.changelog.helper; + + +import java.util.ArrayList; +import java.util.List; + +import de.cketti.changelog.ChangeLogProvider; +import de.cketti.changelog.ReleaseItem; + +import static java.util.Arrays.asList; +import static java.util.Collections.unmodifiableList; + + +public class ChangeLogProviderBuilder { + private List releaseItems = new ArrayList<>(); + + + public ChangeLogProviderBuilder addVersion(int versionCode, String versionName, String date, + String... changes) { + ReleaseItem releaseItem = ReleaseItem.newInstance(versionCode, versionName, date, asList(changes)); + releaseItems.add(releaseItem); + return this; + } + + public ChangeLogProvider build() { + final List safeReleaseItems = new ArrayList<>(releaseItems); + return new ChangeLogProvider() { + @Override + public List getChangeLog() { + return unmodifiableList(safeReleaseItems); + } + + @Override + public List getChangeLogSince(int lastVersionCode) { + List result = new ArrayList<>(); + for (ReleaseItem releaseItem : releaseItems) { + if (releaseItem.versionCode > lastVersionCode) { + result.add(releaseItem); + } + } + + return result; + } + }; + } +} diff --git a/ckChangeLog-core/src/test/resources/invalid_change_element_contains_element.xml b/ckChangeLog-core/src/test/resources/invalid_change_element_contains_element.xml new file mode 100644 index 0000000..263e872 --- /dev/null +++ b/ckChangeLog-core/src/test/resources/invalid_change_element_contains_element.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/ckChangeLog-core/src/test/resources/invalid_empty_change_element.xml b/ckChangeLog-core/src/test/resources/invalid_empty_change_element.xml new file mode 100644 index 0000000..c2b1151 --- /dev/null +++ b/ckChangeLog-core/src/test/resources/invalid_empty_change_element.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/ckChangeLog-core/src/test/resources/invalid_empty_release_element.xml b/ckChangeLog-core/src/test/resources/invalid_empty_release_element.xml new file mode 100644 index 0000000..a54e33d --- /dev/null +++ b/ckChangeLog-core/src/test/resources/invalid_empty_release_element.xml @@ -0,0 +1,4 @@ + + + + diff --git a/ckChangeLog-core/src/test/resources/invalid_release_element_with_missing_version_attribute.xml b/ckChangeLog-core/src/test/resources/invalid_release_element_with_missing_version_attribute.xml new file mode 100644 index 0000000..bc62f79 --- /dev/null +++ b/ckChangeLog-core/src/test/resources/invalid_release_element_with_missing_version_attribute.xml @@ -0,0 +1,4 @@ + + + + diff --git a/ckChangeLog-core/src/test/resources/invalid_release_element_with_missing_versioncode_attribute.xml b/ckChangeLog-core/src/test/resources/invalid_release_element_with_missing_versioncode_attribute.xml new file mode 100644 index 0000000..bc62f79 --- /dev/null +++ b/ckChangeLog-core/src/test/resources/invalid_release_element_with_missing_versioncode_attribute.xml @@ -0,0 +1,4 @@ + + + + diff --git a/ckChangeLog-core/src/test/resources/invalid_wrong_change_element.xml b/ckChangeLog-core/src/test/resources/invalid_wrong_change_element.xml new file mode 100644 index 0000000..ffac1ce --- /dev/null +++ b/ckChangeLog-core/src/test/resources/invalid_wrong_change_element.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/ckChangeLog-core/src/test/resources/invalid_wrong_release_element.xml b/ckChangeLog-core/src/test/resources/invalid_wrong_release_element.xml new file mode 100644 index 0000000..01264b1 --- /dev/null +++ b/ckChangeLog-core/src/test/resources/invalid_wrong_release_element.xml @@ -0,0 +1,4 @@ + + + + diff --git a/ckChangeLog-core/src/test/resources/invalid_wrong_root_element.xml b/ckChangeLog-core/src/test/resources/invalid_wrong_root_element.xml new file mode 100644 index 0000000..96976c3 --- /dev/null +++ b/ckChangeLog-core/src/test/resources/invalid_wrong_root_element.xml @@ -0,0 +1,2 @@ + + diff --git a/ckChangeLog-core/src/test/resources/valid_changelog_with_version_dates.xml b/ckChangeLog-core/src/test/resources/valid_changelog_with_version_dates.xml new file mode 100644 index 0000000..0e78b48 --- /dev/null +++ b/ckChangeLog-core/src/test/resources/valid_changelog_with_version_dates.xml @@ -0,0 +1,16 @@ + + + + + Third release + + + + Second release + + + + First release + + + diff --git a/ckChangeLog-core/src/test/resources/valid_changelog_without_version_dates.xml b/ckChangeLog-core/src/test/resources/valid_changelog_without_version_dates.xml new file mode 100644 index 0000000..5cdaac6 --- /dev/null +++ b/ckChangeLog-core/src/test/resources/valid_changelog_without_version_dates.xml @@ -0,0 +1,17 @@ + + + + + Totally new and shiny version + + + + Fixed: A bug fix + Some other changes I can't quite remember + + + + First release + + + diff --git a/ckChangeLog-core/src/test/resources/valid_empty_changelog.xml b/ckChangeLog-core/src/test/resources/valid_empty_changelog.xml new file mode 100644 index 0000000..59d3cf4 --- /dev/null +++ b/ckChangeLog-core/src/test/resources/valid_empty_changelog.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/ckChangeLog-legacy-dialog/build.gradle b/ckChangeLog-legacy-dialog/build.gradle new file mode 100644 index 0000000..457c250 --- /dev/null +++ b/ckChangeLog-legacy-dialog/build.gradle @@ -0,0 +1,20 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion propCompileSdkVersion + buildToolsVersion propBuildToolsVersion + + defaultConfig { + versionName libraryVersion + minSdkVersion 14 + } +} + +dependencies { + implementation project(':ckChangeLog-core') +} + +project.ext.pom = rootProject.pom +project.ext.pom['artifactId'] = "ckchangelog-legacy-dialog" + +apply from: '../android-mvn-push.gradle' diff --git a/ckChangeLog-legacy-dialog/src/main/AndroidManifest.xml b/ckChangeLog-legacy-dialog/src/main/AndroidManifest.xml new file mode 100644 index 0000000..7af476b --- /dev/null +++ b/ckChangeLog-legacy-dialog/src/main/AndroidManifest.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/ckChangeLog-legacy-dialog/src/main/java/de/cketti/changelog/dialog/DialogChangeLog.java b/ckChangeLog-legacy-dialog/src/main/java/de/cketti/changelog/dialog/DialogChangeLog.java new file mode 100644 index 0000000..3b05557 --- /dev/null +++ b/ckChangeLog-legacy-dialog/src/main/java/de/cketti/changelog/dialog/DialogChangeLog.java @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2012-2018 cketti and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.cketti.changelog.dialog; + + +import java.util.List; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.webkit.WebView; + +import de.cketti.changelog.ChangeLog; +import de.cketti.changelog.ReleaseItem; + + +/** + * Display a dialog showing a full or partial (What's New) Change Log. + * + *

+ * You can display a Change Log after app updates by putting the following code in your Activity's {@code onCreate()} + * method: + *

+ *
{@code
+ * DialogChangeLog changeLog = DialogChangeLog.newInstance(this);
+ * if (changeLog.isFirstRun()) {
+ *     changeLog.getLogDialog().show();
+ * }
+ * }
+ */ +public final class DialogChangeLog { + /** + * Default CSS styles used to format the Change Log. + */ + public static final String DEFAULT_CSS = "" + + "h1 { margin-left: 0px; font-size: 1.2em; }" + "\n" + + "li { margin-left: 0px; }" + "\n" + + "ul { padding-left: 2em; }"; + + + private final Context context; + private final ChangeLog changeLog; + private final HtmlFormatter formatter; + + + /** + * Create an instance using the default CSS to format the Change Log. + */ + public static DialogChangeLog newInstance(Context context) { + return DialogChangeLog.newInstance(context, DEFAULT_CSS); + } + + /** + * Create an instance using the supplied CSS to format the Change Log. + */ + public static DialogChangeLog newInstance(Context context, String css) { + ChangeLog changeLog = ChangeLog.newInstance(context); + String versionFormat = context.getResources().getString(R.string.changelog_version_format); + HtmlFormatter formatter = new HtmlFormatter(versionFormat, css); + return new DialogChangeLog(context, changeLog, formatter); + } + + private DialogChangeLog(Context context, ChangeLog changeLog, HtmlFormatter formatter) { + this.context = context; + this.changeLog = changeLog; + this.formatter = formatter; + } + + /** + * Get the {@code ChangeLog} instance backing this {@code DialogChangeLog}. + */ + public ChangeLog getChangeLog() { + return changeLog; + } + + /** + * Get the "What's New" dialog. + * + * @return An AlertDialog displaying the changes since the previous installed version of your + * app (What's New). But when this is the first run of your app including + * {@code ChangeLog} then the full log dialog is show. + */ + public AlertDialog getLogDialog() { + return getDialog(changeLog.isFirstRunEver()); + } + + /** + * Get a dialog with the full Change Log. + * + * @return An AlertDialog with a full Change Log displayed. + */ + public AlertDialog getFullLogDialog() { + return getDialog(true); + } + + /** + * Check if this is the first execution of this app version. + * + * @return {@code true} if this version of your app is started the first time. + */ + public boolean isFirstRun() { + return changeLog.isFirstRun(); + } + + private AlertDialog getDialog(boolean full) { + WebView webView = new WebView(context); + webView.loadDataWithBaseURL(null, getChangeLogHtml(full), "text/html", "UTF-8", null); + + AlertDialog.Builder builder = new AlertDialog.Builder(context); + builder.setTitle(full ? R.string.changelog_full_title : R.string.changelog_title) + .setView(webView) + .setCancelable(false) + .setPositiveButton(R.string.changelog_ok_button, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + changeLog.writeCurrentVersion(); + } + }); + + if (!full) { + builder.setNegativeButton(R.string.changelog_show_full, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int id) { + getFullLogDialog().show(); + } + }); + } + + return builder.create(); + } + + private String getChangeLogHtml(boolean full) { + List changelog = full ? + changeLog.getChangeLog() : + changeLog.getRecentChanges(); + + return formatter.createHtmlChangeLog(changelog); + } +} diff --git a/ckChangeLog-legacy-dialog/src/main/java/de/cketti/changelog/dialog/HtmlFormatter.java b/ckChangeLog-legacy-dialog/src/main/java/de/cketti/changelog/dialog/HtmlFormatter.java new file mode 100644 index 0000000..7a5addc --- /dev/null +++ b/ckChangeLog-legacy-dialog/src/main/java/de/cketti/changelog/dialog/HtmlFormatter.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2012-2018 cketti and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.cketti.changelog.dialog; + + +import java.util.List; + +import de.cketti.changelog.ReleaseItem; + + +class HtmlFormatter { + private final String versionFormat; + private final String css; + + + public HtmlFormatter(String versionFormat, String css) { + this.versionFormat = versionFormat; + this.css = css; + } + + public String createHtmlChangeLog(List changelog) { + StringBuilder sb = new StringBuilder(); + + sb.append(""); + + for (ReleaseItem release : changelog) { + sb.append("

"); + sb.append(String.format(versionFormat, release.versionName)); + sb.append("

    "); + for (String change : release.changes) { + sb.append("
  • "); + sb.append(change); + sb.append("
  • "); + } + sb.append("
"); + } + + sb.append(""); + + return sb.toString(); + } +} diff --git a/ckChangeLog/src/main/res/values-cs/strings.xml b/ckChangeLog-legacy-dialog/src/main/res/values-cs/strings.xml similarity index 100% rename from ckChangeLog/src/main/res/values-cs/strings.xml rename to ckChangeLog-legacy-dialog/src/main/res/values-cs/strings.xml diff --git a/ckChangeLog/src/main/res/values-de/strings.xml b/ckChangeLog-legacy-dialog/src/main/res/values-de/strings.xml similarity index 100% rename from ckChangeLog/src/main/res/values-de/strings.xml rename to ckChangeLog-legacy-dialog/src/main/res/values-de/strings.xml diff --git a/ckChangeLog/src/main/res/values-el/strings.xml b/ckChangeLog-legacy-dialog/src/main/res/values-el/strings.xml similarity index 100% rename from ckChangeLog/src/main/res/values-el/strings.xml rename to ckChangeLog-legacy-dialog/src/main/res/values-el/strings.xml diff --git a/ckChangeLog/src/main/res/values-es/strings.xml b/ckChangeLog-legacy-dialog/src/main/res/values-es/strings.xml similarity index 100% rename from ckChangeLog/src/main/res/values-es/strings.xml rename to ckChangeLog-legacy-dialog/src/main/res/values-es/strings.xml diff --git a/ckChangeLog/src/main/res/values-pl/strings.xml b/ckChangeLog-legacy-dialog/src/main/res/values-pl/strings.xml similarity index 100% rename from ckChangeLog/src/main/res/values-pl/strings.xml rename to ckChangeLog-legacy-dialog/src/main/res/values-pl/strings.xml diff --git a/ckChangeLog/src/main/res/values-ru/strings.xml b/ckChangeLog-legacy-dialog/src/main/res/values-ru/strings.xml similarity index 100% rename from ckChangeLog/src/main/res/values-ru/strings.xml rename to ckChangeLog-legacy-dialog/src/main/res/values-ru/strings.xml diff --git a/ckChangeLog/src/main/res/values-sk/strings.xml b/ckChangeLog-legacy-dialog/src/main/res/values-sk/strings.xml similarity index 100% rename from ckChangeLog/src/main/res/values-sk/strings.xml rename to ckChangeLog-legacy-dialog/src/main/res/values-sk/strings.xml diff --git a/ckChangeLog/src/main/res/values-uk/strings.xml b/ckChangeLog-legacy-dialog/src/main/res/values-uk/strings.xml similarity index 100% rename from ckChangeLog/src/main/res/values-uk/strings.xml rename to ckChangeLog-legacy-dialog/src/main/res/values-uk/strings.xml diff --git a/ckChangeLog/src/main/res/values/strings.xml b/ckChangeLog-legacy-dialog/src/main/res/values/strings.xml similarity index 100% rename from ckChangeLog/src/main/res/values/strings.xml rename to ckChangeLog-legacy-dialog/src/main/res/values/strings.xml diff --git a/ckChangeLog/build.gradle b/ckChangeLog/build.gradle deleted file mode 100644 index 1a574b8..0000000 --- a/ckChangeLog/build.gradle +++ /dev/null @@ -1,36 +0,0 @@ -apply plugin: 'com.android.library' - -android { - compileSdkVersion propCompileSdkVersion - buildToolsVersion propBuildToolsVersion - - defaultConfig { - versionName "1.2.2" - - minSdkVersion 4 - } -} - -project.ext { - pom = [ - group: "de.cketti.library.changelog", - artifactId: "ckchangelog", - name: "ckChangeLog Library", - description: "An Android Library to display a Change Log", - url: "https://github.com/cketti/ckChangeLog", - - scmUrl: "https://github.com/cketti/ckChangeLog", - scmConnection: "scm:git@github.com:cketti/ckChangeLog.git", - scmDevConnection: "scm:git@github.com:cketti/ckChangeLog.git", - - licenseName: "The Apache Software License, Version 2.0", - licenseUrl: "http://www.apache.org/licenses/LICENSE-2.0.txt", - licenseDist: "repo", - - developerId: "cketti", - developerName: "cketti" - ] -} - -apply from: '../android-mvn-push.gradle' - diff --git a/ckChangeLog/src/main/AndroidManifest.xml b/ckChangeLog/src/main/AndroidManifest.xml deleted file mode 100644 index 03a7ba0..0000000 --- a/ckChangeLog/src/main/AndroidManifest.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/ckChangeLog/src/main/java/de/cketti/library/changelog/ChangeLog.java b/ckChangeLog/src/main/java/de/cketti/library/changelog/ChangeLog.java deleted file mode 100644 index b87b3f0..0000000 --- a/ckChangeLog/src/main/java/de/cketti/library/changelog/ChangeLog.java +++ /dev/null @@ -1,600 +0,0 @@ -/* - * Copyright (C) 2012-2015 cketti and contributors - * https://github.com/cketti/ckChangeLog/graphs/contributors - * - * Portions Copyright (C) 2012 Martin van Zuilekom (http://martin.cubeactive.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * - * Based on android-change-log: - * - * Copyright (C) 2011, Karsten Priegnitz - * - * Permission to use, copy, modify, and distribute this piece of software - * for any purpose with or without fee is hereby granted, provided that - * the above copyright notice and this permission notice appear in the - * source code of all copies. - * - * It would be appreciated if you mention the author in your change log, - * contributors list or the like. - * - * http://code.google.com/p/android-change-log/ - */ -package de.cketti.library.changelog; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; - -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; - -import android.app.AlertDialog; -import android.content.Context; -import android.content.DialogInterface; -import android.content.SharedPreferences; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager.NameNotFoundException; -import android.content.res.XmlResourceParser; -import android.preference.PreferenceManager; -import android.util.Log; -import android.util.SparseArray; -import android.webkit.WebView; - - -/** - * Display a dialog showing a full or partial (What's New) change log. - */ -@SuppressWarnings("UnusedDeclaration") -public class ChangeLog { - /** - * Tag that is used when sending error/debug messages to the log. - */ - protected static final String LOG_TAG = "ckChangeLog"; - - /** - * This is the key used when storing the version code in SharedPreferences. - */ - protected static final String VERSION_KEY = "ckChangeLog_last_version_code"; - - /** - * Constant that used when no version code is available. - */ - protected static final int NO_VERSION = -1; - - /** - * Default CSS styles used to format the change log. - */ - public static final String DEFAULT_CSS = - "h1 { margin-left: 0px; font-size: 1.2em; }" + "\n" + - "li { margin-left: 0px; }" + "\n" + - "ul { padding-left: 2em; }"; - - - /** - * Context that is used to access the resources and to create the ChangeLog dialogs. - */ - protected final Context mContext; - - /** - * Contains the CSS rules used to format the change log. - */ - protected final String mCss; - - /** - * Last version code read from {@code SharedPreferences} or {@link #NO_VERSION}. - */ - private int mLastVersionCode; - - /** - * Version code of the current installation. - */ - private int mCurrentVersionCode; - - /** - * Version name of the current installation. - */ - private String mCurrentVersionName; - - - /** - * Contains constants for the root element of {@code changelog.xml}. - */ - protected interface ChangeLogTag { - static final String NAME = "changelog"; - } - - /** - * Contains constants for the release element of {@code changelog.xml}. - */ - protected interface ReleaseTag { - static final String NAME = "release"; - static final String ATTRIBUTE_VERSION = "version"; - static final String ATTRIBUTE_VERSION_CODE = "versioncode"; - } - - /** - * Contains constants for the change element of {@code changelog.xml}. - */ - protected interface ChangeTag { - static final String NAME = "change"; - } - - /** - * Create a {@code ChangeLog} instance using the default {@link SharedPreferences} file. - * - * @param context - * Context that is used to access the resources and to create the ChangeLog dialogs. - */ - public ChangeLog(Context context) { - this(context, PreferenceManager.getDefaultSharedPreferences(context), DEFAULT_CSS); - } - - /** - * Create a {@code ChangeLog} instance using the default {@link SharedPreferences} file. - * - * @param context - * Context that is used to access the resources and to create the ChangeLog dialogs. - * @param css - * CSS styles that will be used to format the change log. - */ - public ChangeLog(Context context, String css) { - this(context, PreferenceManager.getDefaultSharedPreferences(context), css); - } - - /** - * Create a {@code ChangeLog} instance using the supplied {@code SharedPreferences} instance. - * - * @param context - * Context that is used to access the resources and to create the ChangeLog dialogs. - * @param preferences - * {@code SharedPreferences} instance that is used to persist the last version code. - * @param css - * CSS styles used to format the change log (excluding {@code }). - * - */ - public ChangeLog(Context context, SharedPreferences preferences, String css) { - mContext = context; - mCss = css; - - // Get last version code - mLastVersionCode = preferences.getInt(VERSION_KEY, NO_VERSION); - - // Get current version code and version name - try { - PackageInfo packageInfo = context.getPackageManager().getPackageInfo( - context.getPackageName(), 0); - - mCurrentVersionCode = packageInfo.versionCode; - mCurrentVersionName = packageInfo.versionName; - } catch (NameNotFoundException e) { - mCurrentVersionCode = NO_VERSION; - Log.e(LOG_TAG, "Could not get version information from manifest!", e); - } - } - - /** - * Get version code of last installation. - * - * @return The version code of the last installation of this app (as described in the former - * manifest). This will be the same as returned by {@link #getCurrentVersionCode()} the - * second time this version of the app is launched (more precisely: the second time - * {@code ChangeLog} is instantiated). - * - * @see android:versionCode - */ - public int getLastVersionCode() { - return mLastVersionCode; - } - - /** - * Get version code of current installation. - * - * @return The version code of this app as described in the manifest. - * - * @see android:versionCode - */ - public int getCurrentVersionCode() { - return mCurrentVersionCode; - } - - /** - * Get version name of current installation. - * - * @return The version name of this app as described in the manifest. - * - * @see android:versionName - */ - public String getCurrentVersionName() { - return mCurrentVersionName; - } - - /** - * Check if this is the first execution of this app version. - * - * @return {@code true} if this version of your app is started the first time. - */ - public boolean isFirstRun() { - return mLastVersionCode < mCurrentVersionCode; - } - - /** - * Check if this is a new installation. - * - * @return {@code true} if your app including {@code ChangeLog} is started the first time ever. - * Also {@code true} if your app was uninstalled and installed again. - */ - public boolean isFirstRunEver() { - return mLastVersionCode == NO_VERSION; - } - - /** - * Skip the "What's new" dialog for this app version. - * - *

- * Future calls to {@link #isFirstRun()} and {@link #isFirstRunEver()} will return {@code false} - * for the current app version. - *

- */ - public void skipLogDialog() { - updateVersionInPreferences(); - } - - /** - * Get the "What's New" dialog. - * - * @return An AlertDialog displaying the changes since the previous installed version of your - * app (What's New). But when this is the first run of your app including - * {@code ChangeLog} then the full log dialog is show. - */ - public AlertDialog getLogDialog() { - return getDialog(isFirstRunEver()); - } - - /** - * Get a dialog with the full change log. - * - * @return An AlertDialog with a full change log displayed. - */ - public AlertDialog getFullLogDialog() { - return getDialog(true); - } - - /** - * Create a dialog containing (parts of the) change log. - * - * @param full - * If this is {@code true} the full change log is displayed. Otherwise only changes for - * versions newer than the last version are displayed. - * - * @return A dialog containing the (partial) change log. - */ - protected AlertDialog getDialog(boolean full) { - WebView wv = new WebView(mContext); - //wv.setBackgroundColor(0); // transparent - wv.loadDataWithBaseURL(null, getLog(full), "text/html", "UTF-8", null); - - AlertDialog.Builder builder = new AlertDialog.Builder(mContext); - builder.setTitle( - mContext.getResources().getString( - full ? R.string.changelog_full_title : R.string.changelog_title)) - .setView(wv) - .setCancelable(false) - // OK button - .setPositiveButton( - mContext.getResources().getString(R.string.changelog_ok_button), - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - // The user clicked "OK" so save the current version code as - // "last version code". - updateVersionInPreferences(); - } - }); - - if (!full) { - // Show "More…" button if we're only displaying a partial change log. - builder.setNegativeButton(R.string.changelog_show_full, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int id) { - getFullLogDialog().show(); - } - }); - } - - return builder.create(); - } - - /** - * Write current version code to the preferences. - */ - protected void updateVersionInPreferences() { - SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext); - SharedPreferences.Editor editor = sp.edit(); - editor.putInt(VERSION_KEY, mCurrentVersionCode); - - // TODO: Update preferences from a background thread - editor.commit(); - } - - /** - * Get changes since last version as HTML string. - * - * @return HTML string containing the changes since the previous installed version of your app - * (What's New). - */ - public String getLog() { - return getLog(false); - } - - /** - * Get full change log as HTML string. - * - * @return HTML string containing the full change log. - */ - public String getFullLog() { - return getLog(true); - } - - /** - * Get (partial) change log as HTML string. - * - * @param full - * If this is {@code true} the full change log is returned. Otherwise only changes for - * versions newer than the last version are returned. - * - * @return The (partial) change log. - */ - protected String getLog(boolean full) { - StringBuilder sb = new StringBuilder(); - - sb.append(""); - - String versionFormat = mContext.getResources().getString(R.string.changelog_version_format); - - List changelog = getChangeLog(full); - - for (ReleaseItem release : changelog) { - sb.append("

"); - sb.append(String.format(versionFormat, release.versionName)); - sb.append("

    "); - for (String change : release.changes) { - sb.append("
  • "); - sb.append(change); - sb.append("
  • "); - } - sb.append("
"); - } - - sb.append(""); - - return sb.toString(); - } - - /** - * Returns the merged change log. - * - * @param full - * If this is {@code true} the full change log is returned. Otherwise only changes for - * versions newer than the last version are returned. - * - * @return A sorted {@code List} containing {@link ReleaseItem}s representing the (partial) - * change log. - * - * @see #getChangeLogComparator() - */ - public List getChangeLog(boolean full) { - SparseArray masterChangelog = getMasterChangeLog(full); - SparseArray changelog = getLocalizedChangeLog(full); - - List mergedChangeLog = - new ArrayList(masterChangelog.size()); - - for (int i = 0, len = masterChangelog.size(); i < len; i++) { - int key = masterChangelog.keyAt(i); - - // Use release information from localized change log and fall back to the master file - // if necessary. - ReleaseItem release = changelog.get(key, masterChangelog.get(key)); - - mergedChangeLog.add(release); - } - - Collections.sort(mergedChangeLog, getChangeLogComparator()); - - return mergedChangeLog; - } - - /** - * Read master change log from {@code xml/changelog_master.xml} - * - * @see #readChangeLogFromResource(int, boolean) - */ - protected SparseArray getMasterChangeLog(boolean full) { - return readChangeLogFromResource(R.xml.changelog_master, full); - } - - /** - * Read localized change log from {@code xml[-lang]/changelog.xml} - * - * @see #readChangeLogFromResource(int, boolean) - */ - protected SparseArray getLocalizedChangeLog(boolean full) { - return readChangeLogFromResource(R.xml.changelog, full); - } - - /** - * Read change log from XML resource file. - * - * @param resId - * Resource ID of the XML file to read the change log from. - * @param full - * If this is {@code true} the full change log is returned. Otherwise only changes for - * versions newer than the last version are returned. - * - * @return A {@code SparseArray} containing {@link ReleaseItem}s representing the (partial) - * change log. - */ - protected final SparseArray readChangeLogFromResource(int resId, boolean full) { - XmlResourceParser xml = mContext.getResources().getXml(resId); - try { - return readChangeLog(xml, full); - } finally { - xml.close(); - } - } - - /** - * Read the change log from an XML file. - * - * @param xml - * The {@code XmlPullParser} instance used to read the change log. - * @param full - * If {@code true} the full change log is read. Otherwise only the changes since the - * last (saved) version are read. - * - * @return A {@code SparseArray} mapping the version codes to release information. - */ - protected SparseArray readChangeLog(XmlPullParser xml, boolean full) { - SparseArray result = new SparseArray(); - - try { - int eventType = xml.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG && xml.getName().equals(ReleaseTag.NAME)) { - if (parseReleaseTag(xml, full, result)) { - // Stop reading more elements if this entry is not newer than the last - // version. - break; - } - } - eventType = xml.next(); - } - } catch (XmlPullParserException e) { - Log.e(LOG_TAG, e.getMessage(), e); - } catch (IOException e) { - Log.e(LOG_TAG, e.getMessage(), e); - } - - return result; - } - - /** - * Parse the {@code release} tag of a change log XML file. - * - * @param xml - * The {@code XmlPullParser} instance used to read the change log. - * @param full - * If {@code true} the contents of the {@code release} tag are always added to - * {@code changelog}. Otherwise only if the item's {@code versioncode} attribute is - * higher than the last version code. - * @param changelog - * The {@code SparseArray} to add a new {@link ReleaseItem} instance to. - * - * @return {@code true} if the {@code release} element is describing changes of a version older - * or equal to the last version. In that case {@code changelog} won't be modified and - * {@link #readChangeLog(XmlPullParser, boolean)} will stop reading more elements from - * the change log file. - * - * @throws XmlPullParserException - * @throws IOException - */ - private boolean parseReleaseTag(XmlPullParser xml, boolean full, - SparseArray changelog) throws XmlPullParserException, IOException { - - String version = xml.getAttributeValue(null, ReleaseTag.ATTRIBUTE_VERSION); - - int versionCode; - try { - String versionCodeStr = xml.getAttributeValue(null, ReleaseTag.ATTRIBUTE_VERSION_CODE); - versionCode = Integer.parseInt(versionCodeStr); - } catch (NumberFormatException e) { - versionCode = NO_VERSION; - } - - if (!full && versionCode <= mLastVersionCode) { - return true; - } - - int eventType = xml.getEventType(); - List changes = new ArrayList(); - while (eventType != XmlPullParser.END_TAG || xml.getName().equals(ChangeTag.NAME)) { - if (eventType == XmlPullParser.START_TAG && xml.getName().equals(ChangeTag.NAME)) { - eventType = xml.next(); - - changes.add(xml.getText()); - } - eventType = xml.next(); - } - - ReleaseItem release = new ReleaseItem(versionCode, version, changes); - changelog.put(versionCode, release); - - return false; - } - - /** - * Returns a {@link Comparator} that specifies the sort order of the {@link ReleaseItem}s. - * - *

- * The default implementation returns the items in reverse order (latest version first). - *

- */ - protected Comparator getChangeLogComparator() { - return new Comparator() { - @Override - public int compare(ReleaseItem lhs, ReleaseItem rhs) { - if (lhs.versionCode < rhs.versionCode) { - return 1; - } else if (lhs.versionCode > rhs.versionCode) { - return -1; - } else { - return 0; - } - } - }; - } - - /** - * Container used to store information about a release/version. - */ - public static class ReleaseItem { - /** - * Version code of the release. - */ - public final int versionCode; - - /** - * Version name of the release. - */ - public final String versionName; - - /** - * List of changes introduced with that release. - */ - public final List changes; - - ReleaseItem(int versionCode, String versionName, List changes) { - this.versionCode = versionCode; - this.versionName = versionName; - this.changes = changes; - } - } -} diff --git a/ckChangeLog/src/main/res/xml/changelog_master.xml b/ckChangeLog/src/main/res/xml/changelog_master.xml deleted file mode 100644 index d83a85b..0000000 --- a/ckChangeLog/src/main/res/xml/changelog_master.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 667288a..3baa851 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 9f62e0c..b3bd290 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Fri Jan 09 08:20:23 CET 2015 +#Thu Mar 08 21:09:01 CET 2018 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-bin.zip diff --git a/gradlew b/gradlew index 91a7e26..27309d9 100755 --- a/gradlew +++ b/gradlew @@ -6,12 +6,30 @@ ## ############################################################################## -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" @@ -30,6 +48,7 @@ die ( ) { cygwin=false msys=false darwin=false +nonstop=false case "`uname`" in CYGWIN* ) cygwin=true @@ -40,31 +59,11 @@ case "`uname`" in MINGW* ) msys=true ;; + NONSTOP* ) + nonstop=true + ;; esac -# For Cygwin, ensure paths are in UNIX format before anything is touched. -if $cygwin ; then - [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` -fi - -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >&- -APP_HOME="`pwd -P`" -cd "$SAVED" >&- - CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. @@ -90,7 +89,7 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then @@ -114,6 +113,7 @@ fi if $cygwin ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` diff --git a/gradlew.bat b/gradlew.bat index 8a0b282..832fdb6 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -8,14 +8,14 @@ @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome @@ -46,7 +46,7 @@ echo location of your Java installation. goto fail :init -@rem Get command-line arguments, handling Windowz variants +@rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args if "%@eval[2+2]" == "4" goto 4NT_args diff --git a/sample/build.gradle b/sample-legacy-dialog/build.gradle similarity index 58% rename from sample/build.gradle rename to sample-legacy-dialog/build.gradle index 67e9cc1..e7fa5e2 100644 --- a/sample/build.gradle +++ b/sample-legacy-dialog/build.gradle @@ -8,14 +8,14 @@ android { versionCode 1 versionName "1.0" - minSdkVersion 7 - targetSdkVersion 21 + minSdkVersion 14 + targetSdkVersion 27 } } dependencies { - compile project(':ckChangeLog') - compile 'com.android.support:support-v4:21.0.3' + implementation project(':ckChangeLog-legacy-dialog') + implementation 'com.android.support:support-v4:27.1.1' } diff --git a/sample/src/main/AndroidManifest.xml b/sample-legacy-dialog/src/main/AndroidManifest.xml similarity index 100% rename from sample/src/main/AndroidManifest.xml rename to sample-legacy-dialog/src/main/AndroidManifest.xml diff --git a/sample/src/main/java/de/cketti/sample/changelog/MainActivity.java b/sample-legacy-dialog/src/main/java/de/cketti/sample/changelog/MainActivity.java similarity index 60% rename from sample/src/main/java/de/cketti/sample/changelog/MainActivity.java rename to sample-legacy-dialog/src/main/java/de/cketti/sample/changelog/MainActivity.java index 01e8652..f13b743 100644 --- a/sample/src/main/java/de/cketti/sample/changelog/MainActivity.java +++ b/sample-legacy-dialog/src/main/java/de/cketti/sample/changelog/MainActivity.java @@ -1,6 +1,7 @@ package de.cketti.sample.changelog; -import de.cketti.library.changelog.ChangeLog; +import de.cketti.changelog.dialog.DialogChangeLog; + import android.content.Context; import android.os.Bundle; import android.support.v4.app.FragmentActivity; @@ -10,13 +11,16 @@ public class MainActivity extends FragmentActivity { + private static final String DARK_THEME_CSS = "body { color: #ffffff; background-color: #282828; }" + "\n" + + DialogChangeLog.DEFAULT_CSS; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); - ChangeLog cl = new ChangeLog(this); + DialogChangeLog cl = DialogChangeLog.newInstance(this); if (cl.isFirstRun()) { cl.getLogDialog().show(); } @@ -32,11 +36,11 @@ public boolean onCreateOptionsMenu(Menu menu) { public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.menu_whats_new: { - new DarkThemeChangeLog(this).getLogDialog().show(); + createDarkThemeChangeLog(this).getLogDialog().show(); break; } case R.id.menu_full_changelog: { - new ChangeLog(this).getFullLogDialog().show(); + DialogChangeLog.newInstance(this).getFullLogDialog().show(); break; } } @@ -44,15 +48,8 @@ public boolean onOptionsItemSelected(MenuItem item) { return true; } - /** - * Example that shows how to create a themed dialog. - */ - public static class DarkThemeChangeLog extends ChangeLog { - public static final String DARK_THEME_CSS = - "body { color: #ffffff; background-color: #282828; }" + "\n" + DEFAULT_CSS; - - public DarkThemeChangeLog(Context context) { - super(new ContextThemeWrapper(context, R.style.DarkTheme), DARK_THEME_CSS); - } + private static DialogChangeLog createDarkThemeChangeLog(Context context) { + ContextThemeWrapper themedContext = new ContextThemeWrapper(context, R.style.DarkTheme); + return DialogChangeLog.newInstance(themedContext, DARK_THEME_CSS); } } diff --git a/sample/src/main/res/drawable-hdpi/ic_launcher.png b/sample-legacy-dialog/src/main/res/drawable-hdpi/ic_launcher.png similarity index 100% rename from sample/src/main/res/drawable-hdpi/ic_launcher.png rename to sample-legacy-dialog/src/main/res/drawable-hdpi/ic_launcher.png diff --git a/sample/src/main/res/drawable-mdpi/ic_launcher.png b/sample-legacy-dialog/src/main/res/drawable-mdpi/ic_launcher.png similarity index 100% rename from sample/src/main/res/drawable-mdpi/ic_launcher.png rename to sample-legacy-dialog/src/main/res/drawable-mdpi/ic_launcher.png diff --git a/sample/src/main/res/drawable-xhdpi/ic_launcher.png b/sample-legacy-dialog/src/main/res/drawable-xhdpi/ic_launcher.png similarity index 100% rename from sample/src/main/res/drawable-xhdpi/ic_launcher.png rename to sample-legacy-dialog/src/main/res/drawable-xhdpi/ic_launcher.png diff --git a/sample/src/main/res/drawable-xxhdpi/ic_launcher.png b/sample-legacy-dialog/src/main/res/drawable-xxhdpi/ic_launcher.png similarity index 100% rename from sample/src/main/res/drawable-xxhdpi/ic_launcher.png rename to sample-legacy-dialog/src/main/res/drawable-xxhdpi/ic_launcher.png diff --git a/sample/src/main/res/drawable-xxxhdpi/ic_launcher.png b/sample-legacy-dialog/src/main/res/drawable-xxxhdpi/ic_launcher.png similarity index 100% rename from sample/src/main/res/drawable-xxxhdpi/ic_launcher.png rename to sample-legacy-dialog/src/main/res/drawable-xxxhdpi/ic_launcher.png diff --git a/sample/src/main/res/layout/activity_main.xml b/sample-legacy-dialog/src/main/res/layout/activity_main.xml similarity index 100% rename from sample/src/main/res/layout/activity_main.xml rename to sample-legacy-dialog/src/main/res/layout/activity_main.xml diff --git a/sample/src/main/res/menu/activity_main.xml b/sample-legacy-dialog/src/main/res/menu/activity_main.xml similarity index 100% rename from sample/src/main/res/menu/activity_main.xml rename to sample-legacy-dialog/src/main/res/menu/activity_main.xml diff --git a/sample/src/main/res/xml-de/changelog.xml b/sample-legacy-dialog/src/main/res/raw-de/changelog.xml similarity index 100% rename from sample/src/main/res/xml-de/changelog.xml rename to sample-legacy-dialog/src/main/res/raw-de/changelog.xml diff --git a/sample/src/main/res/xml/changelog_master.xml b/sample-legacy-dialog/src/main/res/raw/changelog_master.xml similarity index 100% rename from sample/src/main/res/xml/changelog_master.xml rename to sample-legacy-dialog/src/main/res/raw/changelog_master.xml diff --git a/sample/src/main/res/values-de/strings.xml b/sample-legacy-dialog/src/main/res/values-de/strings.xml similarity index 100% rename from sample/src/main/res/values-de/strings.xml rename to sample-legacy-dialog/src/main/res/values-de/strings.xml diff --git a/sample/src/main/res/values-es/strings.xml b/sample-legacy-dialog/src/main/res/values-es/strings.xml similarity index 100% rename from sample/src/main/res/values-es/strings.xml rename to sample-legacy-dialog/src/main/res/values-es/strings.xml diff --git a/sample/src/main/res/values-v11/styles.xml b/sample-legacy-dialog/src/main/res/values-v11/styles.xml similarity index 100% rename from sample/src/main/res/values-v11/styles.xml rename to sample-legacy-dialog/src/main/res/values-v11/styles.xml diff --git a/sample/src/main/res/values-v14/styles.xml b/sample-legacy-dialog/src/main/res/values-v14/styles.xml similarity index 100% rename from sample/src/main/res/values-v14/styles.xml rename to sample-legacy-dialog/src/main/res/values-v14/styles.xml diff --git a/sample/src/main/res/values/strings.xml b/sample-legacy-dialog/src/main/res/values/strings.xml similarity index 100% rename from sample/src/main/res/values/strings.xml rename to sample-legacy-dialog/src/main/res/values/strings.xml diff --git a/sample/src/main/res/values/styles.xml b/sample-legacy-dialog/src/main/res/values/styles.xml similarity index 100% rename from sample/src/main/res/values/styles.xml rename to sample-legacy-dialog/src/main/res/values/styles.xml diff --git a/settings.gradle b/settings.gradle index cc5e2d8..e38665e 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,2 +1,3 @@ -include ':ckChangeLog' -include ':sample' +include ':ckChangeLog-core' +include ':ckChangeLog-legacy-dialog' +include ':sample-legacy-dialog'