aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAllan Wang <me@allanwang.ca>2017-06-10 16:11:24 -0700
committerAllan Wang <me@allanwang.ca>2017-06-10 16:11:24 -0700
commit75dd060f2c265a069a0794ed659d780909df92ec (patch)
tree0614dce65845e9dc89358f12795e659dc1c2971b
parent58c69c343396ae18eb3c7e241b2c0618bd830c44 (diff)
downloadkau-75dd060f2c265a069a0794ed659d780909df92ec.tar.gz
kau-75dd060f2c265a069a0794ed659d780909df92ec.tar.bz2
kau-75dd060f2c265a069a0794ed659d780909df92ec.zip
init
-rw-r--r--.gitignore9
-rw-r--r--.idea/compiler.xml22
-rw-r--r--.idea/copyright/profiles_settings.xml3
-rw-r--r--.idea/encodings.xml6
-rw-r--r--.idea/gradle.xml19
-rw-r--r--.idea/markdown-navigator/profiles_settings.xml3
-rw-r--r--.idea/misc.xml146
-rw-r--r--.idea/modules.xml10
-rw-r--r--.idea/runConfigurations.xml12
-rw-r--r--build.gradle27
-rw-r--r--gradle.properties33
-rw-r--r--gradle/wrapper/gradle-wrapper.jarbin0 -> 53636 bytes
-rw-r--r--gradle/wrapper/gradle-wrapper.properties6
-rw-r--r--gradlew160
-rw-r--r--gradlew.bat90
-rw-r--r--library/.gitignore1
-rw-r--r--library/build.gradle64
-rw-r--r--library/progress-proguard.txt1
-rw-r--r--library/src/androidTest/java/ca/allanwang/kprefs/library/ExampleInstrumentedTest.java26
-rw-r--r--library/src/main/AndroidManifest.xml1
-rw-r--r--library/src/main/kotlin/ca/allanwang/kau/changelog/Changelog.kt73
-rw-r--r--library/src/main/kotlin/ca/allanwang/kau/dialogs/color/ColorPalette.kt349
-rw-r--r--library/src/main/kotlin/ca/allanwang/kau/dialogs/color/ColorPickerDialog.kt352
-rw-r--r--library/src/main/kotlin/ca/allanwang/kau/dialogs/color/ColorPickerPreference.kt72
-rw-r--r--library/src/main/kotlin/ca/allanwang/kau/kpref/KPref.kt32
-rw-r--r--library/src/main/kotlin/ca/allanwang/kau/kpref/KPrefBinder.kt46
-rw-r--r--library/src/main/kotlin/ca/allanwang/kau/kpref/KPrefDelegate.kt83
-rw-r--r--library/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefCheckbox.kt34
-rw-r--r--library/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefColorPicker.kt35
-rw-r--r--library/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefHeader.kt22
-rw-r--r--library/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefItemBase.kt35
-rw-r--r--library/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefItemCore.kt83
-rw-r--r--library/src/main/kotlin/ca/allanwang/kau/logging/SL.kt6
-rw-r--r--library/src/main/kotlin/ca/allanwang/kau/logging/TimberLogger.kt15
-rw-r--r--library/src/main/kotlin/ca/allanwang/kau/utils/AnimUtils.kt125
-rw-r--r--library/src/main/kotlin/ca/allanwang/kau/utils/ColorUtils.kt183
-rw-r--r--library/src/main/kotlin/ca/allanwang/kau/utils/Const.kt6
-rw-r--r--library/src/main/kotlin/ca/allanwang/kau/utils/ContextUtils.kt93
-rw-r--r--library/src/main/kotlin/ca/allanwang/kau/utils/FragmentUtils.kt24
-rw-r--r--library/src/main/kotlin/ca/allanwang/kau/utils/IIconUtils.kt19
-rw-r--r--library/src/main/kotlin/ca/allanwang/kau/utils/Kotterknife.kt137
-rw-r--r--library/src/main/kotlin/ca/allanwang/kau/utils/LazyResettable.kt51
-rw-r--r--library/src/main/kotlin/ca/allanwang/kau/utils/StringHolder.kt22
-rw-r--r--library/src/main/kotlin/ca/allanwang/kau/utils/Utils.kt11
-rw-r--r--library/src/main/kotlin/ca/allanwang/kau/utils/ViewUtils.kt73
-rw-r--r--library/src/main/kotlin/ca/allanwang/kau/views/RippleCanvas.kt79
-rw-r--r--library/src/main/res/drawable/kau_transparent.xml5
-rw-r--r--library/src/main/res/layout/kau_changelog_content.xml27
-rw-r--r--library/src/main/res/layout/kau_changelog_title.xml14
-rw-r--r--library/src/main/res/layout/kau_preference.xml68
-rw-r--r--library/src/main/res/layout/kau_preference_checkbox.xml7
-rw-r--r--library/src/main/res/layout/kau_preference_header.xml10
-rw-r--r--library/src/main/res/values/dimens.xml15
-rw-r--r--library/src/main/res/values/ids.xml8
-rw-r--r--library/src/main/res/values/strings.xml15
-rw-r--r--library/src/main/res/xml/kau_changelog.xml14
-rw-r--r--library/src/test/java/ca/allanwang/kprefs/library/ExampleUnitTest.java17
-rw-r--r--sample/.gitignore1
-rw-r--r--sample/build.gradle38
-rw-r--r--sample/proguard-rules.pro25
-rw-r--r--sample/src/androidTest/java/ca/allanwang/kprefs/ExampleInstrumentedTest.java26
-rw-r--r--sample/src/main/AndroidManifest.xml23
-rw-r--r--sample/src/main/kotlin/ca/allanwang/kau/sample/KPrefSample.kt16
-rw-r--r--sample/src/main/kotlin/ca/allanwang/kau/sample/MainActivity.kt53
-rw-r--r--sample/src/main/kotlin/ca/allanwang/kau/sample/SampleApp.kt15
-rw-r--r--sample/src/main/res/layout/activity_main.xml9
-rw-r--r--sample/src/main/res/menu/menu_main.xml31
-rw-r--r--sample/src/main/res/mipmap-hdpi/ic_launcher.pngbin0 -> 3418 bytes
-rw-r--r--sample/src/main/res/mipmap-hdpi/ic_launcher_round.pngbin0 -> 4208 bytes
-rw-r--r--sample/src/main/res/mipmap-mdpi/ic_launcher.pngbin0 -> 2206 bytes
-rw-r--r--sample/src/main/res/mipmap-mdpi/ic_launcher_round.pngbin0 -> 2555 bytes
-rw-r--r--sample/src/main/res/mipmap-xhdpi/ic_launcher.pngbin0 -> 4842 bytes
-rw-r--r--sample/src/main/res/mipmap-xhdpi/ic_launcher_round.pngbin0 -> 6114 bytes
-rw-r--r--sample/src/main/res/mipmap-xxhdpi/ic_launcher.pngbin0 -> 7718 bytes
-rw-r--r--sample/src/main/res/mipmap-xxhdpi/ic_launcher_round.pngbin0 -> 10056 bytes
-rw-r--r--sample/src/main/res/mipmap-xxxhdpi/ic_launcher.pngbin0 -> 10486 bytes
-rw-r--r--sample/src/main/res/mipmap-xxxhdpi/ic_launcher_round.pngbin0 -> 14696 bytes
-rw-r--r--sample/src/main/res/values/colors.xml6
-rw-r--r--sample/src/main/res/values/strings.xml10
-rw-r--r--sample/src/main/res/values/styles.xml11
-rw-r--r--sample/src/main/res/xml/changelog.xml20
-rw-r--r--sample/src/test/java/ca/allanwang/kprefs/ExampleUnitTest.java17
-rw-r--r--settings.gradle1
83 files changed, 3201 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..39fb081
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,9 @@
+*.iml
+.gradle
+/local.properties
+/.idea/workspace.xml
+/.idea/libraries
+.DS_Store
+/build
+/captures
+.externalNativeBuild
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
new file mode 100644
index 0000000..96cc43e
--- /dev/null
+++ b/.idea/compiler.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="CompilerConfiguration">
+ <resourceExtensions />
+ <wildcardResourcePatterns>
+ <entry name="!?*.java" />
+ <entry name="!?*.form" />
+ <entry name="!?*.class" />
+ <entry name="!?*.groovy" />
+ <entry name="!?*.scala" />
+ <entry name="!?*.flex" />
+ <entry name="!?*.kt" />
+ <entry name="!?*.clj" />
+ <entry name="!?*.aj" />
+ </wildcardResourcePatterns>
+ <annotationProcessing>
+ <profile default="true" name="Default" enabled="false">
+ <processorPath useClasspath="true" />
+ </profile>
+ </annotationProcessing>
+ </component>
+</project> \ No newline at end of file
diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml
new file mode 100644
index 0000000..e7bedf3
--- /dev/null
+++ b/.idea/copyright/profiles_settings.xml
@@ -0,0 +1,3 @@
+<component name="CopyrightManager">
+ <settings default="" />
+</component> \ No newline at end of file
diff --git a/.idea/encodings.xml b/.idea/encodings.xml
new file mode 100644
index 0000000..97626ba
--- /dev/null
+++ b/.idea/encodings.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="Encoding">
+ <file url="PROJECT" charset="UTF-8" />
+ </component>
+</project> \ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
new file mode 100644
index 0000000..903e3d4
--- /dev/null
+++ b/.idea/gradle.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="GradleSettings">
+ <option name="linkedExternalProjectsSettings">
+ <GradleProjectSettings>
+ <option name="distributionType" value="DEFAULT_WRAPPED" />
+ <option name="externalProjectPath" value="$PROJECT_DIR$" />
+ <option name="modules">
+ <set>
+ <option value="$PROJECT_DIR$" />
+ <option value="$PROJECT_DIR$/library" />
+ <option value="$PROJECT_DIR$/sample" />
+ </set>
+ </option>
+ <option name="resolveModulePerSourceSet" value="false" />
+ </GradleProjectSettings>
+ </option>
+ </component>
+</project> \ No newline at end of file
diff --git a/.idea/markdown-navigator/profiles_settings.xml b/.idea/markdown-navigator/profiles_settings.xml
new file mode 100644
index 0000000..57927c5
--- /dev/null
+++ b/.idea/markdown-navigator/profiles_settings.xml
@@ -0,0 +1,3 @@
+<component name="MarkdownNavigator.ProfileManager">
+ <settings default="" pdf-export="" />
+</component> \ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..8449ce7
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,146 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="EntryPointsManager">
+ <entry_points version="2.0" />
+ </component>
+ <component name="MarkdownProjectSettings">
+ <PreviewSettings splitEditorLayout="SPLIT" splitEditorPreview="PREVIEW" useGrayscaleRendering="false" zoomFactor="1.25" maxImageWidth="0" showGitHubPageIfSynced="false" allowBrowsingInPreview="false" synchronizePreviewPosition="true" highlightPreviewType="LINE" highlightFadeOut="5" highlightOnTyping="true" synchronizeSourcePosition="true" verticallyAlignSourceAndPreviewSyncPosition="true" showSearchHighlightsInPreview="true" showSelectionInPreview="true">
+ <PanelProvider>
+ <provider providerId="com.vladsch.idea.multimarkdown.editor.swing.html.panel" providerName="Default - Swing" />
+ </PanelProvider>
+ </PreviewSettings>
+ <ParserSettings gitHubSyntaxChange="false">
+ <PegdownExtensions>
+ <option name="ABBREVIATIONS" value="false" />
+ <option name="ANCHORLINKS" value="true" />
+ <option name="ASIDE" value="false" />
+ <option name="ATXHEADERSPACE" value="true" />
+ <option name="AUTOLINKS" value="true" />
+ <option name="DEFINITIONS" value="false" />
+ <option name="DEFINITION_BREAK_DOUBLE_BLANK_LINE" value="false" />
+ <option name="FENCED_CODE_BLOCKS" value="true" />
+ <option name="FOOTNOTES" value="false" />
+ <option name="HARDWRAPS" value="false" />
+ <option name="INSERTED" value="false" />
+ <option name="QUOTES" value="false" />
+ <option name="RELAXEDHRULES" value="true" />
+ <option name="SMARTS" value="false" />
+ <option name="STRIKETHROUGH" value="true" />
+ <option name="SUBSCRIPT" value="false" />
+ <option name="SUPERSCRIPT" value="false" />
+ <option name="SUPPRESS_HTML_BLOCKS" value="false" />
+ <option name="SUPPRESS_INLINE_HTML" value="false" />
+ <option name="TABLES" value="true" />
+ <option name="TASKLISTITEMS" value="true" />
+ <option name="TOC" value="false" />
+ <option name="WIKILINKS" value="true" />
+ </PegdownExtensions>
+ <ParserOptions>
+ <option name="COMMONMARK_LISTS" value="false" />
+ <option name="DUMMY" value="false" />
+ <option name="EMOJI_SHORTCUTS" value="true" />
+ <option name="FLEXMARK_FRONT_MATTER" value="false" />
+ <option name="GFM_LOOSE_BLANK_LINE_AFTER_ITEM_PARA" value="false" />
+ <option name="GFM_TABLE_RENDERING" value="true" />
+ <option name="GITBOOK_URL_ENCODING" value="false" />
+ <option name="GITHUB_EMOJI_URL" value="false" />
+ <option name="GITHUB_LISTS" value="true" />
+ <option name="GITHUB_WIKI_LINKS" value="true" />
+ <option name="JEKYLL_FRONT_MATTER" value="false" />
+ <option name="SIM_TOC_BLANK_LINE_SPACER" value="true" />
+ </ParserOptions>
+ </ParserSettings>
+ <HtmlSettings headerTopEnabled="false" headerBottomEnabled="false" bodyTopEnabled="false" bodyBottomEnabled="false" embedUrlContent="false" addPageHeader="true">
+ <GeneratorProvider>
+ <provider providerId="com.vladsch.idea.multimarkdown.editor.swing.html.generator" providerName="Default Swing HTML Generator" />
+ </GeneratorProvider>
+ <headerTop />
+ <headerBottom />
+ <bodyTop />
+ <bodyBottom />
+ </HtmlSettings>
+ <CssSettings previewScheme="UI_SCHEME" cssUri="" isCssUriEnabled="false" isCssTextEnabled="false" isDynamicPageWidth="true">
+ <StylesheetProvider>
+ <provider providerId="com.vladsch.idea.multimarkdown.editor.swing.html.css" providerName="Default Swing Stylesheet" />
+ </StylesheetProvider>
+ <ScriptProviders />
+ <cssText />
+ </CssSettings>
+ <HtmlExportSettings updateOnSave="false" parentDir="$ProjectFileDir$" targetDir="$ProjectFileDir$" cssDir="" scriptDir="" plainHtml="false" imageDir="" copyLinkedImages="false" imageUniquifyType="0" targetExt="" useTargetExt="false" noCssNoScripts="false" linkToExportedHtml="true" exportOnSettingsChange="true" regenerateOnProjectOpen="false" />
+ <LinkMapSettings>
+ <textMaps />
+ </LinkMapSettings>
+ </component>
+ <component name="NullableNotNullManager">
+ <option name="myDefaultNullable" value="android.support.annotation.Nullable" />
+ <option name="myDefaultNotNull" value="android.support.annotation.NonNull" />
+ <option name="myNullables">
+ <value>
+ <list size="4">
+ <item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
+ <item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
+ <item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" />
+ <item index="3" class="java.lang.String" itemvalue="android.support.annotation.Nullable" />
+ </list>
+ </value>
+ </option>
+ <option name="myNotNulls">
+ <value>
+ <list size="4">
+ <item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
+ <item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
+ <item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
+ <item index="3" class="java.lang.String" itemvalue="android.support.annotation.NonNull" />
+ </list>
+ </value>
+ </option>
+ </component>
+ <component name="ProjectInspectionProfilesVisibleTreeState">
+ <entry key="Project Default">
+ <profile-state>
+ <expanded-state>
+ <State>
+ <id />
+ </State>
+ </expanded-state>
+ <selected-state>
+ <State>
+ <id>Android</id>
+ </State>
+ </selected-state>
+ </profile-state>
+ </entry>
+ </component>
+ <component name="ProjectLevelVcsManager" settingsEditedManually="false">
+ <OptionsSetting value="true" id="Add" />
+ <OptionsSetting value="true" id="Remove" />
+ <OptionsSetting value="true" id="Checkout" />
+ <OptionsSetting value="true" id="Update" />
+ <OptionsSetting value="true" id="Status" />
+ <OptionsSetting value="true" id="Edit" />
+ <ConfirmationsSetting value="0" id="Add" />
+ <ConfirmationsSetting value="0" id="Remove" />
+ </component>
+ <component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" default="true" assert-keyword="true" jdk-15="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
+ <output url="file://$PROJECT_DIR$/build/classes" />
+ </component>
+ <component name="ProjectType">
+ <option name="id" value="Android" />
+ </component>
+ <component name="masterDetails">
+ <states>
+ <state key="ProjectJDKs.UI">
+ <settings>
+ <last-edited>1.8</last-edited>
+ <splitter-proportions>
+ <option name="proportions">
+ <list>
+ <option value="0.2" />
+ </list>
+ </option>
+ </splitter-proportions>
+ </settings>
+ </state>
+ </states>
+ </component>
+</project> \ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..b8450ec
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="ProjectModuleManager">
+ <modules>
+ <module fileurl="file://$PROJECT_DIR$/KAU.iml" filepath="$PROJECT_DIR$/KAU.iml" />
+ <module fileurl="file://$PROJECT_DIR$/library/library.iml" filepath="$PROJECT_DIR$/library/library.iml" />
+ <module fileurl="file://$PROJECT_DIR$/sample/sample.iml" filepath="$PROJECT_DIR$/sample/sample.iml" />
+ </modules>
+ </component>
+</project> \ No newline at end of file
diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml
new file mode 100644
index 0000000..7f68460
--- /dev/null
+++ b/.idea/runConfigurations.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="RunConfigurationProducerService">
+ <option name="ignoredProducers">
+ <set>
+ <option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
+ <option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
+ <option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
+ </set>
+ </option>
+ </component>
+</project> \ No newline at end of file
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..e922f4f
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,27 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+ ext.kotlin_version = '1.1.2-4'
+ repositories {
+ jcenter()
+ maven { url 'https://maven.fabric.io/public' }
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:2.3.3'
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+allprojects {
+ repositories {
+ jcenter()
+ mavenCentral()
+ maven { url "https://jitpack.io" }
+ }
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..1ea34fe
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,33 @@
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx1536m
+
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+APP_ID=KPrefs
+APP_GROUP=ca.allanwang
+MIN_SDK=21
+TARGET_SDK=25
+BUILD_TOOLS=25.0.2
+VERSION_CODE=1
+VERSION_NAME=0.1
+ANDROID_SUPPORT_LIBS=25.3.1
+
+MATERIAL_DIALOG=0.9.4.3
+ICONICS=2.8.5
+TIMBER=4.5.1
+CONSTRAINT_LAYOUT=1.0.2
+FAST_ADAPTER=2.6.0
+BUTTERKNIFE=8.6.0
+
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..13372ae
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..01f9c48
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Wed Jun 07 22:36:24 PDT 2017
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip
diff --git a/gradlew b/gradlew
new file mode 100644
index 0000000..9d82f78
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,160 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# 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
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+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`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..8a0b282
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@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 Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/library/.gitignore b/library/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/library/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/library/build.gradle b/library/build.gradle
new file mode 100644
index 0000000..8e5b674
--- /dev/null
+++ b/library/build.gradle
@@ -0,0 +1,64 @@
+apply plugin: 'com.android.library'
+apply plugin: 'kotlin-android'
+
+android {
+ compileSdkVersion Integer.parseInt(project.TARGET_SDK)
+ buildToolsVersion project.BUILD_TOOLS
+
+ defaultConfig {
+ minSdkVersion Integer.parseInt(project.MIN_SDK)
+ targetSdkVersion Integer.parseInt(project.TARGET_SDK)
+ versionCode Integer.parseInt(project.VERSION_CODE)
+ versionName project.VERSION_NAME
+ consumerProguardFiles 'progress-proguard.txt'
+ testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+ lintOptions {
+ abortOnError false
+ checkReleaseBuilds false
+ }
+ resourcePrefix "kau_"
+ sourceSets {
+ main.java.srcDirs += 'src/main/kotlin'
+ test.java.srcDirs += 'src/test/kotlin'
+ }
+}
+
+dependencies {
+ compile fileTree(dir: 'libs', include: ['*.jar'])
+ androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
+ exclude group: 'com.android.support', module: 'support-annotations'
+ })
+ testCompile 'junit:junit:4.12'
+
+ compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
+ testCompile "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version"
+
+ compile "com.android.support:appcompat-v7:${ANDROID_SUPPORT_LIBS}"
+ compile "com.android.support:support-v4:${ANDROID_SUPPORT_LIBS}"
+ compile "com.android.support:support-v13:${ANDROID_SUPPORT_LIBS}"
+ compile "com.android.support:design:${ANDROID_SUPPORT_LIBS}"
+ compile "com.android.support:recyclerview-v7:${ANDROID_SUPPORT_LIBS}"
+ compile "com.android.support:cardview-v7:${ANDROID_SUPPORT_LIBS}"
+ compile "com.android.support:preference-v14:${ANDROID_SUPPORT_LIBS}"
+ compile "com.android.support.constraint:constraint-layout:${CONSTRAINT_LAYOUT}"
+
+ compile "com.mikepenz:fastadapter:${FAST_ADAPTER}@aar"
+ compile "com.mikepenz:fastadapter-commons:${FAST_ADAPTER}@aar"
+
+ compile "com.afollestad.material-dialogs:core:${MATERIAL_DIALOG}"
+ compile "com.afollestad.material-dialogs:commons:${MATERIAL_DIALOG}"
+
+ compile "com.mikepenz:iconics-core:${ICONICS}@aar"
+
+ compile "com.jakewharton.timber:timber:${TIMBER}"
+
+ compile "com.jakewharton:butterknife:${BUTTERKNIFE}"
+ annotationProcessor "com.jakewharton:butterknife-compiler:${BUTTERKNIFE}"
+} \ No newline at end of file
diff --git a/library/progress-proguard.txt b/library/progress-proguard.txt
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/library/progress-proguard.txt
@@ -0,0 +1 @@
+
diff --git a/library/src/androidTest/java/ca/allanwang/kprefs/library/ExampleInstrumentedTest.java b/library/src/androidTest/java/ca/allanwang/kprefs/library/ExampleInstrumentedTest.java
new file mode 100644
index 0000000..8a80585
--- /dev/null
+++ b/library/src/androidTest/java/ca/allanwang/kprefs/library/ExampleInstrumentedTest.java
@@ -0,0 +1,26 @@
+package ca.allanwang.kprefs.library;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+/**
+ * Instrumentation test, which will execute on an Android device.
+ *
+ * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+ @Test
+ public void useAppContext() throws Exception {
+ // Context of the app under test.
+ Context appContext = InstrumentationRegistry.getTargetContext();
+
+ assertEquals("ca.allanwang.kprefs.library.test", appContext.getPackageName());
+ }
+}
diff --git a/library/src/main/AndroidManifest.xml b/library/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..4461514
--- /dev/null
+++ b/library/src/main/AndroidManifest.xml
@@ -0,0 +1 @@
+<manifest package="ca.allanwang.kau" />
diff --git a/library/src/main/kotlin/ca/allanwang/kau/changelog/Changelog.kt b/library/src/main/kotlin/ca/allanwang/kau/changelog/Changelog.kt
new file mode 100644
index 0000000..14b8c4e
--- /dev/null
+++ b/library/src/main/kotlin/ca/allanwang/kau/changelog/Changelog.kt
@@ -0,0 +1,73 @@
+package com.pitchedapps.frost.utils
+
+import android.content.Context
+import android.content.res.XmlResourceParser
+import android.support.annotation.LayoutRes
+import android.support.annotation.XmlRes
+import android.support.v7.widget.RecyclerView
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TextView
+import ca.allanwang.kau.R
+import org.xmlpull.v1.XmlPullParser
+
+
+/**
+ * Created by Allan Wang on 2017-05-28.
+ */
+internal class ChangelogAdapter(val items: List<Pair<String, ChangelogType>>) : RecyclerView.Adapter<ChangelogAdapter.ChangelogVH>() {
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ChangelogVH(LayoutInflater.from(parent.context)
+ .inflate(getLayout(viewType), parent, false))
+
+ private fun getLayout(position: Int) = items[position].second.layout
+
+ override fun onBindViewHolder(holder: ChangelogVH, position: Int) {
+ holder.text.text = items[position].first
+ }
+
+ override fun getItemId(position: Int) = position.toLong()
+
+ override fun getItemViewType(position: Int) = position
+
+ override fun getItemCount() = items.size
+
+ internal class ChangelogVH(itemView: View) : RecyclerView.ViewHolder(itemView) {
+ val text: TextView = itemView.findViewById(R.id.kau_changelog_text) as TextView
+ }
+}
+
+internal fun parse(context: Context, @XmlRes xmlRes: Int): List<Pair<String, ChangelogType>> {
+ val items = mutableListOf<Pair<String, ChangelogType>>()
+ context.resources.getXml(xmlRes).use {
+ parser ->
+ var eventType = parser.eventType
+ while (eventType != XmlPullParser.END_DOCUMENT) {
+ if (eventType == XmlPullParser.START_TAG)
+ ChangelogType.values.any { it.add(parser, items) }
+ eventType = parser.next()
+ }
+ }
+ return items
+}
+
+internal enum class ChangelogType(val tag: String, val attr: String, @LayoutRes val layout: Int) {
+ TITLE("title", "version", R.layout.kau_changelog_title),
+ ITEM("item", "text", R.layout.kau_changelog_content);
+
+ companion object {
+ val values = values()
+ }
+
+ /**
+ * Returns true if tag matches; false otherwise
+ */
+ fun add(parser: XmlResourceParser, list: MutableList<Pair<String, ChangelogType>>): Boolean {
+ if (parser.name != tag) return false
+ if (parser.getAttributeValue(null, attr).isNotBlank())
+ list.add(Pair(parser.getAttributeValue(null, attr), this))
+ return true
+ }
+}
+
diff --git a/library/src/main/kotlin/ca/allanwang/kau/dialogs/color/ColorPalette.kt b/library/src/main/kotlin/ca/allanwang/kau/dialogs/color/ColorPalette.kt
new file mode 100644
index 0000000..22bd0d4
--- /dev/null
+++ b/library/src/main/kotlin/ca/allanwang/kau/dialogs/color/ColorPalette.kt
@@ -0,0 +1,349 @@
+package ca.allanwang.kau.dialogs.color
+
+import android.graphics.Color
+
+/**
+ * @author Aidan Follestad (afollestad)
+ */
+internal object ColorPalette {
+
+ val PRIMARY_COLORS: IntArray by lazy {
+ colorArrayOf(
+ "#F44336",
+ "#E91E63",
+ "#9C27B0",
+ "#673AB7",
+ "#3F51B5",
+ "#2196F3",
+ "#03A9F4",
+ "#00BCD4",
+ "#009688",
+ "#4CAF50",
+ "#8BC34A",
+ "#CDDC39",
+ "#FFEB3B",
+ "#FFC107",
+ "#FF9800",
+ "#FF5722",
+ "#795548",
+ "#9E9E9E",
+ "#607D8B")
+ }
+
+ val PRIMARY_COLORS_SUB: Array<IntArray> by lazy {
+ arrayOf(colorArrayOf(
+ "#FFEBEE",
+ "#FFCDD2",
+ "#EF9A9A",
+ "#E57373",
+ "#EF5350",
+ "#F44336",
+ "#E53935",
+ "#D32F2F",
+ "#C62828",
+ "#B71C1C"
+ ), colorArrayOf(
+ "#FCE4EC",
+ "#F8BBD0",
+ "#F48FB1",
+ "#F06292",
+ "#EC407A",
+ "#E91E63",
+ "#D81B60",
+ "#C2185B",
+ "#AD1457",
+ "#880E4F"
+ ), colorArrayOf(
+ "#F3E5F5",
+ "#E1BEE7",
+ "#CE93D8",
+ "#BA68C8",
+ "#AB47BC",
+ "#9C27B0",
+ "#8E24AA",
+ "#7B1FA2",
+ "#6A1B9A",
+ "#4A148C"
+ ), colorArrayOf(
+ "#EDE7F6",
+ "#D1C4E9",
+ "#B39DDB",
+ "#9575CD",
+ "#7E57C2",
+ "#673AB7",
+ "#5E35B1",
+ "#512DA8",
+ "#4527A0",
+ "#311B92"
+ ), colorArrayOf(
+ "#E8EAF6",
+ "#C5CAE9",
+ "#9FA8DA",
+ "#7986CB",
+ "#5C6BC0",
+ "#3F51B5",
+ "#3949AB",
+ "#303F9F",
+ "#283593",
+ "#1A237E"
+ ), colorArrayOf(
+ "#E3F2FD",
+ "#BBDEFB",
+ "#90CAF9",
+ "#64B5F6",
+ "#42A5F5",
+ "#2196F3",
+ "#1E88E5",
+ "#1976D2",
+ "#1565C0",
+ "#0D47A1"
+ ), colorArrayOf(
+ "#E1F5FE",
+ "#B3E5FC",
+ "#81D4FA",
+ "#4FC3F7",
+ "#29B6F6",
+ "#03A9F4",
+ "#039BE5",
+ "#0288D1",
+ "#0277BD",
+ "#01579B"
+ ), colorArrayOf(
+ "#E0F7FA",
+ "#B2EBF2",
+ "#80DEEA",
+ "#4DD0E1",
+ "#26C6DA",
+ "#00BCD4",
+ "#00ACC1",
+ "#0097A7",
+ "#00838F",
+ "#006064"
+ ), colorArrayOf(
+ "#E0F2F1",
+ "#B2DFDB",
+ "#80CBC4",
+ "#4DB6AC",
+ "#26A69A",
+ "#009688",
+ "#00897B",
+ "#00796B",
+ "#00695C",
+ "#004D40"
+ ), colorArrayOf(
+ "#E8F5E9",
+ "#C8E6C9",
+ "#A5D6A7",
+ "#81C784",
+ "#66BB6A",
+ "#4CAF50",
+ "#43A047",
+ "#388E3C",
+ "#2E7D32",
+ "#1B5E20"
+ ), colorArrayOf(
+ "#F1F8E9",
+ "#DCEDC8",
+ "#C5E1A5",
+ "#AED581",
+ "#9CCC65",
+ "#8BC34A",
+ "#7CB342",
+ "#689F38",
+ "#558B2F",
+ "#33691E"
+ ), colorArrayOf(
+ "#F9FBE7",
+ "#F0F4C3",
+ "#E6EE9C",
+ "#DCE775",
+ "#D4E157",
+ "#CDDC39",
+ "#C0CA33",
+ "#AFB42B",
+ "#9E9D24",
+ "#827717"
+ ), colorArrayOf(
+ "#FFFDE7",
+ "#FFF9C4",
+ "#FFF59D",
+ "#FFF176",
+ "#FFEE58",
+ "#FFEB3B",
+ "#FDD835",
+ "#FBC02D",
+ "#F9A825",
+ "#F57F17"
+ ), colorArrayOf(
+ "#FFF8E1",
+ "#FFECB3",
+ "#FFE082",
+ "#FFD54F",
+ "#FFCA28",
+ "#FFC107",
+ "#FFB300",
+ "#FFA000",
+ "#FF8F00",
+ "#FF6F00"
+ ), colorArrayOf(
+ "#FFF3E0",
+ "#FFE0B2",
+ "#FFCC80",
+ "#FFB74D",
+ "#FFA726",
+ "#FF9800",
+ "#FB8C00",
+ "#F57C00",
+ "#EF6C00",
+ "#E65100"
+ ), colorArrayOf(
+ "#FBE9E7",
+ "#FFCCBC",
+ "#FFAB91",
+ "#FF8A65",
+ "#FF7043",
+ "#FF5722",
+ "#F4511E",
+ "#E64A19",
+ "#D84315",
+ "#BF360C"
+ ), colorArrayOf(
+ "#EFEBE9",
+ "#D7CCC8",
+ "#BCAAA4",
+ "#A1887F",
+ "#8D6E63",
+ "#795548",
+ "#6D4C41",
+ "#5D4037",
+ "#4E342E",
+ "#3E2723"
+ ), colorArrayOf(
+ "#FAFAFA",
+ "#F5F5F5",
+ "#EEEEEE",
+ "#E0E0E0",
+ "#BDBDBD",
+ "#9E9E9E",
+ "#757575",
+ "#616161",
+ "#424242",
+ "#212121"
+ ), colorArrayOf(
+ "#ECEFF1",
+ "#CFD8DC",
+ "#B0BEC5",
+ "#90A4AE",
+ "#78909C",
+ "#607D8B",
+ "#546E7A",
+ "#455A64",
+ "#37474F",
+ "#263238"))
+ }
+
+ val ACCENT_COLORS: IntArray by lazy {
+ colorArrayOf(
+ "#FF1744",
+ "#F50057",
+ "#D500F9",
+ "#651FFF",
+ "#3D5AFE",
+ "#2979FF",
+ "#00B0FF",
+ "#00E5FF",
+ "#1DE9B6",
+ "#00E676",
+ "#76FF03",
+ "#C6FF00",
+ "#FFEA00",
+ "#FFC400",
+ "#FF9100",
+ "#FF3D00")
+ }
+
+ val ACCENT_COLORS_SUB: Array<IntArray> by lazy {
+ arrayOf(colorArrayOf("#FF8A80",
+ "#FF5252",
+ "#FF1744",
+ "#D50000"
+ ), colorArrayOf(
+ "#FF80AB",
+ "#FF4081",
+ "#F50057",
+ "#C51162"
+ ), colorArrayOf(
+ "#EA80FC",
+ "#E040FB",
+ "#D500F9",
+ "#AA00FF"
+ ), colorArrayOf(
+ "#B388FF",
+ "#7C4DFF",
+ "#651FFF",
+ "#6200EA"
+ ), colorArrayOf(
+ "#8C9EFF",
+ "#536DFE",
+ "#3D5AFE",
+ "#304FFE"
+ ), colorArrayOf(
+ "#82B1FF",
+ "#448AFF",
+ "#2979FF",
+ "#2962FF"
+ ), colorArrayOf(
+ "#80D8FF",
+ "#40C4FF",
+ "#00B0FF",
+ "#0091EA"
+ ), colorArrayOf(
+ "#84FFFF",
+ "#18FFFF",
+ "#00E5FF",
+ "#00B8D4"
+ ), colorArrayOf(
+ "#A7FFEB",
+ "#64FFDA",
+ "#1DE9B6",
+ "#00BFA5"
+ ), colorArrayOf(
+ "#B9F6CA",
+ "#69F0AE",
+ "#00E676",
+ "#00C853"
+ ), colorArrayOf(
+ "#CCFF90",
+ "#B2FF59",
+ "#76FF03",
+ "#64DD17"
+ ), colorArrayOf(
+ "#F4FF81",
+ "#EEFF41",
+ "#C6FF00",
+ "#AEEA00"
+ ), colorArrayOf(
+ "#FFFF8D",
+ "#FFFF00",
+ "#FFEA00",
+ "#FFD600"
+ ), colorArrayOf(
+ "#FFE57F",
+ "#FFD740",
+ "#FFC400",
+ "#FFAB00"
+ ), colorArrayOf(
+ "#FFD180",
+ "#FFAB40",
+ "#FF9100",
+ "#FF6D00"
+ ), colorArrayOf(
+ "#FF9E80",
+ "#FF6E40",
+ "#FF3D00",
+ "#DD2C00"))
+ }
+
+ fun colorArrayOf(vararg colors: String) = colors.map { Color.parseColor(it) }.toIntArray()
+}
+
diff --git a/library/src/main/kotlin/ca/allanwang/kau/dialogs/color/ColorPickerDialog.kt b/library/src/main/kotlin/ca/allanwang/kau/dialogs/color/ColorPickerDialog.kt
new file mode 100644
index 0000000..f78cde0
--- /dev/null
+++ b/library/src/main/kotlin/ca/allanwang/kau/dialogs/color/ColorPickerDialog.kt
@@ -0,0 +1,352 @@
+package ca.allanwang.kau.dialogs.color
+
+import android.content.Context
+import android.graphics.Color
+import android.support.annotation.ColorInt
+import android.support.v4.content.res.ResourcesCompat
+import android.text.Editable
+import android.text.InputFilter
+import android.text.TextWatcher
+import android.util.AttributeSet
+import android.view.View
+import android.view.ViewGroup
+import android.widget.*
+import ca.allanwang.kau.R
+import ca.allanwang.kau.logging.SL
+import ca.allanwang.kau.utils.*
+import com.afollestad.materialdialogs.DialogAction
+import com.afollestad.materialdialogs.MaterialDialog
+import com.afollestad.materialdialogs.Theme
+import com.afollestad.materialdialogs.color.CircleView
+import com.afollestad.materialdialogs.color.FillGridView
+import java.util.*
+
+/**
+ * Created by Allan Wang on 2017-06-08.
+ */
+internal class ColorPickerView @JvmOverloads constructor(
+ context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
+) : ScrollView(context, attrs, defStyleAttr) {
+ var selectedColor: Int = -1
+ var isInSub: Boolean = false
+ var isInCustom: Boolean = false
+ var circleSize: Int = context.dimen(R.dimen.kau_color_circle_size).toInt()
+ lateinit var dialog: MaterialDialog
+ lateinit var builder: Builder
+ lateinit var colorsTop: IntArray
+ var colorsSub: Array<IntArray>? = null
+ var topIndex: Int = -1
+ var subIndex: Int = -1
+ var colorIndex: Int
+ get() = if (isInSub) subIndex else topIndex
+ set(value) {
+ if (isInSub) subIndex = value
+ else {
+ topIndex = value
+ if (colorsSub != null && colorsSub!!.size > value) {
+ dialog.setActionButton(DialogAction.NEGATIVE, builder.backText)
+ isInSub = true
+ }
+ }
+ }
+
+
+ val gridView: FillGridView by bindView(R.id.md_grid)
+ val customFrame: LinearLayout by bindView(R.id.md_colorChooserCustomFrame)
+ val customColorIndicator: View by bindView(R.id.md_colorIndicator)
+ val hexInput: EditText by bindView(R.id.md_hexInput)
+ val alphaLabel: TextView by bindView(R.id.md_colorALabel)
+ val alphaSeekbar: SeekBar by bindView(R.id.md_colorA)
+ val alphaValue: TextView by bindView(R.id.md_colorAValue)
+ val redSeekbar: SeekBar by bindView(R.id.md_colorR)
+ val redValue: TextView by bindView(R.id.md_colorRValue)
+ val greenSeekbar: SeekBar by bindView(R.id.md_colorG)
+ val greenValue: TextView by bindView(R.id.md_colorGValue)
+ val blueSeekbar: SeekBar by bindView(R.id.md_colorB)
+ val blueValue: TextView by bindView(R.id.md_colorBValue)
+
+ var customHexTextWatcher: TextWatcher? = null
+ var customRgbListener: SeekBar.OnSeekBarChangeListener? = null
+
+ init {
+ View.inflate(context, R.layout.md_dialog_colorchooser, this)
+ }
+
+ fun bind(builder: Builder, dialog: MaterialDialog) {
+ this.builder = builder
+ this.dialog = dialog
+ this.colorsTop = builder.colorsTop()
+ this.colorsSub = builder.colorsSub()
+ this.selectedColor = builder.defaultColor
+ if (builder.allowCustom) {
+ if (!builder.allowCustomAlpha) {
+ alphaLabel.gone()
+ alphaSeekbar.gone()
+ alphaValue.gone()
+ hexInput.hint = String.format("%06X", selectedColor)
+ hexInput.filters = arrayOf(InputFilter.LengthFilter(6))
+ } else {
+ hexInput.hint = String.format("%08X", selectedColor)
+ hexInput.filters = arrayOf(InputFilter.LengthFilter(8))
+ }
+ }
+ if (findColor(builder.defaultColor)) isInCustom = true //when toggled this will be false
+ toggleCustom()
+ }
+
+ fun backOrCancel() {
+ if (isInSub) {
+ dialog.setActionButton(DialogAction.NEGATIVE, builder.cancelText)
+ //to top
+ isInSub = false
+ subIndex = -1
+ invalidateGrid()
+ } else {
+ dialog.cancel()
+ }
+ }
+
+ fun toggleCustom() {
+ isInCustom = !isInCustom
+ if (isInCustom) {
+ isInSub = false
+ dialog.setActionButton(DialogAction.NEUTRAL, builder.presetText)
+ dialog.setActionButton(DialogAction.NEGATIVE, builder.cancelText)
+ customHexTextWatcher = object : TextWatcher {
+ override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
+
+ override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
+ try {
+ selectedColor = Color.parseColor("#" + s.toString())
+ } catch (e: IllegalArgumentException) {
+ selectedColor = Color.BLACK
+ }
+
+ customColorIndicator.setBackgroundColor(selectedColor)
+ if (alphaSeekbar.isVisible()) {
+ val alpha = Color.alpha(selectedColor)
+ alphaSeekbar.progress = alpha
+ alphaValue.text = String.format(Locale.CANADA, "%d", alpha)
+ }
+ redSeekbar.progress = Color.red(selectedColor)
+ greenSeekbar.progress = Color.green(selectedColor)
+ blueSeekbar.progress = Color.blue(selectedColor)
+ isInSub = false
+ topIndex = -1
+ subIndex = -1
+ refreshColors()
+ }
+
+ override fun afterTextChanged(s: Editable?) {}
+ }
+ hexInput.setText(selectedColor.toHexString(builder.allowCustomAlpha, false))
+ hexInput.addTextChangedListener(customHexTextWatcher)
+ customRgbListener = object : SeekBar.OnSeekBarChangeListener {
+ override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
+ SL.d("Progress $progress")
+ if (fromUser) {
+ val color = if (builder.allowCustomAlpha)
+ Color.argb(alphaSeekbar.progress,
+ redSeekbar.progress,
+ greenSeekbar.progress,
+ blueSeekbar.progress)
+ else Color.rgb(redSeekbar.progress,
+ greenSeekbar.progress,
+ blueSeekbar.progress)
+
+ hexInput.setText(color.toHexString(builder.allowCustomAlpha, false))
+ }
+ if (builder.allowCustomAlpha) alphaValue.text = alphaSeekbar.progress.toString()
+ redValue.text = redSeekbar.progress.toString()
+ greenValue.text = greenSeekbar.progress.toString()
+ blueValue.text = blueSeekbar.progress.toString()
+ }
+
+ override fun onStartTrackingTouch(seekBar: SeekBar) {}
+
+ override fun onStopTrackingTouch(seekBar: SeekBar) {}
+ }
+ redSeekbar.setOnSeekBarChangeListener(customRgbListener)
+ greenSeekbar.setOnSeekBarChangeListener(customRgbListener)
+ blueSeekbar.setOnSeekBarChangeListener(customRgbListener)
+ if (alphaSeekbar.isVisible())
+ alphaSeekbar.setOnSeekBarChangeListener(customRgbListener)
+ hexInput.setText(selectedColor.toHexString(alphaSeekbar.isVisible(), false))
+ gridView.fadeOut(onFinish = { gridView.gone() })
+ customFrame.fadeIn()
+ } else {
+ findColor(selectedColor)
+ dialog.setActionButton(DialogAction.NEUTRAL, builder.customText)
+ dialog.setActionButton(DialogAction.NEGATIVE, if (isInSub) builder.backText else builder.cancelText)
+ gridView.fadeIn(onStart = { invalidateGrid() })
+ customFrame.fadeOut(onFinish = { customFrame.gone() })
+ hexInput.removeTextChangedListener(customHexTextWatcher)
+ customHexTextWatcher = null
+ alphaSeekbar.setOnSeekBarChangeListener(null)
+ redSeekbar.setOnSeekBarChangeListener(null)
+ greenSeekbar.setOnSeekBarChangeListener(null)
+ blueSeekbar.setOnSeekBarChangeListener(null)
+ customRgbListener = null
+ }
+ }
+
+ fun refreshColors() {
+ if (!isInCustom) findColor(selectedColor)
+ if (builder.dynamicButtonColors) {
+ dialog.getActionButton(DialogAction.POSITIVE).setTextColor(selectedColor)
+ dialog.getActionButton(DialogAction.NEGATIVE).setTextColor(selectedColor)
+ dialog.getActionButton(DialogAction.NEUTRAL).setTextColor(selectedColor)
+ }
+ if (!builder.allowCustom || !isInCustom) return
+ // Once we get close to white or transparent,
+ // the action buttons and seekbars will be a very light gray.
+ // TODO change by dialog theme
+ val visibleColor = if (Color.alpha(selectedColor) < 64 ||
+ Color.red(selectedColor) > 247 && Color.green(selectedColor) > 247 && Color.blue(selectedColor) > 247)
+ "#DEDEDE".toColor() else selectedColor
+
+ if (builder.allowCustomAlpha)
+ alphaSeekbar.visible().tint(visibleColor)
+ redSeekbar.tint(visibleColor)
+ greenSeekbar.tint(visibleColor)
+ blueSeekbar.tint(visibleColor)
+ hexInput.tint(visibleColor)
+ }
+
+ fun findColor(@ColorInt color: Int): Boolean {
+ topIndex = -1
+ subIndex = -1
+ colorsTop.forEachIndexed {
+ index, topColor ->
+ if (findSubColor(color, index)) {
+ topIndex = index
+ return true
+ }
+ if (topColor == color) { // If no sub colors exists and top color matches
+ topIndex = index
+ return true
+ }
+ }
+ return false
+ }
+
+ fun findSubColor(@ColorInt color: Int, topIndex: Int): Boolean {
+ if (colorsSub == null || colorsSub!!.size <= topIndex) return false
+ colorsSub!![topIndex].forEachIndexed {
+ index, subColor ->
+ if (subColor == color) {
+ subIndex = index
+ return true
+ }
+ }
+ return false
+ }
+
+ fun invalidateGrid() {
+ if (gridView.adapter == null) {
+ gridView.adapter = ColorGridAdapter()
+ gridView.selector = ResourcesCompat.getDrawable(resources, R.drawable.kau_transparent, null)
+ } else {
+ (gridView.adapter as BaseAdapter).notifyDataSetChanged()
+ }
+ }
+
+ inner class ColorGridAdapter : BaseAdapter(), OnClickListener, OnLongClickListener {
+ override fun onClick(v: View) {
+ if (v.tag != null && v.tag is String) {
+ val tags = (v.tag as String).split(":")
+ colorIndex = tags[0].toInt()
+ selectedColor = tags[1].toInt()
+ refreshColors()
+ invalidateGrid()
+ }
+ }
+
+ override fun onLongClick(v: View): Boolean {
+ if (v.tag != null && v.tag is String) {
+ val tag = (v.tag as String).split(":")
+ val color = tag[1].toInt()
+ (v as CircleView).showHint(color)
+ return true
+ }
+ return false
+ }
+
+ override fun getItem(position: Int): Any = if (isInSub) colorsSub!![topIndex][position] else colorsTop[position]
+
+ override fun getCount(): Int = if (isInSub) colorsSub!![topIndex].size else colorsTop.size
+
+ override fun getItemId(position: Int): Long = position.toLong()
+
+ override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
+ val view: CircleView = if (convertView == null)
+ CircleView(context).apply { layoutParams = AbsListView.LayoutParams(circleSize, circleSize) }
+ else
+ convertView as CircleView
+ val color: Int = if (isInSub) colorsSub!![topIndex][position] else colorsTop[position]
+ return view.apply {
+ setBackgroundColor(color)
+ isSelected = (if (isInSub) subIndex else topIndex) == position
+ tag = "$position:$color"
+ setOnClickListener(this@ColorGridAdapter)
+ setOnLongClickListener(this@ColorGridAdapter)
+ }
+ }
+
+ }
+}
+
+class Builder {
+ var title: String? = null
+ var titleRes: Int = -1
+ var allowCustom: Boolean = true
+ var allowCustomAlpha: Boolean = false
+ var isAccent: Boolean = false
+ var defaultColor: Int = Color.BLACK
+ var doneText: Int = R.string.kau_done
+ var backText: Int = R.string.kau_back
+ var cancelText: Int = R.string.kau_cancel
+ var presetText: Int = R.string.kau_md_presets
+ var customText: Int = R.string.kau_md_custom
+ get() = if (allowCustom) field else 0
+ var dynamicButtonColors: Boolean = true
+ var circleSizeRes: Int = R.dimen.kau_color_circle_size
+ var colorCallbacks: MutableList<((selectedColor: Int) -> Unit)> = mutableListOf()
+ var colorsTop: IntArray? = null
+ internal fun colorsTop(): IntArray =
+ if (colorsTop != null) colorsTop!!
+ else if (isAccent) ColorPalette.ACCENT_COLORS
+ else ColorPalette.PRIMARY_COLORS
+
+ var colorsSub: Array<IntArray>? = null
+ internal fun colorsSub(): Array<IntArray>? =
+ if (colorsTop != null) colorsSub
+ else if (isAccent) ColorPalette.ACCENT_COLORS_SUB
+ else ColorPalette.PRIMARY_COLORS_SUB
+
+ var theme: Theme? = null
+
+}
+
+
+fun Context.colorPickerDialog(action: Builder.() -> Unit): MaterialDialog {
+ val b = Builder()
+ b.action()
+ val view = ColorPickerView(this)
+ val dialog = with(MaterialDialog.Builder(this)) {
+ title(string(b.titleRes, b.title) ?: string(R.string.kau_md_color_palette))
+ customView(view, false)
+ autoDismiss(false)
+ positiveText(b.doneText)
+ negativeText(b.cancelText)
+ neutralText(b.presetText)
+ onPositive { dialog, _ -> b.colorCallbacks.forEach { it.invoke(view.selectedColor) }; dialog.dismiss() }
+ onNegative { dialog, _ -> view.backOrCancel() }
+ if (b.allowCustom) onNeutral { dialog, _ -> view.toggleCustom() }
+ showListener { view.refreshColors() }
+ if (b.theme != null) theme(b.theme!!)
+ build()
+ }
+ view.bind(b, dialog)
+ return dialog
+} \ No newline at end of file
diff --git a/library/src/main/kotlin/ca/allanwang/kau/dialogs/color/ColorPickerPreference.kt b/library/src/main/kotlin/ca/allanwang/kau/dialogs/color/ColorPickerPreference.kt
new file mode 100644
index 0000000..043e287
--- /dev/null
+++ b/library/src/main/kotlin/ca/allanwang/kau/dialogs/color/ColorPickerPreference.kt
@@ -0,0 +1,72 @@
+package ca.allanwang.kau.dialogs.color
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.graphics.Color
+import android.os.Build
+import android.os.Bundle
+import android.os.Parcelable
+import android.support.annotation.ColorInt
+import android.support.annotation.StringRes
+import android.support.v4.content.res.ResourcesCompat
+import android.support.v7.app.AppCompatActivity
+import android.support.v7.preference.Preference
+import android.text.Editable
+import android.text.TextWatcher
+import android.util.AttributeSet
+import android.view.View
+import android.view.ViewGroup
+import android.widget.*
+import ca.allanwang.kau.R
+import ca.allanwang.kau.utils.ANDROID_NAMESPACE
+import ca.allanwang.kau.utils.integer
+import ca.allanwang.kau.utils.resolveColor
+import ca.allanwang.kau.utils.toColor
+import com.afollestad.materialdialogs.DialogAction
+import com.afollestad.materialdialogs.MaterialDialog
+import com.afollestad.materialdialogs.color.CircleView
+import com.afollestad.materialdialogs.color.ColorChooserDialog
+import com.afollestad.materialdialogs.internal.MDTintHelper
+import com.afollestad.materialdialogs.util.DialogUtils
+import java.util.*
+
+/**
+ * Created by Allan Wang on 2017-06-08.
+ */
+class ColorPickerPreference @JvmOverloads constructor(
+ context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0, defStyleRes: Int = 0
+) : Preference(context, attrs, defStyleAttr, defStyleRes), Preference.OnPreferenceClickListener {
+
+ var defaultColor: Int = Color.BLACK
+ var currentColor: Int
+ var accentMode = false
+ var dialogTitle: Int = 0
+
+ init {
+ onPreferenceClickListener = this
+ if (attrs != null) {
+ val defaultValue = attrs.getAttributeValue(ANDROID_NAMESPACE, "defaultValue")
+ if (defaultValue?.startsWith("#") ?: false) {
+ try {
+ defaultColor = defaultValue.toColor()
+ } catch (e: IllegalArgumentException) {
+ throw IllegalArgumentException("ColorPickerPreference $key has a default value that is not a color resource: $defaultValue")
+ }
+ } else {
+ val resourceId = attrs.getAttributeResourceValue(ANDROID_NAMESPACE, "defaultValue", 0)
+ if (resourceId != 0)
+ defaultColor = context.integer(resourceId)
+ else
+ throw IllegalArgumentException("ColorPickerPreference $key has a default value that is not a color resource: $defaultValue")
+ }
+ }
+ currentColor = getPersistedInt(defaultColor)
+ }
+
+ override fun onPreferenceClick(preference: Preference): Boolean {
+ context.colorPickerDialog {
+
+ }.show()
+ return true
+ }
+} \ No newline at end of file
diff --git a/library/src/main/kotlin/ca/allanwang/kau/kpref/KPref.kt b/library/src/main/kotlin/ca/allanwang/kau/kpref/KPref.kt
new file mode 100644
index 0000000..add79b9
--- /dev/null
+++ b/library/src/main/kotlin/ca/allanwang/kau/kpref/KPref.kt
@@ -0,0 +1,32 @@
+package ca.allanwang.kau.kpref
+
+import android.content.Context
+import android.content.SharedPreferences
+
+/**
+ * Created by Allan Wang on 2017-06-07.
+ */
+open class KPref {
+
+ lateinit private var c: Context
+ lateinit internal var PREFERENCE_NAME: String
+ private var initialized = false
+
+ fun initialize(c: Context, preferenceName: String) {
+ if (initialized) throw KPrefException("KPref object $preferenceName has already been initialized; please only do so once")
+ initialized = true
+ this.c = c.applicationContext
+ PREFERENCE_NAME = preferenceName
+ }
+
+ internal val sp: SharedPreferences by lazy {
+ if (!initialized) throw KPrefException("KPref object has not yet been initialized; please initialize it with a context and preference name")
+ c.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE) }
+
+ internal val prefMap: MutableMap<String, KPrefDelegate<*>> = mutableMapOf()
+
+ fun reset() {
+ prefMap.values.forEach { it.invalidate() }
+ }
+
+} \ No newline at end of file
diff --git a/library/src/main/kotlin/ca/allanwang/kau/kpref/KPrefBinder.kt b/library/src/main/kotlin/ca/allanwang/kau/kpref/KPrefBinder.kt
new file mode 100644
index 0000000..e346e77
--- /dev/null
+++ b/library/src/main/kotlin/ca/allanwang/kau/kpref/KPrefBinder.kt
@@ -0,0 +1,46 @@
+package ca.allanwang.kau.kpref
+
+import android.support.annotation.StringRes
+import android.support.v7.widget.LinearLayoutManager
+import android.support.v7.widget.RecyclerView
+import ca.allanwang.kau.kpref.items.KPrefCheckbox
+import ca.allanwang.kau.kpref.items.KPrefColorPicker
+import ca.allanwang.kau.kpref.items.KPrefHeader
+import ca.allanwang.kau.kpref.items.KPrefItemCore
+import com.mikepenz.fastadapter.commons.adapters.FastItemAdapter
+import com.mikepenz.iconics.typeface.IIcon
+
+/**
+ * Created by Allan Wang on 2017-06-08.
+ */
+fun RecyclerView.setKPrefAdapter(builder: KPrefAdapterBuilder.() -> Unit): FastItemAdapter<KPrefItemCore> {
+ layoutManager = LinearLayoutManager(context)
+ val adapter = FastItemAdapter<KPrefItemCore>()
+ adapter.withOnClickListener { v, _, item, _ -> item.onClick(v) }
+ val items = KPrefAdapterBuilder()
+ builder.invoke(items)
+ adapter.add(items.list)
+ this.adapter = adapter
+ return adapter
+}
+
+class KPrefAdapterBuilder {
+
+ fun header(@StringRes title: Int) = list.add(KPrefHeader(title))
+
+ fun checkbox(@StringRes title: Int,
+ @StringRes description: Int = -1,
+ iicon: IIcon? = null,
+ enabled: Boolean = true,
+ getter: () -> Boolean,
+ setter: (value: Boolean) -> Unit) = list.add(KPrefCheckbox(title, description, iicon, enabled, getter, setter))
+
+ fun colorPicker(@StringRes title: Int,
+ @StringRes description: Int = -1,
+ iicon: IIcon? = null,
+ enabled: Boolean = true,
+ getter: () -> Int,
+ setter: (value: Int) -> Unit)= list.add(KPrefColorPicker(title, description, iicon, enabled, getter, setter))
+
+ internal val list: MutableList<KPrefItemCore> = mutableListOf()
+} \ No newline at end of file
diff --git a/library/src/main/kotlin/ca/allanwang/kau/kpref/KPrefDelegate.kt b/library/src/main/kotlin/ca/allanwang/kau/kpref/KPrefDelegate.kt
new file mode 100644
index 0000000..f897660
--- /dev/null
+++ b/library/src/main/kotlin/ca/allanwang/kau/kpref/KPrefDelegate.kt
@@ -0,0 +1,83 @@
+package ca.allanwang.kau.kpref
+
+/**
+ * Created by Allan Wang on 2017-06-07.
+ */
+private object UNINITIALIZED
+
+fun KPref.kpref(key: String, fallback: Boolean): KPrefDelegate<Boolean> = KPrefDelegate(key, fallback, this)
+fun KPref.kpref(key: String, fallback: Double): KPrefDelegate<Float> = KPrefDelegate(key, fallback.toFloat(), this)
+fun KPref.kpref(key: String, fallback: Float): KPrefDelegate<Float> = KPrefDelegate(key, fallback, this)
+fun KPref.kpref(key: String, fallback: Int): KPrefDelegate<Int> = KPrefDelegate(key, fallback, this)
+fun KPref.kpref(key: String, fallback: Long): KPrefDelegate<Long> = KPrefDelegate(key, fallback, this)
+fun KPref.kpref(key: String, fallback: Set<String>): KPrefDelegate<StringSet> = KPrefDelegate(key, StringSet(fallback), this)
+fun KPref.kpref(key: String, fallback: String): KPrefDelegate<String> = KPrefDelegate(key, fallback, this)
+
+class StringSet(set: Collection<String>) : LinkedHashSet<String>(set)
+
+class KPrefDelegate<T : Any> internal constructor(private val key: String, private val fallback: T, private val pref: KPref, lock: Any? = null) : Lazy<T>, java.io.Serializable {
+
+ @Volatile private var _value: Any = UNINITIALIZED
+ private val lock = lock ?: this
+
+ init {
+ if (pref.prefMap.containsKey(key))
+ throw KPrefException("$key is already used elsewhere in preference ${pref.PREFERENCE_NAME}")
+ pref.prefMap.put(key, this@KPrefDelegate)
+ }
+
+ fun invalidate() {
+ _value = UNINITIALIZED
+ }
+
+ override val value: T
+ get() {
+ val _v1 = _value
+ if (_v1 !== UNINITIALIZED)
+ @Suppress("UNCHECKED_CAST")
+ return _v1 as T
+
+ return synchronized(lock) {
+ val _v2 = _value
+ if (_v2 !== UNINITIALIZED) {
+ @Suppress("UNCHECKED_CAST")
+ _v2 as T
+ } else {
+ _value = when (fallback) {
+ is Boolean -> pref.sp.getBoolean(key, fallback)
+ is Float -> pref.sp.getFloat(key, fallback)
+ is Int -> pref.sp.getInt(key, fallback)
+ is Long -> pref.sp.getLong(key, fallback)
+ is StringSet -> pref.sp.getStringSet(key, fallback)
+ is String -> pref.sp.getString(key, fallback)
+ else -> throw KPrefException(fallback)
+ }
+ @Suppress("UNCHECKED_CAST")
+ _value as T
+ }
+ }
+ }
+
+ override fun isInitialized(): Boolean = _value !== UNINITIALIZED
+
+ override fun toString(): String = if (isInitialized()) value.toString() else "Lazy value not initialized yet."
+
+ operator fun setValue(any: Any, property: kotlin.reflect.KProperty<*>, t: T) {
+ _value = t
+ val editor = pref.sp.edit()
+ when (t) {
+ is Boolean -> editor.putBoolean(key, t)
+ is Float -> editor.putFloat(key, t)
+ is Int -> editor.putInt(key, t)
+ is Long -> editor.putLong(key, t)
+ is StringSet -> editor.putStringSet(key, t)
+ is String -> editor.putString(key, t)
+ else -> throw KPrefException(t)
+ }
+ editor.apply()
+ }
+}
+
+class KPrefException(message: String) : IllegalAccessException(message) {
+ constructor(element: Any?) : this("Invalid type in pref cache: ${element?.javaClass?.simpleName ?: "null"}")
+} \ No newline at end of file
diff --git a/library/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefCheckbox.kt b/library/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefCheckbox.kt
new file mode 100644
index 0000000..3c87571
--- /dev/null
+++ b/library/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefCheckbox.kt
@@ -0,0 +1,34 @@
+package ca.allanwang.kau.kpref.items
+
+import android.support.annotation.StringRes
+import android.view.View
+import android.widget.CheckBox
+import ca.allanwang.kau.R
+import com.mikepenz.iconics.typeface.IIcon
+
+/**
+ * Created by Allan Wang on 2017-06-07.
+ */
+class KPrefCheckbox(@StringRes title: Int,
+ @StringRes description: Int = -1,
+ iicon: IIcon? = null,
+ enabled: Boolean = true,
+ getter: () -> Boolean,
+ setter: (value: Boolean) -> Unit) : KPrefItemBase<Boolean>(title, description, iicon, enabled, getter, setter) {
+
+ override fun onPostBindView(viewHolder: KPrefItemCore.ViewHolder) {
+ super.onPostBindView(viewHolder)
+ viewHolder.addInnerView(R.layout.kau_preference_checkbox)
+ (viewHolder[R.id.kau_pref_checkbox] as CheckBox).isChecked = pref
+ }
+
+ override fun onClick(itemView: View): Boolean {
+ val checkbox = itemView.findViewById(R.id.kau_pref_checkbox) as CheckBox
+ pref = !pref
+ checkbox.isChecked = pref
+ return true
+ }
+
+ override fun getType(): Int = R.id.kau_item_pref_checkbox
+
+} \ No newline at end of file
diff --git a/library/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefColorPicker.kt b/library/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefColorPicker.kt
new file mode 100644
index 0000000..cca35b0
--- /dev/null
+++ b/library/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefColorPicker.kt
@@ -0,0 +1,35 @@
+package ca.allanwang.kau.kpref.items
+
+import android.support.annotation.StringRes
+import android.view.View
+import ca.allanwang.kau.R
+import ca.allanwang.kau.dialogs.color.colorPickerDialog
+import com.mikepenz.iconics.typeface.IIcon
+
+/**
+ * Created by Allan Wang on 2017-06-07.
+ */
+class KPrefColorPicker(@StringRes title: Int,
+ @StringRes description: Int = -1,
+ iicon: IIcon? = null,
+ enabled: Boolean = true,
+ getter: () -> Int,
+ setter: (value: Int) -> Unit) : KPrefItemBase<Int>(title, description, iicon, enabled, getter, setter) {
+
+ override fun onPostBindView(viewHolder: KPrefItemCore.ViewHolder) {
+ super.onPostBindView(viewHolder)
+ //TODO add color circle view
+ }
+
+ override fun onClick(itemView: View): Boolean {
+ itemView.context.colorPickerDialog {
+ titleRes = this@KPrefColorPicker.title
+ defaultColor = pref
+ colorCallbacks.add { pref = it }
+ }.show()
+ return true
+ }
+
+ override fun getType(): Int = R.id.kau_item_pref_color_picker
+
+} \ No newline at end of file
diff --git a/library/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefHeader.kt b/library/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefHeader.kt
new file mode 100644
index 0000000..9c22469
--- /dev/null
+++ b/library/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefHeader.kt
@@ -0,0 +1,22 @@
+package ca.allanwang.kau.kpref.items
+
+import android.support.annotation.StringRes
+import android.view.View
+import ca.allanwang.kau.R
+
+/**
+ * Created by Allan Wang on 2017-06-07.
+ */
+class KPrefHeader(@StringRes title: Int) : KPrefItemCore(title = title) {
+
+ override fun getLayoutRes(): Int = R.layout.kau_preference_header
+
+ override fun onPostBindView(viewHolder: ViewHolder) {
+ viewHolder.itemView.isClickable = false
+ }
+
+ override fun onClick(itemView: View): Boolean = true
+
+ override fun getType() = R.id.kau_item_pref_header
+
+} \ No newline at end of file
diff --git a/library/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefItemBase.kt b/library/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefItemBase.kt
new file mode 100644
index 0000000..c86f3b6
--- /dev/null
+++ b/library/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefItemBase.kt
@@ -0,0 +1,35 @@
+package ca.allanwang.kau.kpref.items
+
+import android.support.annotation.CallSuper
+import android.support.annotation.StringRes
+import android.util.Log
+import ca.allanwang.kau.R
+import com.mikepenz.iconics.typeface.IIcon
+
+/**
+ * Created by Allan Wang on 2017-06-05.
+ *
+ * Base class for pref setters that include the Shared Preference hooks
+ */
+
+abstract class KPrefItemBase<T>(@StringRes title: Int,
+ @StringRes description: Int = -1,
+ iicon: IIcon? = null,
+ val enabled: Boolean = true,
+ private val getter: () -> T,
+ private val setter: (value: T) -> Unit) : KPrefItemCore(title, description, iicon) {
+
+ var pref: T
+ get() = getter.invoke()
+ set(value) {
+ setter.invoke(value)
+ }
+
+ @CallSuper
+ override fun onPostBindView(viewHolder: ViewHolder) {
+ viewHolder.itemView.isEnabled = enabled
+ viewHolder.itemView.alpha = if (enabled) 1.0f else 0.3f
+ }
+
+ override final fun getLayoutRes(): Int = R.layout.kau_preference
+} \ No newline at end of file
diff --git a/library/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefItemCore.kt b/library/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefItemCore.kt
new file mode 100644
index 0000000..8766234
--- /dev/null
+++ b/library/src/main/kotlin/ca/allanwang/kau/kpref/items/KPrefItemCore.kt
@@ -0,0 +1,83 @@
+package ca.allanwang.kau.kpref.items
+
+import android.support.annotation.CallSuper
+import android.support.annotation.IdRes
+import android.support.annotation.LayoutRes
+import android.support.annotation.StringRes
+import android.support.v7.widget.RecyclerView
+import android.view.LayoutInflater
+import android.view.View
+import android.widget.ImageView
+import android.widget.LinearLayout
+import android.widget.TextView
+import butterknife.ButterKnife
+import ca.allanwang.kau.R
+import ca.allanwang.kau.logging.SL
+import ca.allanwang.kau.utils.*
+import com.mikepenz.fastadapter.items.AbstractItem
+import com.mikepenz.iconics.typeface.IIcon
+
+/**
+ * Created by Allan Wang on 2017-06-05.
+ *
+ * Core class containing nothing but the view items
+ */
+
+abstract class KPrefItemCore(@StringRes val title: Int,
+ @StringRes val description: Int = -1,
+ val iicon: IIcon? = null) : AbstractItem<KPrefItemCore, KPrefItemCore.ViewHolder>() {
+
+ override final fun getViewHolder(v: View) = ViewHolder(v)
+
+ @CallSuper
+ override fun bindView(viewHolder: ViewHolder, payloads: List<Any>) {
+ super.bindView(viewHolder, payloads)
+ with(viewHolder) {
+ val context = itemView.context
+ title.text = context.string(this@KPrefItemCore.title)
+ if (description > 0)
+ desc?.visible()?.setText(description)
+ else
+ desc?.gone()
+ if (iicon != null) {
+ iconFrame?.visible()
+ icon?.setIcon(iicon, 48)
+ } else iconFrame?.gone()
+ onPostBindView(this)
+ }
+ }
+
+ abstract fun onPostBindView(viewHolder: ViewHolder)
+
+ abstract fun onClick(itemView: View): Boolean
+
+ override fun unbindView(holder: ViewHolder) {
+ super.unbindView(holder)
+ with(holder) {
+ title.text = null
+ desc?.text = null
+ icon?.setImageDrawable(null)
+ innerFrame?.removeAllViews()
+ itemView.isEnabled = true
+ itemView.alpha = 1.0f
+ }
+ }
+
+ class ViewHolder(v: View) : RecyclerView.ViewHolder(v) {
+ val title: TextView by bindView(R.id.kau_pref_title)
+ val desc: TextView? by bindOptionalView(R.id.kau_pref_desc)
+ val icon: ImageView? by bindOptionalView(R.id.kau_pref_icon)
+ val iconFrame: LinearLayout? by bindOptionalView(R.id.kau_pref_icon_frame)
+ val innerFrame: LinearLayout? by bindOptionalView(R.id.kau_pref_inner_frame)
+
+ init {
+ ButterKnife.bind(v)
+ }
+
+ fun addInnerView(@LayoutRes id: Int) {
+ LayoutInflater.from(innerFrame!!.context).inflate(id, innerFrame)
+ }
+
+ operator fun get(@IdRes id: Int): View = itemView.findViewById(id)
+ }
+} \ No newline at end of file
diff --git a/library/src/main/kotlin/ca/allanwang/kau/logging/SL.kt b/library/src/main/kotlin/ca/allanwang/kau/logging/SL.kt
new file mode 100644
index 0000000..5f67e23
--- /dev/null
+++ b/library/src/main/kotlin/ca/allanwang/kau/logging/SL.kt
@@ -0,0 +1,6 @@
+package ca.allanwang.kau.logging
+
+/**
+ * Created by Allan Wang on 2017-06-08.
+ */
+object SL: TimberLogger("KAU Sample") \ No newline at end of file
diff --git a/library/src/main/kotlin/ca/allanwang/kau/logging/TimberLogger.kt b/library/src/main/kotlin/ca/allanwang/kau/logging/TimberLogger.kt
new file mode 100644
index 0000000..2bbd4a6
--- /dev/null
+++ b/library/src/main/kotlin/ca/allanwang/kau/logging/TimberLogger.kt
@@ -0,0 +1,15 @@
+package ca.allanwang.kau.logging
+
+import timber.log.Timber
+
+
+/**
+ * Created by Allan Wang on 2017-05-28.
+ */
+open class TimberLogger(tag: String) {
+ internal val TAG = "$tag: %s"
+ fun e(s: String) = Timber.e(TAG, s)
+ fun d(s: String) = Timber.d(TAG, s)
+ fun i(s: String) = Timber.i(TAG, s)
+ fun v(s: String) = Timber.v(TAG, s)
+} \ No newline at end of file
diff --git a/library/src/main/kotlin/ca/allanwang/kau/utils/AnimUtils.kt b/library/src/main/kotlin/ca/allanwang/kau/utils/AnimUtils.kt
new file mode 100644
index 0000000..d1e527e
--- /dev/null
+++ b/library/src/main/kotlin/ca/allanwang/kau/utils/AnimUtils.kt
@@ -0,0 +1,125 @@
+package ca.allanwang.kau.utils
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.support.annotation.StringRes
+import android.view.View
+import android.view.ViewAnimationUtils
+import android.view.animation.Animation
+import android.view.animation.AnimationUtils
+import android.view.animation.DecelerateInterpolator
+import android.widget.TextView
+
+/**
+ * Created by Allan Wang on 2017-06-01.
+ *
+ * Animation extension functions for Views
+ */
+fun View.rootCircularReveal(x: Int = 0, y: Int = 0, duration: Long = 500L, onStart: (() -> Unit)? = null, onFinish: (() -> Unit)? = null) {
+ this.addOnLayoutChangeListener(object : View.OnLayoutChangeListener {
+ override fun onLayoutChange(v: View, left: Int, top: Int, right: Int, bottom: Int, oldLeft: Int, oldTop: Int,
+ oldRight: Int, oldBottom: Int) {
+ v.removeOnLayoutChangeListener(this)
+ var x2 = x
+ var y2 = y
+ if (x2 > right) x2 = 0
+ if (y2 > bottom) y2 = 0
+ val radius = Math.hypot(Math.max(x2, right - x2).toDouble(), Math.max(y2, bottom - y2).toDouble()).toInt()
+ val reveal = ViewAnimationUtils.createCircularReveal(v, x2, y2, 0f, radius.toFloat())
+ reveal.interpolator = DecelerateInterpolator(1f)
+ reveal.duration = duration
+ reveal.addListener(object : AnimatorListenerAdapter() {
+ override fun onAnimationStart(animation: Animator?) {
+ visible()
+ onStart?.invoke()
+ }
+
+ override fun onAnimationEnd(animation: Animator?) = onFinish?.invoke() ?: Unit
+ override fun onAnimationCancel(animation: Animator?) = onFinish?.invoke() ?: Unit
+ })
+ reveal.start()
+ }
+ })
+}
+
+fun View.circularReveal(x: Int = 0, y: Int = 0, offset: Long = 0L, radius: Float = -1.0f, duration: Long = 500L, onStart: (() -> Unit)? = null, onFinish: (() -> Unit)? = null) {
+ if (!isAttachedToWindow) {
+ onStart?.invoke()
+ visible()
+ onFinish?.invoke()
+ return
+ }
+ var r = radius
+ if (r < 0.0f) {
+ r = Math.max(Math.hypot(x.toDouble(), y.toDouble()), Math.hypot((width - x.toDouble()), (height - y.toDouble()))).toFloat()
+ }
+ val anim = ViewAnimationUtils.createCircularReveal(this, x, y, 0f, r).setDuration(duration)
+ anim.startDelay = offset
+ anim.addListener(object : AnimatorListenerAdapter() {
+ override fun onAnimationStart(animation: Animator?) {
+ visible()
+ onStart?.invoke()
+ }
+
+ override fun onAnimationEnd(animation: Animator?) = onFinish?.invoke() ?: Unit
+ override fun onAnimationCancel(animation: Animator?) = onFinish?.invoke() ?: Unit
+ })
+ anim.start()
+}
+
+fun View.fadeIn(offset: Long = 0L, duration: Long = 200L, onStart: (() -> Unit)? = null, onFinish: (() -> Unit)? = null) {
+ if (!isAttachedToWindow) {
+ onStart?.invoke()
+ visible()
+ onFinish?.invoke()
+ return
+ }
+ if (isAttachedToWindow) {
+ val anim = AnimationUtils.loadAnimation(context, android.R.anim.fade_in)
+ anim.startOffset = offset
+ anim.duration = duration
+ anim.setAnimationListener(object : Animation.AnimationListener {
+ override fun onAnimationRepeat(animation: Animation?) {}
+ override fun onAnimationEnd(animation: Animation?) = onFinish?.invoke() ?: Unit
+ override fun onAnimationStart(animation: Animation?) {
+ visible()
+ onStart?.invoke()
+ }
+ })
+ startAnimation(anim)
+ }
+}
+
+fun View.fadeOut(offset: Long = 0L, duration: Long = 200L, onStart: (() -> Unit)? = null, onFinish: (() -> Unit)? = null) {
+ if (!isAttachedToWindow) {
+ onStart?.invoke()
+ invisible()
+ onFinish?.invoke()
+ return
+ }
+ val anim = AnimationUtils.loadAnimation(context, android.R.anim.fade_out)
+ anim.startOffset = offset
+ anim.duration = duration
+ anim.setAnimationListener(object : Animation.AnimationListener {
+ override fun onAnimationRepeat(animation: Animation?) {}
+ override fun onAnimationEnd(animation: Animation?) {
+ invisible()
+ onFinish?.invoke()
+ }
+
+ override fun onAnimationStart(animation: Animation?) {
+ onStart?.invoke()
+ }
+ })
+ startAnimation(anim)
+}
+
+fun TextView.setTextWithFade(text: String, duration: Long = 200, onFinish: (() -> Unit)? = null) {
+ fadeOut(duration = duration, onFinish = {
+ setText(text)
+ fadeIn(duration = duration, onFinish = onFinish)
+ })
+}
+
+fun TextView.setTextWithFade(@StringRes textId: Int, duration: Long = 200, onFinish: (() -> Unit)? = null) = setTextWithFade(context.getString(textId), duration, onFinish)
+
diff --git a/library/src/main/kotlin/ca/allanwang/kau/utils/ColorUtils.kt b/library/src/main/kotlin/ca/allanwang/kau/utils/ColorUtils.kt
new file mode 100644
index 0000000..8e396c7
--- /dev/null
+++ b/library/src/main/kotlin/ca/allanwang/kau/utils/ColorUtils.kt
@@ -0,0 +1,183 @@
+package ca.allanwang.kau.utils
+
+import android.content.Context
+import android.content.res.ColorStateList
+import android.graphics.Color
+import android.graphics.PorterDuff
+import android.graphics.drawable.Drawable
+import android.os.Build
+import android.support.annotation.ColorInt
+import android.support.annotation.IntRange
+import android.support.v4.content.ContextCompat
+import android.support.v4.graphics.drawable.DrawableCompat
+import android.support.v7.widget.AppCompatEditText
+import android.widget.*
+import com.afollestad.materialdialogs.R
+
+/**
+ * Created by Allan Wang on 2017-06-08.
+ */
+fun Int.isColorDark(): Boolean
+ = (0.299 * Color.red(this) + 0.587 * Color.green(this) + 0.114 * Color.blue(this)) / 255.0 < 0.5
+
+fun Int.toHexString(withAlpha: Boolean = false, withHexPrefix:Boolean = true): String {
+ val hex = if (withAlpha) String.format("#%08X", this)
+ else String.format("#%06X", 0xFFFFFF and this)
+ return if (withHexPrefix) hex else hex.substring(1)
+}
+
+fun Int.isColorVisibleOn(@ColorInt color: Int, @IntRange(from = 0L, to = 255L) delta: Int = 8,
+ @IntRange(from = 0L, to = 255L) minAlpha: Int = 50): Boolean =
+ if (Color.alpha(this) < minAlpha) false
+ else (Math.abs(Color.red(this) - Color.red(color)) < delta
+ && Math.abs(Color.green(this) - Color.green(color)) < delta
+ && Math.abs(Color.blue(this) - Color.blue(color)) < delta)
+
+
+@ColorInt
+fun Context.getDisabledColor(): Int {
+ val primaryColor = resolveColor(android.R.attr.textColorPrimary)
+ val disabledColor = if (primaryColor.isColorDark()) Color.BLACK else Color.WHITE
+ return disabledColor.adjustAlpha(0.3f)
+}
+
+@ColorInt
+fun Int.adjustAlpha(factor: Float): Int {
+ val alpha = Math.round(Color.alpha(this) * factor)
+ return Color.argb(alpha, Color.red(this), Color.green(this), Color.blue(this))
+}
+
+@Throws(IllegalArgumentException::class)
+fun String.toColor(): Int {
+ val toParse: String
+ if (startsWith("#") && length == 4)
+ toParse = "#${this[1]}${this[1]}${this[2]}${this[2]}${this[3]}${this[3]}"
+ else
+ toParse = this
+ return Color.parseColor(toParse)
+}
+
+//Get ColorStateList
+fun Context.colorStateList(@ColorInt color: Int): ColorStateList {
+ val disabledColor = getDisabledColor()
+ return ColorStateList(arrayOf(intArrayOf(android.R.attr.state_enabled, -android.R.attr.state_checked),
+ intArrayOf(android.R.attr.state_enabled, android.R.attr.state_checked),
+ intArrayOf(-android.R.attr.state_enabled, -android.R.attr.state_checked),
+ intArrayOf(-android.R.attr.state_enabled, android.R.attr.state_checked)),
+ intArrayOf(this.resolveColor(R.attr.colorControlNormal), color, disabledColor, disabledColor))
+}
+
+/*
+ * Tint Helpers
+ * Kotlin tint bindings that start with 'tint' so it doesn't conflict with existing methods
+ * Largely based on MDTintHelper
+ * https://github.com/afollestad/material-dialogs/blob/master/core/src/main/java/com/afollestad/materialdialogs/internal/MDTintHelper.java
+ */
+fun RadioButton.tint(colors: ColorStateList) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ buttonTintList = colors
+ } else {
+ val radioDrawable = context.drawable(R.drawable.abc_btn_radio_material)
+ val d = DrawableCompat.wrap(radioDrawable)
+ DrawableCompat.setTintList(d, colors)
+ buttonDrawable = d
+ }
+}
+
+fun RadioButton.tint(@ColorInt color: Int) = tint(context.colorStateList(color))
+
+fun CheckBox.tint(colors: ColorStateList) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ buttonTintList = colors
+ } else {
+ val checkDrawable = context.drawable(R.drawable.abc_btn_check_material)
+ val drawable = DrawableCompat.wrap(checkDrawable)
+ DrawableCompat.setTintList(drawable, colors)
+ buttonDrawable = drawable
+ }
+}
+
+fun CheckBox.tint(@ColorInt color: Int) = tint(context.colorStateList(color))
+
+fun SeekBar.tint(@ColorInt color: Int) {
+ val s1 = ColorStateList.valueOf(color)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ thumbTintList = s1
+ progressTintList = s1
+ } else if (Build.VERSION.SDK_INT > Build.VERSION_CODES.GINGERBREAD_MR1) {
+ val progressDrawable = DrawableCompat.wrap(progressDrawable)
+ this.progressDrawable = progressDrawable
+ DrawableCompat.setTintList(progressDrawable, s1)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+ val thumbDrawable = DrawableCompat.wrap(thumb)
+ DrawableCompat.setTintList(thumbDrawable, s1)
+ thumb = thumbDrawable
+ }
+ } else {
+ val mode: PorterDuff.Mode = if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD_MR1)
+ PorterDuff.Mode.MULTIPLY else PorterDuff.Mode.SRC_IN
+ indeterminateDrawable?.setColorFilter(color, mode)
+ progressDrawable?.setColorFilter(color, mode)
+ }
+}
+
+fun ProgressBar.tint(@ColorInt color: Int, skipIndeterminate: Boolean = false) {
+ val sl = ColorStateList.valueOf(color)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ progressTintList = sl
+ secondaryProgressTintList = sl
+ if (!skipIndeterminate) indeterminateTintList = sl
+ } else {
+ val mode: PorterDuff.Mode = if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD_MR1)
+ PorterDuff.Mode.MULTIPLY else PorterDuff.Mode.SRC_IN
+ indeterminateDrawable?.setColorFilter(color, mode)
+ progressDrawable?.setColorFilter(color, mode)
+ }
+}
+
+fun Context.textColorStateList(@ColorInt color: Int): ColorStateList {
+ val states = arrayOf(
+ intArrayOf(-android.R.attr.state_enabled),
+ intArrayOf(-android.R.attr.state_pressed, -android.R.attr.state_focused),
+ intArrayOf()
+ )
+ val colors = intArrayOf(
+ resolveColor(R.attr.colorControlNormal),
+ resolveColor(R.attr.colorControlNormal),
+ color
+ )
+ return ColorStateList(states, colors)
+}
+
+fun EditText.tint(@ColorInt color: Int) {
+ val editTextColorStateList = context.textColorStateList(color)
+ if (this is AppCompatEditText) {
+ supportBackgroundTintList = editTextColorStateList
+ } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ backgroundTintList = editTextColorStateList
+ }
+ tintCursor(color)
+}
+
+fun EditText.tintCursor(@ColorInt color: Int) {
+ try {
+ val fCursorDrawableRes = TextView::class.java.getDeclaredField("mCursorDrawableRes")
+ fCursorDrawableRes.isAccessible = true
+ val mCursorDrawableRes = fCursorDrawableRes.getInt(this)
+ val fEditor = TextView::class.java.getDeclaredField("mEditor")
+ fEditor.isAccessible = true
+ val editor = fEditor.get(this)
+ val clazz = editor.javaClass
+ val fCursorDrawable = clazz.getDeclaredField("mCursorDrawable")
+ fCursorDrawable.isAccessible = true
+ val drawables: Array<Drawable> = Array(2, {
+ val drawable = ContextCompat.getDrawable(context, mCursorDrawableRes)
+ drawable.setColorFilter(color, PorterDuff.Mode.SRC_IN)
+ drawable
+ })
+ fCursorDrawable.set(editor, drawables)
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+
+} \ No newline at end of file
diff --git a/library/src/main/kotlin/ca/allanwang/kau/utils/Const.kt b/library/src/main/kotlin/ca/allanwang/kau/utils/Const.kt
new file mode 100644
index 0000000..944caa4
--- /dev/null
+++ b/library/src/main/kotlin/ca/allanwang/kau/utils/Const.kt
@@ -0,0 +1,6 @@
+package ca.allanwang.kau.utils
+
+/**
+ * Created by Allan Wang on 2017-06-08.
+ */
+const val ANDROID_NAMESPACE = "http://schemas.android.com/apk/res/android" \ No newline at end of file
diff --git a/library/src/main/kotlin/ca/allanwang/kau/utils/ContextUtils.kt b/library/src/main/kotlin/ca/allanwang/kau/utils/ContextUtils.kt
new file mode 100644
index 0000000..d27a609
--- /dev/null
+++ b/library/src/main/kotlin/ca/allanwang/kau/utils/ContextUtils.kt
@@ -0,0 +1,93 @@
+package ca.allanwang.kau.utils
+
+import android.app.Activity
+import android.content.Context
+import android.content.Intent
+import android.graphics.drawable.Drawable
+import android.net.ConnectivityManager
+import android.os.Handler
+import android.support.annotation.*
+import android.support.v4.content.ContextCompat
+import android.util.TypedValue
+import android.widget.Toast
+import ca.allanwang.kau.R
+import com.afollestad.materialdialogs.MaterialDialog
+import com.pitchedapps.frost.utils.ChangelogAdapter
+import com.pitchedapps.frost.utils.parse
+import java.util.*
+
+/**
+ * Created by Allan Wang on 2017-06-03.
+ */
+fun Activity.restart(extras: ((Intent) -> Unit)? = null) {
+ val i = Intent(this, this::class.java)
+ i.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION)
+ extras?.invoke(i)
+ startActivity(i)
+ overridePendingTransition(0, 0) //No transitions
+ finish()
+ overridePendingTransition(0, 0)
+}
+
+//Toast helpers
+fun Context.toast(@StringRes id: Int, duration: Int = Toast.LENGTH_LONG) = toast(this.string(id), duration)
+
+fun Context.toast(text: String, duration: Int = Toast.LENGTH_LONG) {
+ Toast.makeText(this, text, duration).show()
+}
+
+//Resource retrievers
+fun Context.string(@StringRes id: Int): String = getString(id)
+
+fun Context.string(@StringRes id: Int, fallback: String?): String? = if (id > 0) string(id) else fallback
+fun Context.string(holder: StringHolder?): String? = holder?.getString(this)
+fun Context.color(@ColorRes id: Int): Int = ContextCompat.getColor(this, id)
+fun Context.integer(@IntegerRes id: Int): Int = resources.getInteger(id)
+fun Context.dimen(@DimenRes id: Int): Float = resources.getDimension(id)
+fun Context.drawable(@DrawableRes id: Int): Drawable = ContextCompat.getDrawable(this, id)
+
+//Attr retrievers
+fun Context.resolveColor(@AttrRes attr: Int, fallback: Int = 0): Int {
+ val a = theme.obtainStyledAttributes(intArrayOf(attr))
+ try {
+ return a.getColor(0, fallback)
+ } finally {
+ a.recycle()
+ }
+}
+
+fun Context.resolveBoolean(@AttrRes attr: Int, fallback: Boolean = false): Boolean {
+ val a = theme.obtainStyledAttributes(intArrayOf(attr))
+ try {
+ return a.getBoolean(0, fallback)
+ } finally {
+ a.recycle()
+ }
+}
+
+fun Context.resolveString(@AttrRes attr: Int, fallback: String = ""): String {
+ val v = TypedValue()
+ return if (theme.resolveAttribute(attr, v, true)) v.string.toString() else fallback
+}
+
+fun Context.showChangelog(@XmlRes xmlRes: Int) {
+ val mHandler = Handler()
+ Thread(Runnable {
+ val items = parse(this, xmlRes)
+ mHandler.post(object : TimerTask() {
+ override fun run() {
+ MaterialDialog.Builder(this@showChangelog)
+ .title(R.string.kau_changelog)
+ .positiveText(R.string.kau_great)
+ .adapter(ChangelogAdapter(items), null)
+ .show()
+ }
+ })
+ }).start()
+}
+
+val isNetworkAvailable = fun Context.(): Boolean {
+ val connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
+ val activeNetworkInfo = connectivityManager.activeNetworkInfo
+ return activeNetworkInfo != null && activeNetworkInfo.isConnectedOrConnecting
+} \ No newline at end of file
diff --git a/library/src/main/kotlin/ca/allanwang/kau/utils/FragmentUtils.kt b/library/src/main/kotlin/ca/allanwang/kau/utils/FragmentUtils.kt
new file mode 100644
index 0000000..2ef1232
--- /dev/null
+++ b/library/src/main/kotlin/ca/allanwang/kau/utils/FragmentUtils.kt
@@ -0,0 +1,24 @@
+package ca.allanwang.kau.utils
+
+import android.os.Bundle
+import android.support.v4.app.Fragment
+
+/**
+ * Created by Allan Wang on 2017-05-29.
+ */
+
+private fun Fragment.bundle(): Bundle {
+ if (this.arguments == null)
+ this.arguments = Bundle()
+ return this.arguments
+}
+
+fun <T : Fragment> T.putString(key: String, value: String): T {
+ this.bundle().putString(key, value)
+ return this
+}
+
+fun <T : Fragment> T.putInt(key: String, value: Int): T {
+ this.bundle().putInt(key, value)
+ return this
+} \ No newline at end of file
diff --git a/library/src/main/kotlin/ca/allanwang/kau/utils/IIconUtils.kt b/library/src/main/kotlin/ca/allanwang/kau/utils/IIconUtils.kt
new file mode 100644
index 0000000..d47d133
--- /dev/null
+++ b/library/src/main/kotlin/ca/allanwang/kau/utils/IIconUtils.kt
@@ -0,0 +1,19 @@
+package ca.allanwang.kau.utils
+
+import android.content.Context
+import android.content.res.ColorStateList
+import android.graphics.Color
+import android.graphics.drawable.Drawable
+import android.support.annotation.ColorInt
+import com.mikepenz.iconics.IconicsDrawable
+import com.mikepenz.iconics.typeface.IIcon
+
+/**
+ * Created by Allan Wang on 2017-05-29.
+ */
+fun IIcon.toDrawable(c: Context, sizeDp: Int = 24, @ColorInt color: Int = Color.WHITE): Drawable {
+ val state = ColorStateList.valueOf(color)
+ val icon = IconicsDrawable(c).icon(this).sizeDp(sizeDp)
+ icon.setTintList(state)
+ return icon
+} \ No newline at end of file
diff --git a/library/src/main/kotlin/ca/allanwang/kau/utils/Kotterknife.kt b/library/src/main/kotlin/ca/allanwang/kau/utils/Kotterknife.kt
new file mode 100644
index 0000000..bd68b03
--- /dev/null
+++ b/library/src/main/kotlin/ca/allanwang/kau/utils/Kotterknife.kt
@@ -0,0 +1,137 @@
+package ca.allanwang.kau.utils
+
+/**
+ * Created by Allan Wang on 2017-05-29.
+ *
+ * Courtesy of Jake Wharton
+ *
+ * https://github.com/JakeWharton/kotterknife/blob/master/src/main/kotlin/kotterknife/ButterKnife.kt
+ */
+import android.app.Activity
+import android.app.Dialog
+import android.app.DialogFragment
+import android.app.Fragment
+import android.support.v7.widget.RecyclerView.ViewHolder
+import android.view.View
+import kotlin.properties.ReadOnlyProperty
+import kotlin.reflect.KProperty
+import android.support.v4.app.DialogFragment as SupportDialogFragment
+import android.support.v4.app.Fragment as SupportFragment
+
+public fun <V : View> View.bindView(id: Int)
+ : ReadOnlyProperty<View, V> = required(id, viewFinder)
+public fun <V : View> Activity.bindView(id: Int)
+ : ReadOnlyProperty<Activity, V> = required(id, viewFinder)
+public fun <V : View> Dialog.bindView(id: Int)
+ : ReadOnlyProperty<Dialog, V> = required(id, viewFinder)
+public fun <V : View> DialogFragment.bindView(id: Int)
+ : ReadOnlyProperty<DialogFragment, V> = required(id, viewFinder)
+public fun <V : View> android.support.v4.app.DialogFragment.bindView(id: Int)
+ : ReadOnlyProperty<android.support.v4.app.DialogFragment, V> = required(id, viewFinder)
+public fun <V : View> Fragment.bindView(id: Int)
+ : ReadOnlyProperty<Fragment, V> = required(id, viewFinder)
+public fun <V : View> android.support.v4.app.Fragment.bindView(id: Int)
+ : ReadOnlyProperty<android.support.v4.app.Fragment, V> = required(id, viewFinder)
+public fun <V : View> ViewHolder.bindView(id: Int)
+ : ReadOnlyProperty<ViewHolder, V> = required(id, viewFinder)
+
+public fun <V : View> View.bindOptionalView(id: Int)
+ : ReadOnlyProperty<View, V?> = optional(id, viewFinder)
+public fun <V : View> Activity.bindOptionalView(id: Int)
+ : ReadOnlyProperty<Activity, V?> = optional(id, viewFinder)
+public fun <V : View> Dialog.bindOptionalView(id: Int)
+ : ReadOnlyProperty<Dialog, V?> = optional(id, viewFinder)
+public fun <V : View> DialogFragment.bindOptionalView(id: Int)
+ : ReadOnlyProperty<DialogFragment, V?> = optional(id, viewFinder)
+public fun <V : View> android.support.v4.app.DialogFragment.bindOptionalView(id: Int)
+ : ReadOnlyProperty<android.support.v4.app.DialogFragment, V?> = optional(id, viewFinder)
+public fun <V : View> Fragment.bindOptionalView(id: Int)
+ : ReadOnlyProperty<Fragment, V?> = optional(id, viewFinder)
+public fun <V : View> android.support.v4.app.Fragment.bindOptionalView(id: Int)
+ : ReadOnlyProperty<android.support.v4.app.Fragment, V?> = optional(id, viewFinder)
+public fun <V : View> ViewHolder.bindOptionalView(id: Int)
+ : ReadOnlyProperty<ViewHolder, V?> = optional(id, viewFinder)
+
+public fun <V : View> View.bindViews(vararg ids: Int)
+ : ReadOnlyProperty<View, List<V>> = required(ids, viewFinder)
+public fun <V : View> Activity.bindViews(vararg ids: Int)
+ : ReadOnlyProperty<Activity, List<V>> = required(ids, viewFinder)
+public fun <V : View> Dialog.bindViews(vararg ids: Int)
+ : ReadOnlyProperty<Dialog, List<V>> = required(ids, viewFinder)
+public fun <V : View> DialogFragment.bindViews(vararg ids: Int)
+ : ReadOnlyProperty<DialogFragment, List<V>> = required(ids, viewFinder)
+public fun <V : View> android.support.v4.app.DialogFragment.bindViews(vararg ids: Int)
+ : ReadOnlyProperty<android.support.v4.app.DialogFragment, List<V>> = required(ids, viewFinder)
+public fun <V : View> Fragment.bindViews(vararg ids: Int)
+ : ReadOnlyProperty<Fragment, List<V>> = required(ids, viewFinder)
+public fun <V : View> android.support.v4.app.Fragment.bindViews(vararg ids: Int)
+ : ReadOnlyProperty<android.support.v4.app.Fragment, List<V>> = required(ids, viewFinder)
+public fun <V : View> ViewHolder.bindViews(vararg ids: Int)
+ : ReadOnlyProperty<ViewHolder, List<V>> = required(ids, viewFinder)
+
+public fun <V : View> View.bindOptionalViews(vararg ids: Int)
+ : ReadOnlyProperty<View, List<V>> = optional(ids, viewFinder)
+public fun <V : View> Activity.bindOptionalViews(vararg ids: Int)
+ : ReadOnlyProperty<Activity, List<V>> = optional(ids, viewFinder)
+public fun <V : View> Dialog.bindOptionalViews(vararg ids: Int)
+ : ReadOnlyProperty<Dialog, List<V>> = optional(ids, viewFinder)
+public fun <V : View> DialogFragment.bindOptionalViews(vararg ids: Int)
+ : ReadOnlyProperty<DialogFragment, List<V>> = optional(ids, viewFinder)
+public fun <V : View> android.support.v4.app.DialogFragment.bindOptionalViews(vararg ids: Int)
+ : ReadOnlyProperty<android.support.v4.app.DialogFragment, List<V>> = optional(ids, viewFinder)
+public fun <V : View> Fragment.bindOptionalViews(vararg ids: Int)
+ : ReadOnlyProperty<Fragment, List<V>> = optional(ids, viewFinder)
+public fun <V : View> android.support.v4.app.Fragment.bindOptionalViews(vararg ids: Int)
+ : ReadOnlyProperty<android.support.v4.app.Fragment, List<V>> = optional(ids, viewFinder)
+public fun <V : View> ViewHolder.bindOptionalViews(vararg ids: Int)
+ : ReadOnlyProperty<ViewHolder, List<V>> = optional(ids, viewFinder)
+
+private val View.viewFinder: View.(Int) -> View?
+ get() = { findViewById(it) }
+private val Activity.viewFinder: Activity.(Int) -> View?
+ get() = { findViewById(it) }
+private val Dialog.viewFinder: Dialog.(Int) -> View?
+ get() = { findViewById(it) }
+private val DialogFragment.viewFinder: DialogFragment.(Int) -> View?
+ get() = { dialog.findViewById(it) }
+private val android.support.v4.app.DialogFragment.viewFinder: android.support.v4.app.DialogFragment.(Int) -> View?
+ get() = { dialog.findViewById(it) }
+private val Fragment.viewFinder: Fragment.(Int) -> View?
+ get() = { view.findViewById(it) }
+private val android.support.v4.app.Fragment.viewFinder: android.support.v4.app.Fragment.(Int) -> View?
+ get() = { view!!.findViewById(it) }
+private val ViewHolder.viewFinder: ViewHolder.(Int) -> View?
+ get() = { itemView.findViewById(it) }
+
+private fun viewNotFound(id:Int, desc: KProperty<*>): Nothing =
+ throw IllegalStateException("View ID $id for '${desc.name}' not found.")
+
+@Suppress("UNCHECKED_CAST")
+private fun <T, V : View> required(id: Int, finder: T.(Int) -> View?)
+ = Lazy { t: T, desc -> t.finder(id) as V? ?: viewNotFound(id, desc) }
+
+@Suppress("UNCHECKED_CAST")
+private fun <T, V : View> optional(id: Int, finder: T.(Int) -> View?)
+ = Lazy { t: T, desc -> t.finder(id) as V? }
+
+@Suppress("UNCHECKED_CAST")
+private fun <T, V : View> required(ids: IntArray, finder: T.(Int) -> View?)
+ = Lazy { t: T, desc -> ids.map { t.finder(it) as V? ?: viewNotFound(it, desc) } }
+
+@Suppress("UNCHECKED_CAST")
+private fun <T, V : View> optional(ids: IntArray, finder: T.(Int) -> View?)
+ = Lazy { t: T, desc -> ids.map { t.finder(it) as V? }.filterNotNull() }
+
+// Like Kotlin's lazy delegate but the initializer gets the target and metadata passed to it
+private class Lazy<T, V>(private val initializer: (T, KProperty<*>) -> V) : ReadOnlyProperty<T, V> {
+ private object EMPTY
+ private var value: Any? = EMPTY
+
+ override fun getValue(thisRef: T, property: KProperty<*>): V {
+ if (value == EMPTY) {
+ value = initializer(thisRef, property)
+ }
+ @Suppress("UNCHECKED_CAST")
+ return value as V
+ }
+} \ No newline at end of file
diff --git a/library/src/main/kotlin/ca/allanwang/kau/utils/LazyResettable.kt b/library/src/main/kotlin/ca/allanwang/kau/utils/LazyResettable.kt
new file mode 100644
index 0000000..6e7e43e
--- /dev/null
+++ b/library/src/main/kotlin/ca/allanwang/kau/utils/LazyResettable.kt
@@ -0,0 +1,51 @@
+package ca.allanwang.kau.utils
+
+import java.io.Serializable
+import kotlin.reflect.KProperty
+
+/**
+ * Created by Allan Wang on 2017-05-30.
+ *
+ * Lazy delegate that can be invalidated if needed
+ * https://stackoverflow.com/a/37294840/4407321
+ */
+private object UNINITIALIZED
+
+fun <T : Any> lazyResettable(initializer: () -> T): LazyResettable<T> = LazyResettable<T>(initializer)
+
+class LazyResettable<T : Any>(private val initializer: () -> T, lock: Any? = null) : Lazy<T>, Serializable {
+ @Volatile private var _value: Any = UNINITIALIZED
+ private val lock = lock ?: this
+
+ fun invalidate() {
+ _value = UNINITIALIZED
+ }
+
+ override val value: T
+ get() {
+ val _v1 = _value
+ if (_v1 !== UNINITIALIZED)
+ @Suppress("UNCHECKED_CAST")
+ return _v1 as T
+
+ return synchronized(lock) {
+ val _v2 = _value
+ if (_v2 !== UNINITIALIZED) {
+ @Suppress("UNCHECKED_CAST")
+ _v2 as T
+ } else {
+ val typedValue = initializer()
+ _value = typedValue
+ typedValue
+ }
+ }
+ }
+
+ override fun isInitialized(): Boolean = _value !== UNINITIALIZED
+
+ override fun toString(): String = if (isInitialized()) value.toString() else "Lazy value not initialized yet."
+
+ operator fun setValue(any: Any, property: KProperty<*>, t: T) {
+ _value = t
+ }
+} \ No newline at end of file
diff --git a/library/src/main/kotlin/ca/allanwang/kau/utils/StringHolder.kt b/library/src/main/kotlin/ca/allanwang/kau/utils/StringHolder.kt
new file mode 100644
index 0000000..e70a2d1
--- /dev/null
+++ b/library/src/main/kotlin/ca/allanwang/kau/utils/StringHolder.kt
@@ -0,0 +1,22 @@
+package ca.allanwang.kau.utils
+
+import android.content.Context
+import android.support.annotation.StringRes
+
+/**
+ * Created by Allan Wang on 2017-06-08.
+ */
+class StringHolder {
+ var text: String? = null
+ var textRes: Int = 0
+
+ constructor(@StringRes textRes: Int) {
+ this.textRes = textRes
+ }
+
+ constructor(text: String) {
+ this.text = text
+ }
+
+ fun getString(context: Context) = context.string(textRes, text)
+} \ No newline at end of file
diff --git a/library/src/main/kotlin/ca/allanwang/kau/utils/Utils.kt b/library/src/main/kotlin/ca/allanwang/kau/utils/Utils.kt
new file mode 100644
index 0000000..c7ff9f2
--- /dev/null
+++ b/library/src/main/kotlin/ca/allanwang/kau/utils/Utils.kt
@@ -0,0 +1,11 @@
+package ca.allanwang.kau.utils
+
+import android.content.res.Resources
+
+/**
+ * Created by Allan Wang on 2017-05-28.
+ */
+
+val dpToPx = fun Int.(): Int = (this * Resources.getSystem().displayMetrics.density).toInt()
+
+val pxToDp = fun Int.(): Int = (this / Resources.getSystem().displayMetrics.density).toInt()
diff --git a/library/src/main/kotlin/ca/allanwang/kau/utils/ViewUtils.kt b/library/src/main/kotlin/ca/allanwang/kau/utils/ViewUtils.kt
new file mode 100644
index 0000000..a5204aa
--- /dev/null
+++ b/library/src/main/kotlin/ca/allanwang/kau/utils/ViewUtils.kt
@@ -0,0 +1,73 @@
+package ca.allanwang.kau.utils
+
+import android.content.res.ColorStateList
+import android.graphics.Color
+import android.support.annotation.ColorInt
+import android.support.annotation.ColorRes
+import android.support.annotation.StringRes
+import android.support.design.widget.Snackbar
+import android.support.v4.content.ContextCompat
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.ProgressBar
+import android.widget.TextView
+import com.mikepenz.iconics.typeface.IIcon
+
+
+/**
+ * Created by Allan Wang on 2017-05-31.
+ */
+fun <T : View> T.visible(): T {
+ visibility = View.VISIBLE
+ return this
+}
+
+fun <T : View> T.invisible(): T {
+ visibility = View.INVISIBLE
+ return this
+}
+
+fun <T : View> T.gone(): T {
+ visibility = View.GONE
+ return this
+}
+
+fun View.isVisible(): Boolean = visibility == View.VISIBLE
+fun View.isInvisible(): Boolean = visibility == View.INVISIBLE
+fun View.isGone(): Boolean = visibility == View.GONE
+
+fun View.matchParent() {
+ with(layoutParams) {
+ height = ViewGroup.LayoutParams.MATCH_PARENT
+ width = ViewGroup.LayoutParams.MATCH_PARENT
+ }
+}
+
+fun ProgressBar.tintRes(@ColorRes id: Int) = tint(ContextCompat.getColor(context, id))
+
+fun ProgressBar.tint(@ColorInt color: Int) {
+ val sl = ColorStateList.valueOf(color)
+ progressTintList = sl
+ secondaryProgressTintList = sl
+ indeterminateTintList = sl
+}
+
+fun View.snackbar(text: String, duration: Int = Snackbar.LENGTH_LONG, builder: (Snackbar) -> Unit = {}) {
+ val snackbar = Snackbar.make(this, text, duration)
+ builder.invoke(snackbar)
+ snackbar.show()
+}
+
+fun View.snackbar(@StringRes textId: Int, duration: Int = Snackbar.LENGTH_LONG, builder: (Snackbar) -> Unit = {})
+ = snackbar(context.string(textId), duration, builder)
+
+fun TextView.setTextIfValid(@StringRes id: Int) {
+ if (id > 0) text = context.string(id)
+}
+
+fun ImageView.setIcon(icon: IIcon?, sizeDp: Int = 24, @ColorInt color: Int = Color.WHITE) {
+ if (icon == null) return
+ setImageDrawable(icon.toDrawable(context, sizeDp, color))
+}
+
diff --git a/library/src/main/kotlin/ca/allanwang/kau/views/RippleCanvas.kt b/library/src/main/kotlin/ca/allanwang/kau/views/RippleCanvas.kt
new file mode 100644
index 0000000..2c44197
--- /dev/null
+++ b/library/src/main/kotlin/ca/allanwang/kau/views/RippleCanvas.kt
@@ -0,0 +1,79 @@
+package ca.allanwang.kau.views
+
+import android.animation.ValueAnimator
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.Paint
+import android.util.AttributeSet
+import android.view.View
+
+/**
+ * Created by Allan Wang on 2016-11-17.
+ *
+ *
+ * Canvas drawn ripples that keep the previous color
+ * Extends to view dimensions
+ * Supports multiple ripples from varying locations
+ */
+class RippleCanvas @JvmOverloads constructor(
+ context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
+) : View(context, attrs, defStyleAttr) {
+ private val paint: Paint = Paint()
+ private var baseColor = Color.TRANSPARENT
+ private val ripples: MutableList<Ripple> = mutableListOf()
+
+ init {
+ paint.isAntiAlias = true
+ paint.style = Paint.Style.FILL
+ }
+
+ override fun onDraw(canvas: Canvas) {
+ canvas.drawColor(baseColor)
+ val itr = ripples.iterator()
+ while (itr.hasNext()) {
+ val r = itr.next()
+ paint.color = r.color
+ canvas.drawCircle(r.x, r.y, r.radius, paint)
+ if (r.radius == r.maxRadius) {
+ itr.remove()
+ baseColor = r.color
+ }
+ }
+ }
+
+ fun ripple(color: Int, startX: Float = 0f, startY: Float = 0f, duration: Int = 1000) {
+ var x = startX
+ var y = startY
+ val w = width.toFloat()
+ val h = height.toFloat()
+ if (x == MIDDLE)
+ x = w / 2
+ else if (x > w) x = 0f
+ if (y == MIDDLE)
+ y = h / 2
+ else if (y > h) y = 0f
+ val maxRadius = Math.hypot(Math.max(x, w - x).toDouble(), Math.max(y, h - y).toDouble()).toFloat()
+ val ripple = Ripple(color, x, y, 0f, maxRadius)
+ ripples.add(ripple)
+ val animator = ValueAnimator.ofFloat(0f, maxRadius)
+ animator.duration = duration.toLong()
+ animator.addUpdateListener { animation ->
+ ripple.radius = animation.animatedValue as Float
+ invalidate()
+ }
+ animator.start()
+ }
+
+ fun set(color: Int) {
+ baseColor = color
+ ripples.clear()
+ invalidate()
+ }
+
+ internal class Ripple(val color: Int, val x: Float, val y: Float, var radius: Float, val maxRadius: Float)
+
+ companion object {
+ const val MIDDLE = -1.0f
+ }
+}
diff --git a/library/src/main/res/drawable/kau_transparent.xml b/library/src/main/res/drawable/kau_transparent.xml
new file mode 100644
index 0000000..399a46b
--- /dev/null
+++ b/library/src/main/res/drawable/kau_transparent.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="@android:color/transparent" />
+</shape> \ No newline at end of file
diff --git a/library/src/main/res/layout/kau_changelog_content.xml b/library/src/main/res/layout/kau_changelog_content.xml
new file mode 100644
index 0000000..90cca40
--- /dev/null
+++ b/library/src/main/res/layout/kau_changelog_content.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:paddingBottom="8.4sp"
+ android:paddingLeft="@dimen/kau_dialog_margin"
+ android:paddingRight="@dimen/kau_dialog_margin">
+
+ <!--padding bottom is 14sp * 0.6-->
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:lineSpacingMultiplier="1.6"
+ android:paddingRight="5dp"
+ android:text="@string/kau_u2022"
+ android:textAppearance="@style/TextAppearance.AppCompat.Small" />
+
+ <TextView
+ android:id="@+id/kau_changelog_text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:lineSpacingMultiplier="1.6"
+ android:textAppearance="@style/TextAppearance.AppCompat.Small" />
+
+</LinearLayout> \ No newline at end of file
diff --git a/library/src/main/res/layout/kau_changelog_title.xml b/library/src/main/res/layout/kau_changelog_title.xml
new file mode 100644
index 0000000..e885e02
--- /dev/null
+++ b/library/src/main/res/layout/kau_changelog_title.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/kau_changelog_text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:lineSpacingMultiplier="1.6"
+ android:paddingBottom="@dimen/kau_dialog_margin_bottom"
+ android:paddingLeft="@dimen/kau_dialog_margin"
+ android:paddingRight="@dimen/kau_dialog_margin"
+ android:paddingTop="@dimen/kau_dialog_margin_bottom"
+ android:textAppearance="@style/TextAppearance.AppCompat.Medium"
+ android:textStyle="bold" />
+
diff --git a/library/src/main/res/layout/kau_preference.xml b/library/src/main/res/layout/kau_preference.xml
new file mode 100644
index 0000000..265015a
--- /dev/null
+++ b/library/src/main/res/layout/kau_preference.xml
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="?android:attr/selectableItemBackground"
+ android:baselineAligned="false"
+ android:clipToPadding="false"
+ android:gravity="center_vertical"
+ android:minHeight="?android:attr/listPreferredItemHeightSmall"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart">
+
+ <LinearLayout
+ android:id="@+id/kau_pref_icon_frame"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="-4dp"
+ android:gravity="start|center_vertical"
+ android:minWidth="60dp"
+ android:orientation="horizontal"
+ android:paddingBottom="4dp"
+ android:paddingEnd="12dp"
+ android:paddingTop="4dp">
+
+ <ImageView
+ android:id="@+id/kau_pref_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:maxHeight="48dp"
+ android:maxWidth="48dp" />
+ </LinearLayout>
+
+ <RelativeLayout
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:paddingBottom="16dp"
+ android:paddingTop="16dp">
+
+ <TextView
+ android:id="@+id/kau_pref_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:ellipsize="marquee"
+ android:textColor="?android:attr/textColorPrimary"
+ android:textAppearance="?android:attr/textAppearanceListItem" />
+
+ <TextView
+ android:id="@+id/kau_pref_desc"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignStart="@id/kau_pref_title"
+ android:layout_below="@id/kau_pref_title"
+ android:ellipsize="end"
+ android:maxLines="10"
+ android:textAppearance="?android:attr/textAppearanceListItemSecondary"
+ android:textColor="?android:attr/textColorSecondary" />
+
+ </RelativeLayout>
+
+ <LinearLayout android:id="@+id/kau_pref_inner_frame"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:gravity="end|center_vertical"
+ android:paddingLeft="16dp"
+ android:orientation="vertical" />
+
+</LinearLayout> \ No newline at end of file
diff --git a/library/src/main/res/layout/kau_preference_checkbox.xml b/library/src/main/res/layout/kau_preference_checkbox.xml
new file mode 100644
index 0000000..1f2807b
--- /dev/null
+++ b/library/src/main/res/layout/kau_preference_checkbox.xml
@@ -0,0 +1,7 @@
+<CheckBox xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/kau_pref_checkbox"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:focusable="false"
+ android:clickable="false"
+ android:background="@null" /> \ No newline at end of file
diff --git a/library/src/main/res/layout/kau_preference_header.xml b/library/src/main/res/layout/kau_preference_header.xml
new file mode 100644
index 0000000..5deece3
--- /dev/null
+++ b/library/src/main/res/layout/kau_preference_header.xml
@@ -0,0 +1,10 @@
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/kau_pref_title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="8dip"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingRight"
+ android:paddingStart="?android:attr/listPreferredItemPaddingLeft"
+ android:paddingTop="16dip"
+ android:textColor="?android:attr/colorAccent"
+ android:textSize="14sp" /> \ No newline at end of file
diff --git a/library/src/main/res/values/dimens.xml b/library/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..85ef703
--- /dev/null
+++ b/library/src/main/res/values/dimens.xml
@@ -0,0 +1,15 @@
+<resources>
+ <!-- Default screen margins, per the Android Design guidelines. -->
+ <dimen name="kau_activity_horizontal_margin">16dp</dimen>
+ <dimen name="kau_activity_vertical_margin">16dp</dimen>
+ <dimen name="kau_dialog_margin">24dp</dimen>
+ <dimen name="kau_dialog_margin_bottom">16dp</dimen>
+
+ <dimen name="kau_fab_margin">16dp</dimen>
+ <dimen name="kau_appbar_padding_top">8dp</dimen>
+ <dimen name="kau_splash_logo">16dp</dimen>
+ <dimen name="kau_progress_bar_height">1dip</dimen>
+ <dimen name="kau_account_image_size">100dp</dimen>
+ <dimen name="kau_color_circle_size">56dp</dimen>
+
+</resources>
diff --git a/library/src/main/res/values/ids.xml b/library/src/main/res/values/ids.xml
new file mode 100644
index 0000000..36be2b2
--- /dev/null
+++ b/library/src/main/res/values/ids.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <item name="kau_item_account" type="id" />
+ <item name="kau_item_pref_header" type="id" />
+ <item name="kau_item_pref_text" type="id" />
+ <item name="kau_item_pref_checkbox" type="id" />
+ <item name="kau_item_pref_color_picker" type="id" />
+</resources> \ No newline at end of file
diff --git a/library/src/main/res/values/strings.xml b/library/src/main/res/values/strings.xml
new file mode 100644
index 0000000..98dc0a1
--- /dev/null
+++ b/library/src/main/res/values/strings.xml
@@ -0,0 +1,15 @@
+<resources>
+ <string name="kau_changelog">Changelog</string>
+ <string name="kau_great">Great</string>
+ <string name="kau_error">Error</string>
+ <string name="kau_back">Back</string>
+ <string name="kau_cancel">Cancel</string>
+ <string name="kau_done">Done</string>
+ <string name="kau_u2022">•</string>
+ <string name="kau_color_picker">Color Picker</string>
+
+ <!--Color Picker-->
+ <string name="kau_md_custom">Custom</string>
+ <string name="kau_md_presets">Presets</string>
+ <string name="kau_md_color_palette">Color Palette</string>
+</resources>
diff --git a/library/src/main/res/xml/kau_changelog.xml b/library/src/main/res/xml/kau_changelog.xml
new file mode 100644
index 0000000..e570995
--- /dev/null
+++ b/library/src/main/res/xml/kau_changelog.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <!--This is a template-->
+
+ <!--
+ <version title="v"/>
+ <item text="" />
+ -->
+
+ <version title="v0.1" />
+ <item text="Initial Changelog" />
+ <item text="" />
+</resources> \ No newline at end of file
diff --git a/library/src/test/java/ca/allanwang/kprefs/library/ExampleUnitTest.java b/library/src/test/java/ca/allanwang/kprefs/library/ExampleUnitTest.java
new file mode 100644
index 0000000..12e478b
--- /dev/null
+++ b/library/src/test/java/ca/allanwang/kprefs/library/ExampleUnitTest.java
@@ -0,0 +1,17 @@
+package ca.allanwang.kprefs.library;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
+ */
+public class ExampleUnitTest {
+ @Test
+ public void addition_isCorrect() throws Exception {
+ assertEquals(4, 2 + 2);
+ }
+} \ No newline at end of file
diff --git a/sample/.gitignore b/sample/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/sample/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/sample/build.gradle b/sample/build.gradle
new file mode 100644
index 0000000..9a349e6
--- /dev/null
+++ b/sample/build.gradle
@@ -0,0 +1,38 @@
+apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android'
+
+android {
+ compileSdkVersion Integer.parseInt(project.TARGET_SDK)
+ buildToolsVersion project.BUILD_TOOLS
+
+ defaultConfig {
+ applicationId "${project.APP_GROUP}." + project.APP_ID.toLowerCase() + ".sample"
+ minSdkVersion Integer.parseInt(project.MIN_SDK)
+ targetSdkVersion Integer.parseInt(project.TARGET_SDK)
+ versionCode Integer.parseInt(project.VERSION_CODE)
+ versionName project.VERSION_NAME
+ testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+
+ sourceSets {
+ main.java.srcDirs += 'src/main/kotlin'
+ test.java.srcDirs += 'src/test/kotlin'
+ }
+}
+
+dependencies {
+ compile fileTree(dir: 'libs', include: ['*.jar'])
+ compile project(':library')
+ androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
+ exclude group: 'com.android.support', module: 'support-annotations'
+ })
+ testCompile 'junit:junit:4.12'
+ compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
+ testCompile "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version"
+}
diff --git a/sample/proguard-rules.pro b/sample/proguard-rules.pro
new file mode 100644
index 0000000..2b766bc
--- /dev/null
+++ b/sample/proguard-rules.pro
@@ -0,0 +1,25 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in C:\Users\User7681\AppData\Local\Android\Sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/sample/src/androidTest/java/ca/allanwang/kprefs/ExampleInstrumentedTest.java b/sample/src/androidTest/java/ca/allanwang/kprefs/ExampleInstrumentedTest.java
new file mode 100644
index 0000000..e3d17e9
--- /dev/null
+++ b/sample/src/androidTest/java/ca/allanwang/kprefs/ExampleInstrumentedTest.java
@@ -0,0 +1,26 @@
+package ca.allanwang.kprefs;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+/**
+ * Instrumentation test, which will execute on an Android device.
+ *
+ * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+ @Test
+ public void useAppContext() throws Exception {
+ // Context of the app under test.
+ Context appContext = InstrumentationRegistry.getTargetContext();
+
+ assertEquals("ca.allanwang.kprefs", appContext.getPackageName());
+ }
+}
diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..4b31b50
--- /dev/null
+++ b/sample/src/main/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="ca.allanwang.kau.sample">
+
+ <application
+ android:name=".SampleApp"
+ android:allowBackup="true"
+ android:icon="@mipmap/ic_launcher"
+ android:label="@string/app_name"
+ android:roundIcon="@mipmap/ic_launcher_round"
+ android:supportsRtl="true"
+ android:theme="@style/AppTheme">
+ <activity
+ android:name=".MainActivity"
+ android:label="@string/title_activity_main">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest> \ No newline at end of file
diff --git a/sample/src/main/kotlin/ca/allanwang/kau/sample/KPrefSample.kt b/sample/src/main/kotlin/ca/allanwang/kau/sample/KPrefSample.kt
new file mode 100644
index 0000000..d63e533
--- /dev/null
+++ b/sample/src/main/kotlin/ca/allanwang/kau/sample/KPrefSample.kt
@@ -0,0 +1,16 @@
+package ca.allanwang.kau.sample
+
+import android.graphics.Color
+import ca.allanwang.kau.kpref.KPref
+import ca.allanwang.kau.kpref.kpref
+
+/**
+ * Created by Allan Wang on 2017-06-07.
+ */
+object KPrefSample : KPref() {
+ var textColor: Int by kpref("TEXT_COLOR", Color.WHITE)
+ var bgColor: Int by kpref("BG_COLOR", Color.BLACK)
+ var check1: Boolean by kpref("check1", true)
+ var check2: Boolean by kpref("check2", false)
+ var check3: Boolean by kpref("check3", false)
+} \ No newline at end of file
diff --git a/sample/src/main/kotlin/ca/allanwang/kau/sample/MainActivity.kt b/sample/src/main/kotlin/ca/allanwang/kau/sample/MainActivity.kt
new file mode 100644
index 0000000..c8207a3
--- /dev/null
+++ b/sample/src/main/kotlin/ca/allanwang/kau/sample/MainActivity.kt
@@ -0,0 +1,53 @@
+package ca.allanwang.kau.sample
+
+import android.os.Bundle
+import android.support.v7.app.AppCompatActivity
+import android.support.v7.widget.RecyclerView
+import android.view.Menu
+import android.view.MenuItem
+import ca.allanwang.kau.kpref.setKPrefAdapter
+import ca.allanwang.kau.utils.showChangelog
+
+class MainActivity : AppCompatActivity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ val recycler = RecyclerView(this)
+// recycler.matchParent()
+ setContentView(recycler)
+ recycler.setKPrefAdapter {
+ header(R.string.header)
+ checkbox(title = R.string.checkbox_1, description = R.string.desc,
+ getter = { KPrefSample.check1 }, setter = { KPrefSample.check1 = it })
+ checkbox(title = R.string.checkbox_2,
+ getter = { KPrefSample.check2 }, setter = { KPrefSample.check2 = it })
+ checkbox(title = R.string.checkbox_3, enabled = false,
+ getter = { KPrefSample.check3 }, setter = { KPrefSample.check3 = it })
+ colorPicker(title = R.string.text_color,
+ getter = { KPrefSample.textColor }, setter = { KPrefSample.textColor = it })
+ }
+ }
+
+ override fun onCreateOptionsMenu(menu: Menu): Boolean {
+ menuInflater.inflate(R.menu.menu_main, menu)
+ return true
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ when (item.itemId) {
+ R.id.action_settings -> {
+
+ }
+ R.id.action_changelog -> showChangelog(R.xml.kau_changelog)
+ R.id.action_call -> {
+ }
+ R.id.action_db -> {
+ }
+ R.id.action_restart -> {
+ }
+ else -> return super.onOptionsItemSelected(item)
+ }
+ return true
+ }
+
+}
diff --git a/sample/src/main/kotlin/ca/allanwang/kau/sample/SampleApp.kt b/sample/src/main/kotlin/ca/allanwang/kau/sample/SampleApp.kt
new file mode 100644
index 0000000..7fdc83d
--- /dev/null
+++ b/sample/src/main/kotlin/ca/allanwang/kau/sample/SampleApp.kt
@@ -0,0 +1,15 @@
+package ca.allanwang.kau.sample
+
+import android.app.Application
+import timber.log.Timber
+
+/**
+ * Created by Allan Wang on 2017-06-08.
+ */
+class SampleApp : Application() {
+ override fun onCreate() {
+ super.onCreate()
+ Timber.plant(Timber.DebugTree())
+ KPrefSample.initialize(this, "pref_sample")
+ }
+} \ No newline at end of file
diff --git a/sample/src/main/res/layout/activity_main.xml b/sample/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..7254add
--- /dev/null
+++ b/sample/src/main/res/layout/activity_main.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ tools:context="ca.allanwang.kau.sample.MainActivity">
+
+</android.support.constraint.ConstraintLayout>
diff --git a/sample/src/main/res/menu/menu_main.xml b/sample/src/main/res/menu/menu_main.xml
new file mode 100644
index 0000000..5562ea4
--- /dev/null
+++ b/sample/src/main/res/menu/menu_main.xml
@@ -0,0 +1,31 @@
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ tools:context="com.pitchedapps.myapplication.MainActivity">
+ <item
+ android:id="@+id/action_settings"
+ android:orderInCategory="100"
+ android:title="Settings"
+ app:showAsAction="never" />
+ <item
+ android:id="@+id/action_changelog"
+ android:orderInCategory="200"
+ android:title="@string/kau_changelog"
+ app:showAsAction="never" />
+ <item
+ android:id="@+id/action_restart"
+ android:orderInCategory="220"
+ android:title="Restart"
+ app:showAsAction="never" />
+ <item
+ android:id="@+id/action_call"
+ android:orderInCategory="300"
+ android:title="Call"
+ app:showAsAction="never" />
+ <item
+ android:id="@+id/action_db"
+ android:orderInCategory="400"
+ android:title="DB"
+ app:showAsAction="never" />
+</menu>
+
diff --git a/sample/src/main/res/mipmap-hdpi/ic_launcher.png b/sample/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..cde69bc
--- /dev/null
+++ b/sample/src/main/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/sample/src/main/res/mipmap-hdpi/ic_launcher_round.png b/sample/src/main/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 0000000..9a078e3
--- /dev/null
+++ b/sample/src/main/res/mipmap-hdpi/ic_launcher_round.png
Binary files differ
diff --git a/sample/src/main/res/mipmap-mdpi/ic_launcher.png b/sample/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..c133a0c
--- /dev/null
+++ b/sample/src/main/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/sample/src/main/res/mipmap-mdpi/ic_launcher_round.png b/sample/src/main/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 0000000..efc028a
--- /dev/null
+++ b/sample/src/main/res/mipmap-mdpi/ic_launcher_round.png
Binary files differ
diff --git a/sample/src/main/res/mipmap-xhdpi/ic_launcher.png b/sample/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..bfa42f0
--- /dev/null
+++ b/sample/src/main/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/sample/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/sample/src/main/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..3af2608
--- /dev/null
+++ b/sample/src/main/res/mipmap-xhdpi/ic_launcher_round.png
Binary files differ
diff --git a/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png b/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..324e72c
--- /dev/null
+++ b/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/sample/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/sample/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..9bec2e6
--- /dev/null
+++ b/sample/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
Binary files differ
diff --git a/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..aee44e1
--- /dev/null
+++ b/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/sample/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/sample/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..34947cd
--- /dev/null
+++ b/sample/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
Binary files differ
diff --git a/sample/src/main/res/values/colors.xml b/sample/src/main/res/values/colors.xml
new file mode 100644
index 0000000..3ab3e9c
--- /dev/null
+++ b/sample/src/main/res/values/colors.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <color name="colorPrimary">#3F51B5</color>
+ <color name="colorPrimaryDark">#303F9F</color>
+ <color name="colorAccent">#FF4081</color>
+</resources>
diff --git a/sample/src/main/res/values/strings.xml b/sample/src/main/res/values/strings.xml
new file mode 100644
index 0000000..ea9712f
--- /dev/null
+++ b/sample/src/main/res/values/strings.xml
@@ -0,0 +1,10 @@
+<resources>
+ <string name="app_name">KPrefs</string>
+ <string name="title_activity_main">MainActivity</string>
+ <string name="header">This is a header</string>
+ <string name="desc">This is a description</string>
+ <string name="checkbox_1">Checkbox 1</string>
+ <string name="checkbox_2">Checkbox 2</string>
+ <string name="checkbox_3">Checkbox 3</string>
+ <string name="text_color">Text Color</string>
+</resources>
diff --git a/sample/src/main/res/values/styles.xml b/sample/src/main/res/values/styles.xml
new file mode 100644
index 0000000..daa2a5c
--- /dev/null
+++ b/sample/src/main/res/values/styles.xml
@@ -0,0 +1,11 @@
+<resources>
+
+ <!-- Base application theme. -->
+ <style name="AppTheme" parent="Theme.AppCompat">
+ <!-- Customize your theme here. -->
+ <item name="colorPrimary">@color/colorPrimary</item>
+ <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
+ <item name="colorAccent">@color/colorAccent</item>
+ </style>
+
+</resources>
diff --git a/sample/src/main/res/xml/changelog.xml b/sample/src/main/res/xml/changelog.xml
new file mode 100644
index 0000000..7569fb2
--- /dev/null
+++ b/sample/src/main/res/xml/changelog.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <!--
+ <version title="v"/>
+ <item text="" />
+ -->
+
+ <version title="v0.1" />
+ <item text="Initial Changelog" />
+ <item text="" />
+ <item text="" />
+ <item text="" />
+ <item text="" />
+ <item text="" />
+ <item text="" />
+ <item text="" />
+ <item text="" />
+ <item text="" />
+</resources> \ No newline at end of file
diff --git a/sample/src/test/java/ca/allanwang/kprefs/ExampleUnitTest.java b/sample/src/test/java/ca/allanwang/kprefs/ExampleUnitTest.java
new file mode 100644
index 0000000..4c029fd
--- /dev/null
+++ b/sample/src/test/java/ca/allanwang/kprefs/ExampleUnitTest.java
@@ -0,0 +1,17 @@
+package ca.allanwang.kprefs;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
+ */
+public class ExampleUnitTest {
+ @Test
+ public void addition_isCorrect() throws Exception {
+ assertEquals(4, 2 + 2);
+ }
+} \ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..52baf7e
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1 @@
+include ':sample', ':library'