diff options
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 Binary files differnew file mode 100644 index 0000000..13372ae --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.jar 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 @@ -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 Binary files differnew file mode 100644 index 0000000..cde69bc --- /dev/null +++ b/sample/src/main/res/mipmap-hdpi/ic_launcher.png diff --git a/sample/src/main/res/mipmap-hdpi/ic_launcher_round.png b/sample/src/main/res/mipmap-hdpi/ic_launcher_round.png Binary files differnew file mode 100644 index 0000000..9a078e3 --- /dev/null +++ b/sample/src/main/res/mipmap-hdpi/ic_launcher_round.png diff --git a/sample/src/main/res/mipmap-mdpi/ic_launcher.png b/sample/src/main/res/mipmap-mdpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000..c133a0c --- /dev/null +++ b/sample/src/main/res/mipmap-mdpi/ic_launcher.png diff --git a/sample/src/main/res/mipmap-mdpi/ic_launcher_round.png b/sample/src/main/res/mipmap-mdpi/ic_launcher_round.png Binary files differnew file mode 100644 index 0000000..efc028a --- /dev/null +++ b/sample/src/main/res/mipmap-mdpi/ic_launcher_round.png diff --git a/sample/src/main/res/mipmap-xhdpi/ic_launcher.png b/sample/src/main/res/mipmap-xhdpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000..bfa42f0 --- /dev/null +++ b/sample/src/main/res/mipmap-xhdpi/ic_launcher.png diff --git a/sample/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/sample/src/main/res/mipmap-xhdpi/ic_launcher_round.png Binary files differnew file mode 100644 index 0000000..3af2608 --- /dev/null +++ b/sample/src/main/res/mipmap-xhdpi/ic_launcher_round.png diff --git a/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png b/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000..324e72c --- /dev/null +++ b/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png diff --git a/sample/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/sample/src/main/res/mipmap-xxhdpi/ic_launcher_round.png Binary files differnew file mode 100644 index 0000000..9bec2e6 --- /dev/null +++ b/sample/src/main/res/mipmap-xxhdpi/ic_launcher_round.png diff --git a/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000..aee44e1 --- /dev/null +++ b/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/sample/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/sample/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png Binary files differnew file mode 100644 index 0000000..34947cd --- /dev/null +++ b/sample/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png 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' |