Components refactor
|
|
@ -1,30 +1,8 @@
|
|||
# Built application files
|
||||
*.apk
|
||||
*.ap_
|
||||
|
||||
# Files for the Dalvik VM
|
||||
*.dex
|
||||
|
||||
# Java class files
|
||||
*.class
|
||||
|
||||
# Generated files
|
||||
bin/
|
||||
gen/
|
||||
|
||||
# Gradle files
|
||||
.gradle/
|
||||
build/
|
||||
/*/build/
|
||||
|
||||
# Local configuration file (sdk path, etc)
|
||||
local.properties
|
||||
|
||||
# Log Files
|
||||
*.log
|
||||
|
||||
.gradle
|
||||
.idea
|
||||
.DS_Store
|
||||
/captures
|
||||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
.externalNativeBuild
|
||||
|
|
|
|||
54
build.gradle
|
|
@ -1,28 +1,36 @@
|
|||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
android {
|
||||
compileSdkVersion compileSdk
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.2.60'
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 16
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.1.3'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api project(':libraries:core')
|
||||
|
||||
compileOnly "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
|
||||
compileOnly "com.android.support:appcompat-v7:$supportLibraryVersion"
|
||||
compileOnly "com.android.support:design:$supportLibraryVersion"
|
||||
compileOnly "com.android.support:recyclerview-v7:$supportLibraryVersion"
|
||||
|
||||
compileOnly "io.reactivex.rxjava2:rxandroid:$rxAndroidVersion"
|
||||
compileOnly "io.reactivex.rxjava2:rxjava:$rxJavaVersion"
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
||||
|
||||
ext {
|
||||
versions = [
|
||||
compileSdk : 27,
|
||||
minSdk : 19,
|
||||
supportLibrary: '27.1.1',
|
||||
navigation : '1.0.0-alpha04',
|
||||
lifecycle : '1.1.1',
|
||||
dagger : '2.16',
|
||||
retrofit : '2.4.0',
|
||||
rxJava : '2.1.17',
|
||||
rxAndroid : '2.0.2'
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
# 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
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
#Sun Aug 05 23:37:20 MSK 2018
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
|
||||
|
|
@ -0,0 +1,172 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
esac
|
||||
|
||||
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" -a "$nonstop" = "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
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=$(save "$@")
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
||||
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
||||
cd "$(dirname "$0")"
|
||||
fi
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
@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
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS=
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
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 Windows variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_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=%*
|
||||
|
||||
: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
|
||||
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
compileSdkVersion versions.compileSdk
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 16
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "com.android.support:support-annotations:$versions.supportLibrary"
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
<manifest package="ru.touchin.roboswag.core.log" />
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov)
|
||||
*
|
||||
* This file is part of RoboSwag library.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package ru.touchin.roboswag.core.log;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 13/11/2015.
|
||||
* Simple {@link LogProcessor} implementation which is logging messages to console (logcat).
|
||||
*/
|
||||
public class ConsoleLogProcessor extends LogProcessor {
|
||||
|
||||
private static final int MAX_LOG_LENGTH = 4000;
|
||||
|
||||
public ConsoleLogProcessor(@NonNull final LcLevel lclevel) {
|
||||
super(lclevel);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private String normalize(@NonNull final String message) {
|
||||
return message.replace("\r\n", "\n").replace("\0", "");
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings({"WrongConstant", "LogConditional"})
|
||||
//WrongConstant, LogConditional: level.getPriority() is not wrong constant!
|
||||
public void processLogMessage(@NonNull final LcGroup group, @NonNull final LcLevel level,
|
||||
@NonNull final String tag, @NonNull final String message, @Nullable final Throwable throwable) {
|
||||
final String messageToLog = normalize(message + (throwable != null ? '\n' + Log.getStackTraceString(throwable) : ""));
|
||||
final int length = messageToLog.length();
|
||||
for (int i = 0; i < length; i++) {
|
||||
int newline = messageToLog.indexOf('\n', i);
|
||||
newline = newline != -1 ? newline : length;
|
||||
do {
|
||||
final int end = Math.min(newline, i + MAX_LOG_LENGTH);
|
||||
Log.println(level.getPriority(), tag, messageToLog.substring(i, end));
|
||||
i = end;
|
||||
}
|
||||
while (i < newline);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,277 @@
|
|||
/*
|
||||
* Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov)
|
||||
*
|
||||
* This file is part of RoboSwag library.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package ru.touchin.roboswag.core.log;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import ru.touchin.roboswag.core.utils.ShouldNotHappenException;
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 13/11/2015.
|
||||
* General logging utility of RoboSwag library.
|
||||
* You can initialize {@link LogProcessor} to intercept log messages and make decision how to show them.
|
||||
* Also you can specify assertions behavior to manually make application more stable in production but intercept illegal states in some
|
||||
* third-party tool to fix them later but not crash in production.
|
||||
*/
|
||||
@SuppressWarnings({"checkstyle:methodname", "PMD.ShortMethodName", "PMD.ShortClassName"})
|
||||
//MethodNameCheck,ShortMethodName: log methods better be 1-symbol
|
||||
public final class Lc {
|
||||
|
||||
public static final LcGroup GENERAL_LC_GROUP = new LcGroup("GENERAL");
|
||||
|
||||
public static final int STACK_TRACE_CODE_DEPTH;
|
||||
|
||||
private static boolean crashOnAssertions = true;
|
||||
@NonNull
|
||||
private static LogProcessor logProcessor = new ConsoleLogProcessor(LcLevel.ERROR);
|
||||
|
||||
static {
|
||||
final StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
|
||||
int stackDepth;
|
||||
for (stackDepth = 0; stackDepth < stackTrace.length; stackDepth++) {
|
||||
if (stackTrace[stackDepth].getClassName().equals(Lc.class.getName())) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
STACK_TRACE_CODE_DEPTH = stackDepth + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flag to crash application or pass it to {@link LogProcessor#processLogMessage(LcGroup, LcLevel, String, String, Throwable)}
|
||||
* on specific {@link LcGroup#assertion(Throwable)} points of code.
|
||||
*
|
||||
* @return True if application should crash on assertion.
|
||||
*/
|
||||
public static boolean isCrashOnAssertions() {
|
||||
return crashOnAssertions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link LogProcessor} object to intercept incoming log messages (by default it returns {@link ConsoleLogProcessor}).
|
||||
*
|
||||
* @return Specific {@link LogProcessor}.
|
||||
*/
|
||||
@NonNull
|
||||
public static LogProcessor getLogProcessor() {
|
||||
return logProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize general logging behavior.
|
||||
*
|
||||
* @param logProcessor {@link LogProcessor} to intercept all log messages;
|
||||
* @param crashOnAssertions Flag to crash application
|
||||
* or pass it to {@link LogProcessor#processLogMessage(LcGroup, LcLevel, String, String, Throwable)}
|
||||
* on specific {@link LcGroup#assertion(Throwable)} points of code.
|
||||
*/
|
||||
public static void initialize(@NonNull final LogProcessor logProcessor, final boolean crashOnAssertions) {
|
||||
Lc.crashOnAssertions = crashOnAssertions;
|
||||
Lc.logProcessor = logProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs debug message via {@link #GENERAL_LC_GROUP}.
|
||||
*
|
||||
* @param message Message or format of message to log;
|
||||
* @param args Arguments of formatted message.
|
||||
*/
|
||||
public static void d(@NonNull final String message, @NonNull final Object... args) {
|
||||
GENERAL_LC_GROUP.d(message, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs debug message via {@link #GENERAL_LC_GROUP}.
|
||||
*
|
||||
* @param throwable Exception to log;
|
||||
* @param message Message or format of message to log;
|
||||
* @param args Arguments of formatted message.
|
||||
*/
|
||||
public static void d(@NonNull final Throwable throwable, @NonNull final String message, @NonNull final Object... args) {
|
||||
GENERAL_LC_GROUP.d(throwable, message, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs info message via {@link #GENERAL_LC_GROUP}.
|
||||
*
|
||||
* @param message Message or format of message to log;
|
||||
* @param args Arguments of formatted message.
|
||||
*/
|
||||
public static void i(@NonNull final String message, @NonNull final Object... args) {
|
||||
GENERAL_LC_GROUP.i(message, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs info message via {@link #GENERAL_LC_GROUP}.
|
||||
*
|
||||
* @param throwable Exception to log;
|
||||
* @param message Message or format of message to log;
|
||||
* @param args Arguments of formatted message.
|
||||
*/
|
||||
public static void i(@NonNull final Throwable throwable, @NonNull final String message, @NonNull final Object... args) {
|
||||
GENERAL_LC_GROUP.i(throwable, message, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs warning message via {@link #GENERAL_LC_GROUP}.
|
||||
*
|
||||
* @param message Message or format of message to log;
|
||||
* @param args Arguments of formatted message.
|
||||
*/
|
||||
public static void w(@NonNull final String message, @NonNull final Object... args) {
|
||||
GENERAL_LC_GROUP.w(message, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs warning message via {@link #GENERAL_LC_GROUP}.
|
||||
*
|
||||
* @param throwable Exception to log;
|
||||
* @param message Message or format of message to log;
|
||||
* @param args Arguments of formatted message.
|
||||
*/
|
||||
public static void w(@NonNull final Throwable throwable, @NonNull final String message, @NonNull final Object... args) {
|
||||
GENERAL_LC_GROUP.w(throwable, message, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs error message via {@link #GENERAL_LC_GROUP}.
|
||||
*
|
||||
* @param message Message or format of message to log;
|
||||
* @param args Arguments of formatted message.
|
||||
*/
|
||||
public static void e(@NonNull final String message, @NonNull final Object... args) {
|
||||
GENERAL_LC_GROUP.e(message, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs error message via {@link #GENERAL_LC_GROUP}.
|
||||
*
|
||||
* @param throwable Exception to log;
|
||||
* @param message Message or format of message to log;
|
||||
* @param args Arguments of formatted message.
|
||||
*/
|
||||
public static void e(@NonNull final Throwable throwable, @NonNull final String message, @NonNull final Object... args) {
|
||||
GENERAL_LC_GROUP.e(throwable, message, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes assertion. Normally it will throw {@link ShouldNotHappenException} and crash app.
|
||||
* If it should crash or not is specified at {@link Lc#isCrashOnAssertions()}.
|
||||
* In some cases crash on assertions should be switched off and assertion should be processed in {@link LogProcessor}.
|
||||
* It is useful for example to not crash but log it as handled crash in Crashlitycs in production build.
|
||||
*
|
||||
* @param message Message that is describing assertion.
|
||||
*/
|
||||
public static void assertion(@NonNull final String message) {
|
||||
GENERAL_LC_GROUP.assertion(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes assertion. Normally it will throw {@link ShouldNotHappenException} and crash app.
|
||||
* If it should crash or not is specified at {@link Lc#isCrashOnAssertions()}.
|
||||
* In some cases crash on assertions should be switched off and assertion should be processed in {@link LogProcessor}.
|
||||
* It is useful for example to not crash but log it as handled crash in Crashlitycs in production build.
|
||||
*
|
||||
* @param throwable Exception that is describing assertion.
|
||||
*/
|
||||
public static void assertion(@NonNull final Throwable throwable) {
|
||||
GENERAL_LC_GROUP.assertion(throwable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws assertion on main thread (to avoid Rx exceptions e.g.) and cuts top causes by type of exception class.
|
||||
*
|
||||
* @param assertion Source throwable;
|
||||
* @param exceptionsClassesToCut Classes which will be cut from top of causes stack of source throwable.
|
||||
*/
|
||||
@SafeVarargs
|
||||
public static void cutAssertion(@NonNull final Throwable assertion, @NonNull final Class<? extends Throwable>... exceptionsClassesToCut) {
|
||||
new Handler(Looper.getMainLooper()).post(() -> {
|
||||
final List<Throwable> processedExceptions = new ArrayList<>();
|
||||
Throwable result = assertion;
|
||||
boolean exceptionAssignableFromIgnores;
|
||||
do {
|
||||
exceptionAssignableFromIgnores = false;
|
||||
processedExceptions.add(result);
|
||||
for (final Class exceptionClass : exceptionsClassesToCut) {
|
||||
if (result.getClass().isAssignableFrom(exceptionClass)) {
|
||||
exceptionAssignableFromIgnores = true;
|
||||
result = result.getCause();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
while (exceptionAssignableFromIgnores && result != null && !processedExceptions.contains(result));
|
||||
Lc.assertion(result != null ? result : assertion);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns line of code from where this method called.
|
||||
*
|
||||
* @param caller Object who is calling for code point;
|
||||
* @return String represents code point.
|
||||
*/
|
||||
@NonNull
|
||||
public static String getCodePoint(@Nullable final Object caller) {
|
||||
return getCodePoint(caller, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns line of code from where this method called.
|
||||
*
|
||||
* @param caller Object who is calling for code point;
|
||||
* @param stackShift caller Shift of stack (e.g. 2 means two elements deeper);
|
||||
* @return String represents code point.
|
||||
*/
|
||||
@NonNull
|
||||
public static String getCodePoint(@Nullable final Object caller, final int stackShift) {
|
||||
final StackTraceElement traceElement = Thread.currentThread().getStackTrace()[STACK_TRACE_CODE_DEPTH + stackShift];
|
||||
return traceElement.getMethodName() + '(' + traceElement.getFileName() + ':' + traceElement.getLineNumber() + ')'
|
||||
+ (caller != null ? " of object " + caller.getClass().getSimpleName() + '(' + Integer.toHexString(caller.hashCode()) + ')' : "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints stacktrace in log with specified tag.
|
||||
*
|
||||
* @param tag Tag to be shown in logs.
|
||||
*/
|
||||
|
||||
@SuppressLint("LogConditional")
|
||||
public static void printStackTrace(@NonNull final String tag) {
|
||||
final StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
|
||||
if (Log.isLoggable(tag, Log.DEBUG)) {
|
||||
Log.d(tag, TextUtils.join("\n", Arrays.copyOfRange(stackTrace, STACK_TRACE_CODE_DEPTH, stackTrace.length)));
|
||||
}
|
||||
}
|
||||
|
||||
private Lc() {
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,237 @@
|
|||
/*
|
||||
* Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov)
|
||||
*
|
||||
* This file is part of RoboSwag library.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package ru.touchin.roboswag.core.log;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Locale;
|
||||
|
||||
import ru.touchin.roboswag.core.utils.ShouldNotHappenException;
|
||||
import ru.touchin.roboswag.core.utils.ThreadLocalValue;
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 14/05/2016.
|
||||
* Group of log messages with specific tag prefix (name of group).
|
||||
* It could be used in specific {@link LogProcessor} to filter messages by group.
|
||||
*/
|
||||
@SuppressWarnings({"checkstyle:methodname", "PMD.ShortMethodName"})
|
||||
//MethodNameCheck,ShortMethodName: log methods better be 1-symbol
|
||||
public class LcGroup {
|
||||
|
||||
/**
|
||||
* Logging group to log UI metrics (like inflation or layout time etc.).
|
||||
*/
|
||||
public static final LcGroup UI_METRICS = new LcGroup("UI_METRICS");
|
||||
/**
|
||||
* Logging group to log UI lifecycle (onCreate, onStart, onResume etc.).
|
||||
*/
|
||||
public static final LcGroup UI_LIFECYCLE = new LcGroup("UI_LIFECYCLE");
|
||||
|
||||
private static final ThreadLocalValue<SimpleDateFormat> DATE_TIME_FORMATTER
|
||||
= new ThreadLocalValue<>(() -> new SimpleDateFormat("HH:mm:ss.SSS", Locale.getDefault()));
|
||||
|
||||
@NonNull
|
||||
private final String name;
|
||||
private boolean disabled;
|
||||
|
||||
public LcGroup(@NonNull final String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables logging of this group.
|
||||
*/
|
||||
public void disable() {
|
||||
disabled = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables logging of this group.
|
||||
*/
|
||||
public void enable() {
|
||||
disabled = false;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private String createLogTag() {
|
||||
final StackTraceElement trace = Thread.currentThread().getStackTrace()[Lc.STACK_TRACE_CODE_DEPTH + 3];
|
||||
return trace.getFileName() + ':' + trace.getLineNumber();
|
||||
}
|
||||
|
||||
@SuppressWarnings("PMD.AvoidCatchingThrowable")
|
||||
//AvoidCatchingThrowable: it is needed to safety format message
|
||||
@Nullable
|
||||
private String createFormattedMessage(@Nullable final String message, @NonNull final Object... args) {
|
||||
try {
|
||||
if (args.length > 0 && message == null) {
|
||||
throw new ShouldNotHappenException("Args are not empty but format message is null");
|
||||
}
|
||||
return message != null ? (args.length > 0 ? String.format(message, args) : message) : null;
|
||||
} catch (final Throwable formattingException) {
|
||||
Lc.assertion(formattingException);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private String createLogMessage(@Nullable final String formattedMessage) {
|
||||
return DATE_TIME_FORMATTER.get().format(System.currentTimeMillis())
|
||||
+ ' ' + Thread.currentThread().getName()
|
||||
+ ' ' + name
|
||||
+ (formattedMessage != null ? (' ' + formattedMessage) : "");
|
||||
}
|
||||
|
||||
private void logMessage(@NonNull final LcLevel logLevel, @Nullable final String message,
|
||||
@Nullable final Throwable throwable, @NonNull final Object... args) {
|
||||
if (disabled || logLevel.lessThan(Lc.getLogProcessor().getMinLogLevel())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (throwable == null && args.length > 0 && args[0] instanceof Throwable) {
|
||||
Lc.w("Maybe you've misplaced exception with first format arg? format: %s; arg: %s", message, args[0]);
|
||||
}
|
||||
|
||||
final String formattedMessage = createFormattedMessage(message, args);
|
||||
if (logLevel == LcLevel.ASSERT && Lc.isCrashOnAssertions()) {
|
||||
throw createAssertion(formattedMessage, throwable);
|
||||
}
|
||||
|
||||
Lc.getLogProcessor().processLogMessage(this, logLevel, createLogTag(), createLogMessage(formattedMessage), throwable);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private ShouldNotHappenException createAssertion(@Nullable final String message, @Nullable final Throwable exception) {
|
||||
return exception != null
|
||||
? (message != null ? new ShouldNotHappenException(message, exception)
|
||||
: (exception instanceof ShouldNotHappenException ? (ShouldNotHappenException) exception : new ShouldNotHappenException(exception)))
|
||||
: (message != null ? new ShouldNotHappenException(message) : new ShouldNotHappenException());
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs debug message.
|
||||
*
|
||||
* @param message Message or format of message to log;
|
||||
* @param args Arguments of formatted message.
|
||||
*/
|
||||
public void d(@NonNull final String message, @NonNull final Object... args) {
|
||||
logMessage(LcLevel.DEBUG, message, null, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs debug message.
|
||||
*
|
||||
* @param throwable Exception to log;
|
||||
* @param message Message or format of message to log;
|
||||
* @param args Arguments of formatted message.
|
||||
*/
|
||||
public void d(@NonNull final Throwable throwable, @NonNull final String message, @NonNull final Object... args) {
|
||||
logMessage(LcLevel.DEBUG, message, throwable, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs info message.
|
||||
*
|
||||
* @param message Message or format of message to log;
|
||||
* @param args Arguments of formatted message.
|
||||
*/
|
||||
public void i(@NonNull final String message, @NonNull final Object... args) {
|
||||
logMessage(LcLevel.INFO, message, null, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs info message.
|
||||
*
|
||||
* @param throwable Exception to log;
|
||||
* @param message Message or format of message to log;
|
||||
* @param args Arguments of formatted message.
|
||||
*/
|
||||
public void i(@NonNull final Throwable throwable, @NonNull final String message, @NonNull final Object... args) {
|
||||
logMessage(LcLevel.INFO, message, throwable, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs warning message.
|
||||
*
|
||||
* @param message Message or format of message to log;
|
||||
* @param args Arguments of formatted message.
|
||||
*/
|
||||
public void w(@NonNull final String message, @NonNull final Object... args) {
|
||||
logMessage(LcLevel.WARN, message, null, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs warning message.
|
||||
*
|
||||
* @param throwable Exception to log;
|
||||
* @param message Message or format of message to log;
|
||||
* @param args Arguments of formatted message.
|
||||
*/
|
||||
public void w(@NonNull final Throwable throwable, @NonNull final String message, @NonNull final Object... args) {
|
||||
logMessage(LcLevel.WARN, message, throwable, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs error message.
|
||||
*
|
||||
* @param message Message or format of message to log;
|
||||
* @param args Arguments of formatted message.
|
||||
*/
|
||||
public void e(@NonNull final String message, @NonNull final Object... args) {
|
||||
logMessage(LcLevel.ERROR, message, null, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs error message.
|
||||
*
|
||||
* @param throwable Exception to log;
|
||||
* @param message Message or format of message to log;
|
||||
* @param args Arguments of formatted message.
|
||||
*/
|
||||
public void e(@NonNull final Throwable throwable, @NonNull final String message, @NonNull final Object... args) {
|
||||
logMessage(LcLevel.ERROR, message, throwable, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes assertion. Normally it will throw {@link ShouldNotHappenException} and crash app.
|
||||
* If it should crash or not is specified at {@link Lc#isCrashOnAssertions()}.
|
||||
* In some cases crash on assertions should be switched off and assertion should be processed in {@link LogProcessor}.
|
||||
* It is useful for example to not crash but log it as handled crash in Crashlitycs in production build.
|
||||
*
|
||||
* @param message Message that is describing assertion.
|
||||
*/
|
||||
public void assertion(@NonNull final String message) {
|
||||
logMessage(LcLevel.ASSERT, "Assertion appears at %s with message: %s", null, Lc.getCodePoint(null, 2), message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes assertion. Normally it will throw {@link ShouldNotHappenException} and crash app.
|
||||
* If it should crash or not is specified at {@link Lc#isCrashOnAssertions()}.
|
||||
* In some cases crash on assertions should be switched off and assertion should be processed in {@link LogProcessor}.
|
||||
* It is useful for example to not crash but log it as handled crash in Crashlitycs in production build.
|
||||
*
|
||||
* @param throwable Exception that is describing assertion.
|
||||
*/
|
||||
public void assertion(@NonNull final Throwable throwable) {
|
||||
logMessage(LcLevel.ASSERT, "Assertion appears at %s", throwable, Lc.getCodePoint(null, 2));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov)
|
||||
*
|
||||
* This file is part of RoboSwag library.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package ru.touchin.roboswag.core.log;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 14/05/2016.
|
||||
* Level of log message.
|
||||
*/
|
||||
public enum LcLevel {
|
||||
|
||||
VERBOSE(Log.VERBOSE),
|
||||
DEBUG(Log.DEBUG),
|
||||
INFO(Log.INFO),
|
||||
WARN(Log.WARN),
|
||||
ERROR(Log.ERROR),
|
||||
ASSERT(Log.ASSERT);
|
||||
|
||||
private final int priority;
|
||||
|
||||
LcLevel(final int priority) {
|
||||
this.priority = priority;
|
||||
}
|
||||
|
||||
/**
|
||||
* Standard {@link Log} integer value of level represents priority of message.
|
||||
*
|
||||
* @return Integer level.
|
||||
*/
|
||||
public int getPriority() {
|
||||
return priority;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares priorities of LcLevels and returns if current is less than another.
|
||||
*
|
||||
* @param logLevel {@link LcLevel} to compare priority with;
|
||||
* @return True if current level priority less than level passed as parameter.
|
||||
*/
|
||||
public boolean lessThan(@NonNull final LcLevel logLevel) {
|
||||
return this.priority < logLevel.priority;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov)
|
||||
*
|
||||
* This file is part of RoboSwag library.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package ru.touchin.roboswag.core.log;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 13/11/2015.
|
||||
* Abstract object to intercept log messages coming from {@link LcGroup} and {@link Lc} log methods.
|
||||
*/
|
||||
public abstract class LogProcessor {
|
||||
|
||||
@NonNull
|
||||
private final LcLevel minLogLevel;
|
||||
|
||||
public LogProcessor(@NonNull final LcLevel minLogLevel) {
|
||||
this.minLogLevel = minLogLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Minimum logging level.
|
||||
* Any messages with lower priority won't be passed into {@link #processLogMessage(LcGroup, LcLevel, String, String, Throwable)}.
|
||||
*
|
||||
* @return Minimum log level represented by {@link LcLevel} object.
|
||||
*/
|
||||
@NonNull
|
||||
public LcLevel getMinLogLevel() {
|
||||
return minLogLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Core method to process any incoming log messages from {@link LcGroup} and {@link Lc} with level higher or equals {@link #getMinLogLevel()}.
|
||||
*
|
||||
* @param group {@link LcGroup} where log message came from;
|
||||
* @param level {@link LcLevel} level (priority) of message;
|
||||
* @param tag String mark of message;
|
||||
* @param message Message to log;
|
||||
* @param throwable Exception to log.
|
||||
*/
|
||||
public abstract void processLogMessage(@NonNull final LcGroup group, @NonNull final LcLevel level,
|
||||
@NonNull final String tag, @NonNull final String message, @Nullable final Throwable throwable);
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov)
|
||||
*
|
||||
* This file is part of RoboSwag library.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package ru.touchin.roboswag.core.utils;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 13/11/2015.
|
||||
* Exception that should be threw when some unexpected code reached.
|
||||
* E.g. if some value null but it is not legal or in default case in switch if all specific cases should be processed.
|
||||
*/
|
||||
public class ShouldNotHappenException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 0;
|
||||
|
||||
public ShouldNotHappenException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public ShouldNotHappenException(@NonNull final String detailMessage) {
|
||||
super(detailMessage);
|
||||
}
|
||||
|
||||
public ShouldNotHappenException(@NonNull final String detailMessage, @NonNull final Throwable throwable) {
|
||||
super(detailMessage, throwable);
|
||||
}
|
||||
|
||||
public ShouldNotHappenException(@NonNull final Throwable throwable) {
|
||||
super(throwable);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov)
|
||||
*
|
||||
* This file is part of RoboSwag library.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package ru.touchin.roboswag.core.utils;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 13/11/2015.
|
||||
* Thread local value with specified creator of value per thread.
|
||||
*/
|
||||
public class ThreadLocalValue<T> extends ThreadLocal<T> {
|
||||
|
||||
@NonNull
|
||||
private final Fabric<T> fabric;
|
||||
|
||||
public ThreadLocalValue(@NonNull final Fabric<T> fabric) {
|
||||
super();
|
||||
this.fabric = fabric;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected T initialValue() {
|
||||
return fabric.create();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fabric of thread-local objects.
|
||||
*
|
||||
* @param <T> Type of objects.
|
||||
*/
|
||||
public interface Fabric<T> {
|
||||
|
||||
/**
|
||||
* Creates object.
|
||||
*
|
||||
* @return new instance of object.
|
||||
*/
|
||||
@NonNull
|
||||
T create();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
compileSdkVersion versions.compileSdk
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 16
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api project(":utils")
|
||||
api project(":logging")
|
||||
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
|
||||
implementation "com.android.support:appcompat-v7:$versions.supportLibrary"
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
<manifest
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="ru.touchin.roboswag.components.navigation"/>
|
||||
|
|
@ -36,4 +36,4 @@ public interface OnFragmentStartedListener {
|
|||
*/
|
||||
void onFragmentStarted(@NonNull Fragment fragment);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -29,8 +29,8 @@ import android.view.MenuItem;
|
|||
|
||||
import java.util.Set;
|
||||
|
||||
import ru.touchin.roboswag.components.utils.UiUtils;
|
||||
import ru.touchin.roboswag.core.log.Lc;
|
||||
import ru.touchin.roboswag.core.log.LcGroup;
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 08/03/2016.
|
||||
|
|
@ -44,54 +44,54 @@ public abstract class BaseActivity extends AppCompatActivity {
|
|||
@Override
|
||||
protected void onCreate(@Nullable final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
UiUtils.UI_LIFECYCLE_LC_GROUP.i(Lc.getCodePoint(this));
|
||||
LcGroup.UI_LIFECYCLE.i(Lc.getCodePoint(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(final int requestCode, final int resultCode, @Nullable final Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
UiUtils.UI_LIFECYCLE_LC_GROUP.i(Lc.getCodePoint(this) + " requestCode: " + requestCode + "; resultCode: " + resultCode);
|
||||
LcGroup.UI_LIFECYCLE.i(Lc.getCodePoint(this) + " requestCode: " + requestCode + "; resultCode: " + resultCode);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
UiUtils.UI_LIFECYCLE_LC_GROUP.i(Lc.getCodePoint(this));
|
||||
LcGroup.UI_LIFECYCLE.i(Lc.getCodePoint(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
UiUtils.UI_LIFECYCLE_LC_GROUP.i(Lc.getCodePoint(this));
|
||||
LcGroup.UI_LIFECYCLE.i(Lc.getCodePoint(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
UiUtils.UI_LIFECYCLE_LC_GROUP.i(Lc.getCodePoint(this));
|
||||
LcGroup.UI_LIFECYCLE.i(Lc.getCodePoint(this));
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(@NonNull final Bundle stateToSave) {
|
||||
super.onSaveInstanceState(stateToSave);
|
||||
UiUtils.UI_LIFECYCLE_LC_GROUP.i(Lc.getCodePoint(this));
|
||||
LcGroup.UI_LIFECYCLE.i(Lc.getCodePoint(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLowMemory() {
|
||||
super.onLowMemory();
|
||||
UiUtils.UI_LIFECYCLE_LC_GROUP.i(Lc.getCodePoint(this));
|
||||
LcGroup.UI_LIFECYCLE.i(Lc.getCodePoint(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
UiUtils.UI_LIFECYCLE_LC_GROUP.i(Lc.getCodePoint(this));
|
||||
LcGroup.UI_LIFECYCLE.i(Lc.getCodePoint(this));
|
||||
super.onStop();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
UiUtils.UI_LIFECYCLE_LC_GROUP.i(Lc.getCodePoint(this));
|
||||
LcGroup.UI_LIFECYCLE.i(Lc.getCodePoint(this));
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
|
|
@ -30,7 +30,6 @@ import android.os.SystemClock;
|
|||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
import android.support.v4.view.ViewCompat;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
|
|
@ -42,10 +41,9 @@ import android.widget.FrameLayout;
|
|||
|
||||
import java.lang.reflect.Constructor;
|
||||
|
||||
import ru.touchin.roboswag.components.R;
|
||||
import ru.touchin.roboswag.components.navigation.viewcontrollers.ViewController;
|
||||
import ru.touchin.roboswag.components.utils.UiUtils;
|
||||
import ru.touchin.roboswag.core.log.Lc;
|
||||
import ru.touchin.roboswag.core.log.LcGroup;
|
||||
import ru.touchin.roboswag.core.utils.ShouldNotHappenException;
|
||||
|
||||
/**
|
||||
|
|
@ -182,7 +180,7 @@ public class ViewControllerFragment<TActivity extends FragmentActivity, TState e
|
|||
if (inDebugMode) {
|
||||
final long creationPeriod = SystemClock.elapsedRealtime() - creationTime;
|
||||
if (creationPeriod > acceptableUiCalculationTime) {
|
||||
UiUtils.UI_METRICS_LC_GROUP.w("Creation of %s took too much: %dms", viewControllerClass, creationPeriod);
|
||||
LcGroup.UI_METRICS.w("Creation of %s took too much: %dms", viewControllerClass, creationPeriod);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -212,11 +210,6 @@ public class ViewControllerFragment<TActivity extends FragmentActivity, TState e
|
|||
@Nullable
|
||||
@Override
|
||||
public Animation onCreateAnimation(final int transit, final boolean enter, final int nextAnim) {
|
||||
if (nextAnim == R.anim.fragment_slide_in_right_animation || nextAnim == R.anim.fragment_slide_out_right_animation) {
|
||||
ViewCompat.setTranslationZ(getView(), 1F);
|
||||
} else {
|
||||
ViewCompat.setTranslationZ(getView(), 0F);
|
||||
}
|
||||
if (viewController != null) {
|
||||
return viewController.onCreateAnimation(transit, enter, nextAnim);
|
||||
} else {
|
||||
|
|
@ -371,7 +364,7 @@ public class ViewControllerFragment<TActivity extends FragmentActivity, TState e
|
|||
if (inDebugMode && lastMeasureTime > 0) {
|
||||
final long layoutTime = SystemClock.uptimeMillis() - lastMeasureTime;
|
||||
if (layoutTime > acceptableUiCalculationTime) {
|
||||
UiUtils.UI_METRICS_LC_GROUP.w("Measure and layout of %s took too much: %dms", tagName, layoutTime);
|
||||
LcGroup.UI_METRICS.w("Measure and layout of %s took too much: %dms", tagName, layoutTime);
|
||||
}
|
||||
lastMeasureTime = 0;
|
||||
}
|
||||
|
|
@ -30,9 +30,9 @@ import android.view.LayoutInflater;
|
|||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import io.reactivex.functions.BiConsumer;
|
||||
import ru.touchin.roboswag.components.navigation.OnFragmentStartedListener;
|
||||
import ru.touchin.roboswag.core.log.Lc;
|
||||
import ru.touchin.roboswag.core.utils.BiConsumer;
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 21/10/2015.
|
||||
|
|
@ -46,18 +46,19 @@ import android.view.MenuInflater;
|
|||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import android.view.animation.Animation;
|
||||
|
||||
import ru.touchin.roboswag.components.navigation.fragments.ViewControllerFragment;
|
||||
import ru.touchin.roboswag.components.utils.UiUtils;
|
||||
import ru.touchin.roboswag.core.log.Lc;
|
||||
import ru.touchin.roboswag.core.log.LcGroup;
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 21/10/2015.
|
||||
* Class to control view of specific fragment, activity and application by logic bridge.
|
||||
*
|
||||
* @param <TActivity> Type of activity where such {@link ViewController} could be;
|
||||
* @param <TState> Type of state;
|
||||
* @param <TState> Type of state;
|
||||
*/
|
||||
public class ViewController<TActivity extends FragmentActivity, TState extends Parcelable> implements LifecycleOwner {
|
||||
|
||||
|
|
@ -221,7 +222,7 @@ public class ViewController<TActivity extends FragmentActivity, TState extends P
|
|||
|
||||
/**
|
||||
* Returns a color state list associated with a particular resource ID.
|
||||
*
|
||||
* <p>
|
||||
* <p>Starting in {@link android.os.Build.VERSION_CODES#M}, the returned
|
||||
* color state list will be styled for the specified Context's theme.
|
||||
*
|
||||
|
|
@ -275,7 +276,7 @@ public class ViewController<TActivity extends FragmentActivity, TState extends P
|
|||
*/
|
||||
@CallSuper
|
||||
public void onCreate() {
|
||||
UiUtils.UI_LIFECYCLE_LC_GROUP.i(Lc.getCodePoint(this));
|
||||
LcGroup.UI_LIFECYCLE.i(Lc.getCodePoint(this));
|
||||
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
|
||||
}
|
||||
|
||||
|
|
@ -285,10 +286,10 @@ public class ViewController<TActivity extends FragmentActivity, TState extends P
|
|||
* {@link Animator} resources instead of {@link Animation} resources, {@code nextAnim}
|
||||
* will be an animator resource.
|
||||
*
|
||||
* @param transit The value set in {@link FragmentTransaction#setTransition(int)} or 0 if not
|
||||
* set.
|
||||
* @param enter {@code true} when the fragment is added/attached/shown or {@code false} when
|
||||
* the fragment is removed/detached/hidden.
|
||||
* @param transit The value set in {@link FragmentTransaction#setTransition(int)} or 0 if not
|
||||
* set.
|
||||
* @param enter {@code true} when the fragment is added/attached/shown or {@code false} when
|
||||
* the fragment is removed/detached/hidden.
|
||||
* @param nextAnim The resource set in
|
||||
* {@link FragmentTransaction#setCustomAnimations(int, int)},
|
||||
* {@link FragmentTransaction#setCustomAnimations(int, int, int, int)}, or
|
||||
|
|
@ -306,10 +307,10 @@ public class ViewController<TActivity extends FragmentActivity, TState extends P
|
|||
* {@link Animation} resources instead of {@link Animator} resources, {@code nextAnim}
|
||||
* will be an animation resource.
|
||||
*
|
||||
* @param transit The value set in {@link FragmentTransaction#setTransition(int)} or 0 if not
|
||||
* set.
|
||||
* @param enter {@code true} when the fragment is added/attached/shown or {@code false} when
|
||||
* the fragment is removed/detached/hidden.
|
||||
* @param transit The value set in {@link FragmentTransaction#setTransition(int)} or 0 if not
|
||||
* set.
|
||||
* @param enter {@code true} when the fragment is added/attached/shown or {@code false} when
|
||||
* the fragment is removed/detached/hidden.
|
||||
* @param nextAnim The resource set in
|
||||
* {@link FragmentTransaction#setCustomAnimations(int, int)},
|
||||
* {@link FragmentTransaction#setCustomAnimations(int, int, int, int)}, or
|
||||
|
|
@ -335,7 +336,7 @@ public class ViewController<TActivity extends FragmentActivity, TState extends P
|
|||
*/
|
||||
@CallSuper
|
||||
public void onStart() {
|
||||
UiUtils.UI_LIFECYCLE_LC_GROUP.i(Lc.getCodePoint(this));
|
||||
LcGroup.UI_LIFECYCLE.i(Lc.getCodePoint(this));
|
||||
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
|
||||
UiUtils.OfViews.hideSoftInput(getContainer());
|
||||
}
|
||||
|
|
@ -345,7 +346,7 @@ public class ViewController<TActivity extends FragmentActivity, TState extends P
|
|||
* Usually it is indicating that user can't see fragment on screen and useful to track analytics events.
|
||||
*/
|
||||
public void onAppear() {
|
||||
UiUtils.UI_LIFECYCLE_LC_GROUP.i(Lc.getCodePoint(this));
|
||||
LcGroup.UI_LIFECYCLE.i(Lc.getCodePoint(this));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -354,7 +355,7 @@ public class ViewController<TActivity extends FragmentActivity, TState extends P
|
|||
*/
|
||||
@CallSuper
|
||||
public void onResume() {
|
||||
UiUtils.UI_LIFECYCLE_LC_GROUP.i(Lc.getCodePoint(this));
|
||||
LcGroup.UI_LIFECYCLE.i(Lc.getCodePoint(this));
|
||||
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME);
|
||||
}
|
||||
|
||||
|
|
@ -373,7 +374,7 @@ public class ViewController<TActivity extends FragmentActivity, TState extends P
|
|||
*/
|
||||
@CallSuper
|
||||
public void onPause() {
|
||||
UiUtils.UI_LIFECYCLE_LC_GROUP.i(Lc.getCodePoint(this));
|
||||
LcGroup.UI_LIFECYCLE.i(Lc.getCodePoint(this));
|
||||
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE);
|
||||
}
|
||||
|
||||
|
|
@ -384,7 +385,7 @@ public class ViewController<TActivity extends FragmentActivity, TState extends P
|
|||
*/
|
||||
@CallSuper
|
||||
public void onSaveInstanceState(@NonNull final Bundle savedInstanceState) {
|
||||
UiUtils.UI_LIFECYCLE_LC_GROUP.i(Lc.getCodePoint(this));
|
||||
LcGroup.UI_LIFECYCLE.i(Lc.getCodePoint(this));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -392,7 +393,7 @@ public class ViewController<TActivity extends FragmentActivity, TState extends P
|
|||
* Usually it is indicating that user can't see fragment on screen and useful to track analytics events.
|
||||
*/
|
||||
public void onDisappear() {
|
||||
UiUtils.UI_LIFECYCLE_LC_GROUP.i(Lc.getCodePoint(this));
|
||||
LcGroup.UI_LIFECYCLE.i(Lc.getCodePoint(this));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -401,7 +402,7 @@ public class ViewController<TActivity extends FragmentActivity, TState extends P
|
|||
*/
|
||||
@CallSuper
|
||||
public void onStop() {
|
||||
UiUtils.UI_LIFECYCLE_LC_GROUP.i(Lc.getCodePoint(this));
|
||||
LcGroup.UI_LIFECYCLE.i(Lc.getCodePoint(this));
|
||||
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP);
|
||||
}
|
||||
|
||||
|
|
@ -411,7 +412,7 @@ public class ViewController<TActivity extends FragmentActivity, TState extends P
|
|||
*/
|
||||
@CallSuper
|
||||
public void onDestroy() {
|
||||
UiUtils.UI_LIFECYCLE_LC_GROUP.i(Lc.getCodePoint(this));
|
||||
LcGroup.UI_LIFECYCLE.i(Lc.getCodePoint(this));
|
||||
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY);
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-android-extensions'
|
||||
|
||||
android {
|
||||
compileSdkVersion 27
|
||||
defaultConfig {
|
||||
applicationId "ru.touchin.roboswag.components"
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 27
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
implementation 'com.android.support:appcompat-v7:27.1.1'
|
||||
implementation 'com.android.support.constraint:constraint-layout:1.1.2'
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# 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
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="ru.touchin.roboswag.components">
|
||||
|
||||
<application
|
||||
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">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package ru.touchin.roboswag.components
|
||||
|
||||
import android.support.v7.app.AppCompatActivity
|
||||
import android.os.Bundle
|
||||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_main)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
<vector
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportHeight="108"
|
||||
android:viewportWidth="108">
|
||||
<path
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
|
||||
android:strokeColor="#00000000"
|
||||
android:strokeWidth="1">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="78.5885"
|
||||
android:endY="90.9159"
|
||||
android:startX="48.7653"
|
||||
android:startY="61.0927"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#44000000"
|
||||
android:offset="0.0"/>
|
||||
<item
|
||||
android:color="#00000000"
|
||||
android:offset="1.0"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:fillType="nonZero"
|
||||
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
|
||||
android:strokeColor="#00000000"
|
||||
android:strokeWidth="1"/>
|
||||
</vector>
|
||||
|
|
@ -0,0 +1,171 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportHeight="108"
|
||||
android:viewportWidth="108">
|
||||
<path
|
||||
android:fillColor="#26A69A"
|
||||
android:pathData="M0,0h108v108h-108z"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M9,0L9,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,0L19,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,0L29,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,0L39,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,0L49,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,0L59,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,0L69,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,0L79,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M89,0L89,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M99,0L99,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,9L108,9"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,19L108,19"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,29L108,29"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,39L108,39"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,49L108,49"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,59L108,59"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,69L108,69"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,79L108,79"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,89L108,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,99L108,99"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,29L89,29"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,39L89,39"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,49L89,49"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,59L89,59"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,69L89,69"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,79L89,79"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,19L29,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,19L39,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,19L49,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,19L59,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,19L69,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,19L79,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
</vector>
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
<?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=".MainActivity">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Hello World!"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"/>
|
||||
|
||||
</android.support.constraint.ConstraintLayout>
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 6.9 KiB |
|
After Width: | Height: | Size: 6.3 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 9.0 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
|
@ -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>
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
<resources>
|
||||
<string name="app_name">Components</string>
|
||||
</resources>
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<resources>
|
||||
|
||||
<!-- Base application theme. -->
|
||||
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
|
||||
<!-- Customize your theme here. -->
|
||||
<item name="colorPrimary">@color/colorPrimary</item>
|
||||
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||
<item name="colorAccent">@color/colorAccent</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
|
|
@ -0,0 +1 @@
|
|||
include ':sample', ':utils', ':logging', ':navigation', ':storable'
|
||||
|
|
@ -1 +0,0 @@
|
|||
<manifest package="ru.touchin.roboswag.components"/>
|
||||
|
|
@ -1,96 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2017 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov)
|
||||
*
|
||||
* This file is part of RoboSwag library.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package ru.touchin.roboswag.components.adapters;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.view.ViewCompat;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Objects of such class controls creation and binding of specific type of RecyclerView's ViewHolders.
|
||||
* Default {@link #getItemViewType} is generating on construction of object.
|
||||
*
|
||||
* @param <TViewHolder> Type of {@link RecyclerView.ViewHolder} of delegate.
|
||||
*/
|
||||
public abstract class AdapterDelegate<TViewHolder extends RecyclerView.ViewHolder> {
|
||||
|
||||
private final int defaultItemViewType = ViewCompat.generateViewId();
|
||||
|
||||
/**
|
||||
* Unique ID of AdapterDelegate.
|
||||
*
|
||||
* @return Unique ID.
|
||||
*/
|
||||
public int getItemViewType() {
|
||||
return defaultItemViewType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if object is processable by this delegate.
|
||||
*
|
||||
* @param items Items to check;
|
||||
* @param adapterPosition Position of item in adapter;
|
||||
* @param collectionPosition Position of item in collection;
|
||||
* @return True if item is processable by this delegate.
|
||||
*/
|
||||
public abstract boolean isForViewType(@NonNull final List<Object> items, final int adapterPosition, final int collectionPosition);
|
||||
|
||||
/**
|
||||
* Returns unique ID of item to support stable ID's logic of RecyclerView's adapter.
|
||||
*
|
||||
* @param items Items in adapter;
|
||||
* @param adapterPosition Position of item in adapter;
|
||||
* @param collectionPosition Position of item in collection;
|
||||
* @return Unique item ID.
|
||||
*/
|
||||
public long getItemId(@NonNull final List<Object> items, final int adapterPosition, final int collectionPosition) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates ViewHolder to bind item to it later.
|
||||
*
|
||||
* @param parent Container of ViewHolder's view.
|
||||
* @return New ViewHolder.
|
||||
*/
|
||||
@NonNull
|
||||
public abstract TViewHolder onCreateViewHolder(@NonNull final ViewGroup parent);
|
||||
|
||||
/**
|
||||
* Binds item to created by this object ViewHolder.
|
||||
*
|
||||
* @param holder ViewHolder to bind item to;
|
||||
* @param items Items in adapter;
|
||||
* @param adapterPosition Position of item in adapter;
|
||||
* @param collectionPosition Position of item in collection that contains item;
|
||||
* @param payloads Payloads;
|
||||
*/
|
||||
public abstract void onBindViewHolder(
|
||||
@NonNull final RecyclerView.ViewHolder holder,
|
||||
@NonNull final List<Object> items,
|
||||
final int adapterPosition,
|
||||
final int collectionPosition,
|
||||
@NonNull final List<Object> payloads
|
||||
);
|
||||
|
||||
}
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
package ru.touchin.roboswag.components.adapters
|
||||
|
||||
import android.support.v7.widget.RecyclerView
|
||||
import android.util.SparseArray
|
||||
import android.view.ViewGroup
|
||||
|
||||
/**
|
||||
* Manager for delegation callbacks from [RecyclerView.Adapter] to delegates.
|
||||
*/
|
||||
class DelegatesManager {
|
||||
|
||||
private val delegates = SparseArray<AdapterDelegate<*>>()
|
||||
|
||||
fun getItemViewType(items: List<*>, adapterPosition: Int, collectionPosition: Int): Int {
|
||||
for (index in 0 until delegates.size()) {
|
||||
val delegate = delegates.valueAt(index)
|
||||
if (delegate.isForViewType(items, adapterPosition, collectionPosition)) {
|
||||
return delegate.itemViewType
|
||||
}
|
||||
}
|
||||
throw IllegalStateException("Delegate not found for adapterPosition: $adapterPosition")
|
||||
}
|
||||
|
||||
fun getItemId(items: List<*>, adapterPosition: Int, collectionPosition: Int): Long {
|
||||
val delegate = getDelegate(getItemViewType(items, adapterPosition, collectionPosition))
|
||||
return delegate.getItemId(items, adapterPosition, collectionPosition)
|
||||
}
|
||||
|
||||
fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder = getDelegate(viewType).onCreateViewHolder(parent)
|
||||
|
||||
fun onBindViewHolder(holder: RecyclerView.ViewHolder, items: List<*>, adapterPosition: Int, collectionPosition: Int, payloads: List<Any>) {
|
||||
val delegate = getDelegate(getItemViewType(items, adapterPosition, collectionPosition))
|
||||
delegate.onBindViewHolder(holder, items, adapterPosition, collectionPosition, payloads)
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds [PositionAdapterDelegate] to adapter.
|
||||
*
|
||||
* @param delegate Delegate to add.
|
||||
*/
|
||||
fun addDelegate(delegate: AdapterDelegate<*>) = delegates.put(delegate.itemViewType, delegate)
|
||||
|
||||
/**
|
||||
* Removes [AdapterDelegate] from adapter.
|
||||
*
|
||||
* @param delegate Delegate to remove.
|
||||
*/
|
||||
fun removeDelegate(delegate: AdapterDelegate<*>) = delegates.remove(delegate.itemViewType)
|
||||
|
||||
private fun getDelegate(viewType: Int) = delegates[viewType] ?: throw IllegalStateException("No AdapterDelegate added for view type: $viewType")
|
||||
|
||||
}
|
||||
|
|
@ -1,89 +0,0 @@
|
|||
package ru.touchin.roboswag.components.adapters
|
||||
|
||||
import android.support.v7.recyclerview.extensions.AsyncDifferConfig
|
||||
import android.support.v7.recyclerview.extensions.AsyncListDiffer
|
||||
import android.support.v7.util.DiffUtil
|
||||
import android.support.v7.widget.RecyclerView
|
||||
import android.view.ViewGroup
|
||||
import ru.touchin.roboswag.components.extensions.setOnRippleClickListener
|
||||
|
||||
/**
|
||||
* Base adapter with delegation and diff computing on background thread.
|
||||
*/
|
||||
open class DelegationListAdapter<TItem>(config: AsyncDifferConfig<TItem>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||
|
||||
constructor(diffCallback: DiffUtil.ItemCallback<TItem>) : this(AsyncDifferConfig.Builder<TItem>(diffCallback).build())
|
||||
|
||||
var itemClickListener: ((TItem, RecyclerView.ViewHolder) -> Unit)? = null
|
||||
|
||||
private val delegatesManager = DelegatesManager()
|
||||
private var differ = AsyncListDiffer(OffsetAdapterUpdateCallback(this, ::getHeadersCount), config)
|
||||
|
||||
open fun getHeadersCount() = 0
|
||||
|
||||
open fun getFootersCount() = 0
|
||||
|
||||
override fun getItemCount() = getHeadersCount() + getList().size + getFootersCount()
|
||||
|
||||
override fun getItemViewType(position: Int) = delegatesManager.getItemViewType(getList(), position, getCollectionPosition(position))
|
||||
|
||||
override fun getItemId(position: Int) = delegatesManager.getItemId(getList(), position, getCollectionPosition(position))
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = delegatesManager.onCreateViewHolder(parent, viewType)
|
||||
|
||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int, payloads: List<Any>) {
|
||||
val collectionPosition = getCollectionPosition(position)
|
||||
if (collectionPosition in 0 until getList().size) {
|
||||
if (itemClickListener != null) {
|
||||
holder.itemView.setOnRippleClickListener {
|
||||
itemClickListener?.invoke(getList()[getCollectionPosition(holder.adapterPosition)], holder)
|
||||
}
|
||||
} else {
|
||||
holder.itemView.setOnClickListener(null)
|
||||
}
|
||||
}
|
||||
delegatesManager.onBindViewHolder(holder, getList(), position, collectionPosition, payloads)
|
||||
}
|
||||
|
||||
final override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) = Unit
|
||||
|
||||
/**
|
||||
* Adds [AdapterDelegate] to adapter.
|
||||
*
|
||||
* @param delegate Delegate to add.
|
||||
*/
|
||||
fun addDelegate(delegate: AdapterDelegate<*>) = delegatesManager.addDelegate(delegate)
|
||||
|
||||
/**
|
||||
* Removes [AdapterDelegate] from adapter.
|
||||
*
|
||||
* @param delegate Delegate to remove.
|
||||
*/
|
||||
fun removeDelegate(delegate: AdapterDelegate<*>) = delegatesManager.removeDelegate(delegate)
|
||||
|
||||
/**
|
||||
* Submits a new list to be diffed, and displayed.
|
||||
*
|
||||
* If a list is already being displayed, a diff will be computed on a background thread, which
|
||||
* will dispatch Adapter.notifyItem events on the main thread.
|
||||
*
|
||||
* @param list The new list to be displayed.
|
||||
*/
|
||||
fun submitList(list: List<TItem>) = differ.submitList(list)
|
||||
|
||||
/**
|
||||
* Get the current List - any diffing to present this list has already been computed and
|
||||
* dispatched via the ListUpdateCallback.
|
||||
* <p>
|
||||
* If a <code>null</code> List, or no List has been submitted, an empty list will be returned.
|
||||
* <p>
|
||||
* The returned list may not be mutated - mutations to content must be done through
|
||||
* {@link #submitList(List)}.
|
||||
*
|
||||
* @return current List.
|
||||
*/
|
||||
fun getList(): List<TItem> = differ.currentList
|
||||
|
||||
fun getCollectionPosition(adapterPosition: Int) = adapterPosition - getHeadersCount()
|
||||
|
||||
}
|
||||
|
|
@ -1,85 +0,0 @@
|
|||
package ru.touchin.roboswag.components.adapters;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Objects of such class controls creation and binding of specific type of RecyclerView's ViewHolders.
|
||||
* Such delegates are creating and binding ViewHolders for specific items.
|
||||
* Default {@link #getItemViewType} is generating on construction of object.
|
||||
*
|
||||
* @param <TViewHolder> Type of {@link RecyclerView.ViewHolder} of delegate;
|
||||
* @param <TItem> Type of items to bind to {@link RecyclerView.ViewHolder}s.
|
||||
*/
|
||||
public abstract class ItemAdapterDelegate<TViewHolder extends RecyclerView.ViewHolder, TItem> extends AdapterDelegate<TViewHolder> {
|
||||
|
||||
@Override
|
||||
public boolean isForViewType(@NonNull final List<Object> items, final int adapterPosition, final int collectionPosition) {
|
||||
return collectionPosition >= 0
|
||||
&& collectionPosition < items.size()
|
||||
&& isForViewType(items.get(collectionPosition), adapterPosition, collectionPosition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if object is processable by this delegate.
|
||||
* This item will be casted to {@link TItem} and passes to {@link #onBindViewHolder(TViewHolder, TItem, int, int, List)}.
|
||||
*
|
||||
* @param item Item to check;
|
||||
* @param adapterPosition Position of item in adapter;
|
||||
* @param collectionPosition Position of item in collection that contains item;
|
||||
* @return True if item is processable by this delegate.
|
||||
*/
|
||||
public boolean isForViewType(@NonNull final Object item, final int adapterPosition, final int collectionPosition) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(@NonNull final List<Object> items, final int adapterPosition, final int collectionPosition) {
|
||||
//noinspection unchecked
|
||||
return getItemId((TItem) items.get(collectionPosition), adapterPosition, collectionPosition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns unique ID of item to support stable ID's logic of RecyclerView's adapter.
|
||||
*
|
||||
* @param item Item in adapter;
|
||||
* @param adapterPosition Position of item in adapter;
|
||||
* @param collectionPosition Position of item in collection that contains item;
|
||||
* @return Unique item ID.
|
||||
*/
|
||||
public long getItemId(@NonNull final TItem item, final int adapterPosition, final int collectionPosition) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(
|
||||
@NonNull final RecyclerView.ViewHolder holder,
|
||||
@NonNull final List<Object> items,
|
||||
final int adapterPosition,
|
||||
final int collectionPosition,
|
||||
@NonNull final List<Object> payloads
|
||||
) {
|
||||
//noinspection unchecked
|
||||
onBindViewHolder((TViewHolder) holder, (TItem) items.get(collectionPosition), adapterPosition, collectionPosition, payloads);
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds item with payloads to created by this object ViewHolder.
|
||||
*
|
||||
* @param holder ViewHolder to bind item to;
|
||||
* @param item Item in adapter;
|
||||
* @param adapterPosition Position of item in adapter;
|
||||
* @param collectionPosition Position of item in collection that contains item;
|
||||
* @param payloads Payloads;
|
||||
*/
|
||||
public abstract void onBindViewHolder(
|
||||
@NonNull final TViewHolder holder,
|
||||
@NonNull final TItem item,
|
||||
final int adapterPosition,
|
||||
final int collectionPosition,
|
||||
@NonNull final List<Object> payloads
|
||||
);
|
||||
|
||||
}
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
package ru.touchin.roboswag.components.adapters
|
||||
|
||||
import android.support.v7.util.ListUpdateCallback
|
||||
import android.support.v7.widget.RecyclerView
|
||||
|
||||
class OffsetAdapterUpdateCallback(private val adapter: RecyclerView.Adapter<*>, private val offsetProvider: () -> Int) : ListUpdateCallback {
|
||||
|
||||
override fun onInserted(position: Int, count: Int) {
|
||||
adapter.notifyItemRangeInserted(position + offsetProvider(), count)
|
||||
}
|
||||
|
||||
override fun onRemoved(position: Int, count: Int) {
|
||||
adapter.notifyItemRangeRemoved(position + offsetProvider(), count)
|
||||
}
|
||||
|
||||
override fun onMoved(fromPosition: Int, toPosition: Int) {
|
||||
adapter.notifyItemMoved(fromPosition + offsetProvider(), toPosition + offsetProvider())
|
||||
}
|
||||
|
||||
override fun onChanged(position: Int, count: Int, payload: Any?) {
|
||||
adapter.notifyItemRangeChanged(position + offsetProvider(), count, payload)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
package ru.touchin.roboswag.components.adapters;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Objects of such class controls creation and binding of specific type of RecyclerView's ViewHolders.
|
||||
* Such delegates are creating and binding ViewHolders by position in adapter.
|
||||
* Default {@link #getItemViewType} is generating on construction of object.
|
||||
*
|
||||
* @param <TViewHolder> Type of {@link RecyclerView.ViewHolder} of delegate.
|
||||
*/
|
||||
public abstract class PositionAdapterDelegate<TViewHolder extends RecyclerView.ViewHolder> extends AdapterDelegate<TViewHolder> {
|
||||
|
||||
@Override
|
||||
public boolean isForViewType(@NonNull final List<Object> items, final int adapterPosition, final int collectionPosition) {
|
||||
return isForViewType(adapterPosition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if object is processable by this delegate.
|
||||
*
|
||||
* @param adapterPosition Position of item in adapter;
|
||||
* @return True if item is processable by this delegate.
|
||||
*/
|
||||
public abstract boolean isForViewType(final int adapterPosition);
|
||||
|
||||
@Override
|
||||
public long getItemId(@NonNull final List<Object> objects, final int adapterPosition, final int itemsOffset) {
|
||||
return getItemId(adapterPosition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns unique ID of item to support stable ID's logic of RecyclerView's adapter.
|
||||
*
|
||||
* @param adapterPosition Position of item in adapter;
|
||||
* @return Unique item ID.
|
||||
*/
|
||||
public long getItemId(final int adapterPosition) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(
|
||||
@NonNull final RecyclerView.ViewHolder holder,
|
||||
@NonNull final List<Object> items,
|
||||
final int adapterPosition,
|
||||
final int collectionPosition,
|
||||
@NonNull final List<Object> payloads
|
||||
) {
|
||||
//noinspection unchecked
|
||||
onBindViewHolder((TViewHolder) holder, adapterPosition, payloads);
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds position with payloads to ViewHolder.
|
||||
*
|
||||
* @param holder ViewHolder to bind position to;
|
||||
* @param adapterPosition Position of item in adapter;
|
||||
* @param payloads Payloads.
|
||||
*/
|
||||
public void onBindViewHolder(@NonNull final TViewHolder holder, final int adapterPosition, @NonNull final List<Object> payloads) {
|
||||
//do nothing by default
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
package ru.touchin.roboswag.components.extensions
|
||||
|
||||
import kotlin.properties.Delegates
|
||||
import kotlin.properties.ObservableProperty
|
||||
import kotlin.properties.ReadWriteProperty
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
/**
|
||||
* Simple observable delegate only for notification of new value.
|
||||
*/
|
||||
inline fun <T> Delegates.observable(
|
||||
initialValue: T,
|
||||
crossinline onChange: (newValue: T) -> Unit
|
||||
): ReadWriteProperty<Any?, T> = object : ObservableProperty<T>(initialValue) {
|
||||
override fun afterChange(property: KProperty<*>, oldValue: T, newValue: T) = onChange(newValue)
|
||||
}
|
||||
|
||||
inline fun <T> Delegates.distinctUntilChanged(
|
||||
initialValue: T,
|
||||
crossinline onChange: (newValue: T) -> Unit
|
||||
): ReadWriteProperty<Any?, T> = object : ObservableProperty<T>(initialValue) {
|
||||
override fun afterChange(property: KProperty<*>, oldValue: T, newValue: T) =
|
||||
if (newValue != null && oldValue != newValue) onChange(newValue) else Unit
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
package ru.touchin.roboswag.components.extensions
|
||||
|
||||
import android.os.Build
|
||||
import android.view.View
|
||||
|
||||
private const val RIPPLE_EFFECT_DELAY = 150L
|
||||
|
||||
/**
|
||||
* Sets click listener to view. On click it will call something after delay.
|
||||
*
|
||||
* @param delay Delay after which click listener will be called;
|
||||
* @param listener Click listener.
|
||||
*/
|
||||
fun View.setOnRippleClickListener(delay: Long = RIPPLE_EFFECT_DELAY, listener: (View) -> Unit) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
setOnClickListener { view -> postDelayed({ if (hasWindowFocus()) listener(view) }, delay) }
|
||||
} else {
|
||||
setOnClickListener(listener)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
package ru.touchin.roboswag.components.extensions
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.support.annotation.ColorInt
|
||||
import android.support.annotation.ColorRes
|
||||
import android.support.annotation.DrawableRes
|
||||
import android.support.annotation.IdRes
|
||||
import android.support.annotation.StringRes
|
||||
import android.support.v4.content.ContextCompat
|
||||
import android.support.v7.widget.RecyclerView
|
||||
import android.view.View
|
||||
|
||||
fun <T : View> RecyclerView.ViewHolder.findViewById(@IdRes resId: Int): T = itemView.findViewById(resId)
|
||||
|
||||
val RecyclerView.ViewHolder.context: Context
|
||||
get() = itemView.context
|
||||
|
||||
fun RecyclerView.ViewHolder.getText(@StringRes resId: Int): CharSequence = context.getText(resId)
|
||||
|
||||
fun RecyclerView.ViewHolder.getString(@StringRes resId: Int): String = context.getString(resId)
|
||||
|
||||
@SuppressWarnings("SpreadOperator") // it's OK for small arrays
|
||||
fun RecyclerView.ViewHolder.getString(@StringRes resId: Int, vararg args: Any): String = context.getString(resId, *args)
|
||||
|
||||
@ColorInt
|
||||
fun RecyclerView.ViewHolder.getColor(@ColorRes resId: Int): Int = ContextCompat.getColor(context, resId)
|
||||
|
||||
fun RecyclerView.ViewHolder.getColorStateList(@ColorRes resId: Int): ColorStateList? = ContextCompat.getColorStateList(context, resId)
|
||||
|
||||
fun RecyclerView.ViewHolder.getDrawable(@DrawableRes resId: Int): Drawable? = ContextCompat.getDrawable(context, resId)
|
||||
|
|
@ -1,119 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov)
|
||||
*
|
||||
* This file is part of RoboSwag library.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package ru.touchin.roboswag.components.utils.audio;
|
||||
|
||||
import android.bluetooth.BluetoothA2dp;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.media.AudioManager;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.subjects.BehaviorSubject;
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 02/11/2015.
|
||||
* Simple observer of wired or wireless (bluetooth A2DP) headsets state (connected or not).
|
||||
* <br><font color="yellow"> You require android.permission.BLUETOOTH and API level >= 11 if want to observe wireless headset state </font>
|
||||
*/
|
||||
public final class HeadsetStateObserver {
|
||||
|
||||
@NonNull
|
||||
private final AudioManager audioManager;
|
||||
@NonNull
|
||||
private final Observable<Boolean> connectedObservable;
|
||||
|
||||
public HeadsetStateObserver(@NonNull final Context context) {
|
||||
audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
|
||||
connectedObservable = Observable
|
||||
.fromCallable(() -> new IsConnectedReceiver(audioManager))
|
||||
.switchMap(isConnectedReceiver -> Observable.combineLatest(isConnectedReceiver.isWiredConnectedChangedEvent,
|
||||
isConnectedReceiver.isWirelessConnectedChangedEvent,
|
||||
(isWiredConnected, isWirelessConnected) -> isWiredConnected || isWirelessConnected)
|
||||
.distinctUntilChanged()
|
||||
.doOnSubscribe(disposable -> {
|
||||
final IntentFilter headsetStateIntentFilter = new IntentFilter(Intent.ACTION_HEADSET_PLUG);
|
||||
headsetStateIntentFilter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
|
||||
context.registerReceiver(isConnectedReceiver, headsetStateIntentFilter);
|
||||
})
|
||||
.doOnDispose(() -> context.unregisterReceiver(isConnectedReceiver)))
|
||||
.replay(1)
|
||||
.refCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if wired or wireless headset is connected.
|
||||
*
|
||||
* @return True if headset is connected.
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public boolean isConnected() {
|
||||
return audioManager.isWiredHeadsetOn() || audioManager.isBluetoothA2dpOn();
|
||||
}
|
||||
|
||||
/**
|
||||
* Observes connection state of headset.
|
||||
*
|
||||
* @return Returns observable which will provide current connection state and any of it's udpdate.
|
||||
*/
|
||||
@NonNull
|
||||
public Observable<Boolean> observeIsConnected() {
|
||||
return connectedObservable;
|
||||
}
|
||||
|
||||
private static class IsConnectedReceiver extends BroadcastReceiver {
|
||||
|
||||
@NonNull
|
||||
private final BehaviorSubject<Boolean> isWiredConnectedChangedEvent;
|
||||
@NonNull
|
||||
private final BehaviorSubject<Boolean> isWirelessConnectedChangedEvent;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public IsConnectedReceiver(@NonNull final AudioManager audioManager) {
|
||||
super();
|
||||
isWiredConnectedChangedEvent = BehaviorSubject.createDefault(audioManager.isWiredHeadsetOn());
|
||||
isWirelessConnectedChangedEvent = BehaviorSubject.createDefault(audioManager.isBluetoothA2dpOn());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceive(@NonNull final Context context, @NonNull final Intent intent) {
|
||||
if (Intent.ACTION_HEADSET_PLUG.equals(intent.getAction()) && !isInitialStickyBroadcast()) {
|
||||
isWiredConnectedChangedEvent.onNext(intent.getIntExtra("state", 0) != 0);
|
||||
}
|
||||
if (BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED.equals(intent.getAction())) {
|
||||
final int bluetoothState = intent.getIntExtra(BluetoothA2dp.EXTRA_STATE, BluetoothA2dp.STATE_DISCONNECTED);
|
||||
switch (bluetoothState) {
|
||||
case BluetoothA2dp.STATE_DISCONNECTED:
|
||||
isWirelessConnectedChangedEvent.onNext(false);
|
||||
break;
|
||||
case BluetoothA2dp.STATE_CONNECTED:
|
||||
isWirelessConnectedChangedEvent.onNext(true);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,132 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov)
|
||||
*
|
||||
* This file is part of RoboSwag library.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package ru.touchin.roboswag.components.utils.audio;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.ContentObserver;
|
||||
import android.media.AudioManager;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.provider.Settings;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.subjects.PublishSubject;
|
||||
import ru.touchin.roboswag.core.log.Lc;
|
||||
import ru.touchin.roboswag.core.utils.ShouldNotHappenException;
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 02/11/2015.
|
||||
* Simple class to control and observe volume of specific stream type (phone call, music etc.).
|
||||
*/
|
||||
public final class VolumeController {
|
||||
|
||||
@NonNull
|
||||
private final AudioManager audioManager;
|
||||
private final int maxVolume;
|
||||
@NonNull
|
||||
private final Observable<Integer> volumeObservable;
|
||||
@NonNull
|
||||
private final PublishSubject<Integer> selfVolumeChangedEvent = PublishSubject.create();
|
||||
|
||||
public VolumeController(@NonNull final Context context) {
|
||||
this(context, AudioManager.STREAM_MUSIC);
|
||||
}
|
||||
|
||||
public VolumeController(@NonNull final Context context, final int streamType) {
|
||||
audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
|
||||
maxVolume = audioManager.getStreamMaxVolume(streamType);
|
||||
volumeObservable = Observable
|
||||
.fromCallable(VolumeObserver::new)
|
||||
.switchMap(volumeObserver -> selfVolumeChangedEvent
|
||||
.mergeWith(volumeObserver.systemVolumeChangedEvent
|
||||
.map(event -> getVolume())
|
||||
.doOnSubscribe(disposable -> context.getContentResolver()
|
||||
.registerContentObserver(Settings.System.CONTENT_URI, true, volumeObserver))
|
||||
.doOnDispose(() -> context.getContentResolver()
|
||||
.unregisterContentObserver(volumeObserver)))
|
||||
.startWith(getVolume()))
|
||||
.distinctUntilChanged()
|
||||
.replay(1)
|
||||
.refCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* Max volume amount to set.
|
||||
*
|
||||
* @return max volume.
|
||||
*/
|
||||
public int getMaxVolume() {
|
||||
return maxVolume;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets volume.
|
||||
*
|
||||
* @param volume Volume value to set from 0 to {@link #getMaxVolume()}.
|
||||
*/
|
||||
public void setVolume(final int volume) {
|
||||
if (volume < 0 || volume > maxVolume) {
|
||||
Lc.assertion(new ShouldNotHappenException("Volume: " + volume + " out of bounds [0," + maxVolume + ']'));
|
||||
return;
|
||||
}
|
||||
if (getVolume() != volume) {
|
||||
audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0);
|
||||
selfVolumeChangedEvent.onNext(volume);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns volume.
|
||||
*
|
||||
* @return Returns volume value from 0 to {@link #getMaxVolume()}.
|
||||
*/
|
||||
public int getVolume() {
|
||||
return audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
|
||||
}
|
||||
|
||||
/**
|
||||
* Observes current volume.
|
||||
*
|
||||
* @return Observable which will provide current volume and then it's updates.
|
||||
*/
|
||||
@NonNull
|
||||
public Observable<Integer> observeVolume() {
|
||||
return volumeObservable;
|
||||
}
|
||||
|
||||
private static class VolumeObserver extends ContentObserver {
|
||||
|
||||
@NonNull
|
||||
private final PublishSubject<Void> systemVolumeChangedEvent = PublishSubject.create();
|
||||
|
||||
public VolumeObserver() {
|
||||
super(new Handler(Looper.getMainLooper()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChange(final boolean selfChange) {
|
||||
super.onChange(selfChange);
|
||||
systemVolumeChangedEvent.onNext(null);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,323 +0,0 @@
|
|||
/**
|
||||
* Copyright (C) 2015 Wasabeef
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ru.touchin.roboswag.components.utils.images;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.Build;
|
||||
import android.renderscript.Allocation;
|
||||
import android.renderscript.Element;
|
||||
import android.renderscript.RSRuntimeException;
|
||||
import android.renderscript.RenderScript;
|
||||
import android.renderscript.ScriptIntrinsicBlur;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
public final class BlurUtils {
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
|
||||
@Nullable
|
||||
public static Bitmap blurRenderscript(@NonNull final Context context, @NonNull final Bitmap bitmap, final int radius) throws RSRuntimeException {
|
||||
RenderScript rs = null;
|
||||
Allocation input = null;
|
||||
Allocation output = null;
|
||||
ScriptIntrinsicBlur blur = null;
|
||||
try {
|
||||
rs = RenderScript.create(context);
|
||||
rs.setMessageHandler(new RenderScript.RSMessageHandler());
|
||||
input = Allocation.createFromBitmap(rs, bitmap, Allocation.MipmapControl.MIPMAP_NONE,
|
||||
Allocation.USAGE_SCRIPT);
|
||||
output = Allocation.createTyped(rs, input.getType());
|
||||
blur = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));
|
||||
|
||||
blur.setInput(input);
|
||||
blur.setRadius(radius);
|
||||
blur.forEach(output);
|
||||
output.copyTo(bitmap);
|
||||
} finally {
|
||||
if (rs != null) {
|
||||
rs.destroy();
|
||||
}
|
||||
if (input != null) {
|
||||
input.destroy();
|
||||
}
|
||||
if (output != null) {
|
||||
output.destroy();
|
||||
}
|
||||
if (blur != null) {
|
||||
blur.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@SuppressWarnings({"PMD.ExcessiveMethodLength", "PMD.CyclomaticComplexity", "PMD.ModifiedCyclomaticComplexity", "PMD.StdCyclomaticComplexity",
|
||||
"PMD.NcssMethodCount", "PMD.NPathComplexity", "checkstyle:MethodLength", "checkstyle:LocalFinalVariableName",
|
||||
"checkstyle:ArrayTypeStyle", "checkstyle:InnerAssignment", "checkstyle:LocalVariableName"})
|
||||
public static Bitmap blurFast(@NonNull final Bitmap sentBitmap, final int radius, final boolean canReuseInBitmap) {
|
||||
|
||||
// Stack Blur v1.0 from
|
||||
// http://www.quasimondo.com/StackBlurForCanvas/StackBlurDemo.html
|
||||
//
|
||||
// Java Author: Mario Klingemann <mario at quasimondo.com>
|
||||
// http://incubator.quasimondo.com
|
||||
// created Feburary 29, 2004
|
||||
// Android port : Yahel Bouaziz <yahel at kayenko.com>
|
||||
// http://www.kayenko.com
|
||||
// ported april 5th, 2012
|
||||
|
||||
// This is a compromise between Gaussian Blur and Box blur
|
||||
// It creates much better looking blurs than Box Blur, but is
|
||||
// 7x faster than my Gaussian Blur implementation.
|
||||
//
|
||||
// I called it Stack Blur because this describes best how this
|
||||
// filter works internally: it creates a kind of moving stack
|
||||
// of colors whilst scanning through the image. Thereby it
|
||||
// just has to add one new block of color to the right side
|
||||
// of the stack and remove the leftmost color. The remaining
|
||||
// colors on the topmost layer of the stack are either added on
|
||||
// or reduced by one, depending on if they are on the right or
|
||||
// on the left side of the stack.
|
||||
//
|
||||
// If you are using this algorithm in your code please add
|
||||
// the following line:
|
||||
//
|
||||
// Stack Blur Algorithm by Mario Klingemann <mario@quasimondo.com>
|
||||
|
||||
final Bitmap bitmap;
|
||||
if (canReuseInBitmap) {
|
||||
bitmap = sentBitmap;
|
||||
} else {
|
||||
bitmap = sentBitmap.copy(sentBitmap.getConfig(), true);
|
||||
}
|
||||
|
||||
if (radius < 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final int w = bitmap.getWidth();
|
||||
final int h = bitmap.getHeight();
|
||||
|
||||
final int[] pix = new int[w * h];
|
||||
bitmap.getPixels(pix, 0, w, 0, 0, w, h);
|
||||
|
||||
final int wm = w - 1;
|
||||
final int hm = h - 1;
|
||||
final int wh = w * h;
|
||||
final int div = radius + radius + 1;
|
||||
|
||||
final int r[] = new int[wh];
|
||||
final int g[] = new int[wh];
|
||||
final int b[] = new int[wh];
|
||||
int rsum;
|
||||
int gsum;
|
||||
int bsum;
|
||||
int x;
|
||||
int i;
|
||||
int p;
|
||||
int yp;
|
||||
int yi;
|
||||
int yw;
|
||||
final int vmin[] = new int[Math.max(w, h)];
|
||||
|
||||
int divsum = (div + 1) >> 1;
|
||||
divsum *= divsum;
|
||||
final int dv[] = new int[256 * divsum];
|
||||
for (i = 0; i < 256 * divsum; i++) {
|
||||
dv[i] = i / divsum;
|
||||
}
|
||||
|
||||
yw = yi = 0;
|
||||
|
||||
final int[][] stack = new int[div][3];
|
||||
int stackpointer;
|
||||
int stackstart;
|
||||
int[] sir;
|
||||
int rbs;
|
||||
final int r1 = radius + 1;
|
||||
int routsum;
|
||||
int goutsum;
|
||||
int boutsum;
|
||||
int rinsum;
|
||||
int ginsum;
|
||||
int binsum;
|
||||
|
||||
int y;
|
||||
|
||||
for (y = 0; y < h; y++) {
|
||||
rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
|
||||
for (i = -radius; i <= radius; i++) {
|
||||
p = pix[yi + Math.min(wm, Math.max(i, 0))];
|
||||
sir = stack[i + radius];
|
||||
sir[0] = (p & 0xff0000) >> 16;
|
||||
sir[1] = (p & 0x00ff00) >> 8;
|
||||
sir[2] = p & 0x0000ff;
|
||||
rbs = r1 - Math.abs(i);
|
||||
rsum += sir[0] * rbs;
|
||||
gsum += sir[1] * rbs;
|
||||
bsum += sir[2] * rbs;
|
||||
if (i > 0) {
|
||||
rinsum += sir[0];
|
||||
ginsum += sir[1];
|
||||
binsum += sir[2];
|
||||
} else {
|
||||
routsum += sir[0];
|
||||
goutsum += sir[1];
|
||||
boutsum += sir[2];
|
||||
}
|
||||
}
|
||||
stackpointer = radius;
|
||||
|
||||
for (x = 0; x < w; x++) {
|
||||
|
||||
r[yi] = dv[rsum];
|
||||
g[yi] = dv[gsum];
|
||||
b[yi] = dv[bsum];
|
||||
|
||||
rsum -= routsum;
|
||||
gsum -= goutsum;
|
||||
bsum -= boutsum;
|
||||
|
||||
stackstart = stackpointer - radius + div;
|
||||
sir = stack[stackstart % div];
|
||||
|
||||
routsum -= sir[0];
|
||||
goutsum -= sir[1];
|
||||
boutsum -= sir[2];
|
||||
|
||||
if (y == 0) {
|
||||
vmin[x] = Math.min(x + radius + 1, wm);
|
||||
}
|
||||
p = pix[yw + vmin[x]];
|
||||
|
||||
sir[0] = (p & 0xff0000) >> 16;
|
||||
sir[1] = (p & 0x00ff00) >> 8;
|
||||
sir[2] = p & 0x0000ff;
|
||||
|
||||
rinsum += sir[0];
|
||||
ginsum += sir[1];
|
||||
binsum += sir[2];
|
||||
|
||||
rsum += rinsum;
|
||||
gsum += ginsum;
|
||||
bsum += binsum;
|
||||
|
||||
stackpointer = (stackpointer + 1) % div;
|
||||
sir = stack[stackpointer % div];
|
||||
|
||||
routsum += sir[0];
|
||||
goutsum += sir[1];
|
||||
boutsum += sir[2];
|
||||
|
||||
rinsum -= sir[0];
|
||||
ginsum -= sir[1];
|
||||
binsum -= sir[2];
|
||||
|
||||
yi++;
|
||||
}
|
||||
yw += w;
|
||||
}
|
||||
for (x = 0; x < w; x++) {
|
||||
rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
|
||||
yp = -radius * w;
|
||||
for (i = -radius; i <= radius; i++) {
|
||||
yi = Math.max(0, yp) + x;
|
||||
|
||||
sir = stack[i + radius];
|
||||
|
||||
sir[0] = r[yi];
|
||||
sir[1] = g[yi];
|
||||
sir[2] = b[yi];
|
||||
|
||||
rbs = r1 - Math.abs(i);
|
||||
|
||||
rsum += r[yi] * rbs;
|
||||
gsum += g[yi] * rbs;
|
||||
bsum += b[yi] * rbs;
|
||||
|
||||
if (i > 0) {
|
||||
rinsum += sir[0];
|
||||
ginsum += sir[1];
|
||||
binsum += sir[2];
|
||||
} else {
|
||||
routsum += sir[0];
|
||||
goutsum += sir[1];
|
||||
boutsum += sir[2];
|
||||
}
|
||||
|
||||
if (i < hm) {
|
||||
yp += w;
|
||||
}
|
||||
}
|
||||
yi = x;
|
||||
stackpointer = radius;
|
||||
for (y = 0; y < h; y++) {
|
||||
// Preserve alpha channel: ( 0xff000000 & pix[yi] )
|
||||
pix[yi] = (0xff000000 & pix[yi]) | (dv[rsum] << 16) | (dv[gsum] << 8) | dv[bsum];
|
||||
|
||||
rsum -= routsum;
|
||||
gsum -= goutsum;
|
||||
bsum -= boutsum;
|
||||
|
||||
stackstart = stackpointer - radius + div;
|
||||
sir = stack[stackstart % div];
|
||||
|
||||
routsum -= sir[0];
|
||||
goutsum -= sir[1];
|
||||
boutsum -= sir[2];
|
||||
|
||||
if (x == 0) {
|
||||
vmin[y] = Math.min(y + r1, hm) * w;
|
||||
}
|
||||
p = x + vmin[y];
|
||||
|
||||
sir[0] = r[p];
|
||||
sir[1] = g[p];
|
||||
sir[2] = b[p];
|
||||
|
||||
rinsum += sir[0];
|
||||
ginsum += sir[1];
|
||||
binsum += sir[2];
|
||||
|
||||
rsum += rinsum;
|
||||
gsum += ginsum;
|
||||
bsum += binsum;
|
||||
|
||||
stackpointer = (stackpointer + 1) % div;
|
||||
sir = stack[stackpointer];
|
||||
|
||||
routsum += sir[0];
|
||||
goutsum += sir[1];
|
||||
boutsum += sir[2];
|
||||
|
||||
rinsum -= sir[0];
|
||||
ginsum -= sir[1];
|
||||
binsum -= sir[2];
|
||||
|
||||
yi += w;
|
||||
}
|
||||
}
|
||||
|
||||
bitmap.setPixels(pix, 0, w, 0, 0, w, h);
|
||||
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
private BlurUtils() {
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,120 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov)
|
||||
*
|
||||
* This file is part of RoboSwag library.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package ru.touchin.roboswag.components.views;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.ColorInt;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.widget.AppCompatImageView;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.TypedValue;
|
||||
|
||||
import ru.touchin.roboswag.components.R;
|
||||
import ru.touchin.roboswag.components.utils.UiUtils;
|
||||
|
||||
/**
|
||||
* Created by Ilia Kurtov on 07/12/2016.
|
||||
* Simple endless progress bar view in material (round circle) style.
|
||||
* It is able to setup size, stroke width and color.
|
||||
* See MaterialLoadingBar Attributes:
|
||||
* R.styleable#MaterialLoadingBar_size
|
||||
* R.styleable#MaterialLoadingBar_strokeWidth
|
||||
* R.styleable#MaterialLoadingBar_color
|
||||
* Use
|
||||
* R.styleable#MaterialLoadingBar_materialLoadingBarStyle
|
||||
* to set default style of MaterialLoadingBar in your Theme.
|
||||
* Sample:
|
||||
* <style name="MyAppLoadingBar">
|
||||
* <item name="strokeWidth">3dp</item>
|
||||
* <item name="color">@android:color/black</item>
|
||||
* <item name="size">24dp</item>
|
||||
* </style>
|
||||
* <style name="AppTheme" parent="Theme.AppCompat.NoActionBar">
|
||||
* <item name="materialLoadingBarStyle">@style/MyAppLoadingBar</item>
|
||||
* </style>
|
||||
*/
|
||||
public class MaterialLoadingBar extends AppCompatImageView {
|
||||
|
||||
private static int getPrimaryColor(@NonNull final Context context) {
|
||||
final int colorAttr;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
colorAttr = android.R.attr.colorPrimary;
|
||||
} else {
|
||||
colorAttr = context.getResources().getIdentifier("colorPrimary", "attr", context.getPackageName());
|
||||
}
|
||||
final TypedValue outValue = new TypedValue();
|
||||
context.getTheme().resolveAttribute(colorAttr, outValue, true);
|
||||
return outValue.data;
|
||||
}
|
||||
|
||||
private MaterialProgressDrawable progressDrawable;
|
||||
|
||||
public MaterialLoadingBar(@NonNull final Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public MaterialLoadingBar(@NonNull final Context context, @Nullable final AttributeSet attrs) {
|
||||
this(context, attrs, R.attr.materialLoadingBarStyle);
|
||||
}
|
||||
|
||||
public MaterialLoadingBar(@NonNull final Context context, @Nullable final AttributeSet attrs, final int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
|
||||
final TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MaterialLoadingBar,
|
||||
defStyleAttr,
|
||||
0);
|
||||
final int size = (int) typedArray.getDimension(R.styleable.MaterialLoadingBar_size, UiUtils.OfMetrics.dpToPixels(context, 48));
|
||||
final int color = typedArray.getColor(R.styleable.MaterialLoadingBar_color, getPrimaryColor(context));
|
||||
final float strokeWidth = typedArray.getDimension(R.styleable.MaterialLoadingBar_strokeWidth,
|
||||
UiUtils.OfMetrics.dpToPixels(context, 4));
|
||||
typedArray.recycle();
|
||||
|
||||
progressDrawable = new MaterialProgressDrawable(context, size);
|
||||
setColor(color);
|
||||
progressDrawable.setStrokeWidth(strokeWidth);
|
||||
setScaleType(ScaleType.CENTER);
|
||||
setImageDrawable(progressDrawable);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
progressDrawable.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetachedFromWindow() {
|
||||
progressDrawable.stop();
|
||||
super.onDetachedFromWindow();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set color of loader.
|
||||
*
|
||||
* @param colorInt Color of loader to be set.
|
||||
*/
|
||||
public void setColor(@ColorInt final int colorInt) {
|
||||
progressDrawable.setColor(colorInt);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,292 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov)
|
||||
*
|
||||
* This file is part of RoboSwag library.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package ru.touchin.roboswag.components.views;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.drawable.Animatable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.SystemClock;
|
||||
import android.support.annotation.ColorInt;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import ru.touchin.roboswag.components.utils.UiUtils;
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 01/03/16.
|
||||
* Simple realization of endless progress bar which is looking material-like.
|
||||
*/
|
||||
public class MaterialProgressDrawable extends Drawable implements Runnable, Animatable {
|
||||
|
||||
private static final int UPDATE_INTERVAL = 1000 / 60;
|
||||
|
||||
private static final float DEFAULT_STROKE_WIDTH_DP = 4.5f;
|
||||
private static final Parameters DEFAULT_PARAMETERS = new Parameters(20, 270, 4, 12, 4, 8);
|
||||
|
||||
private final int size;
|
||||
@NonNull
|
||||
private final Paint paint;
|
||||
@NonNull
|
||||
private Parameters parameters = DEFAULT_PARAMETERS;
|
||||
@NonNull
|
||||
private final RectF arcBounds = new RectF();
|
||||
|
||||
private float rotationAngle;
|
||||
private float arcSize;
|
||||
private boolean running;
|
||||
|
||||
public MaterialProgressDrawable(@NonNull final Context context) {
|
||||
this(context, -1);
|
||||
}
|
||||
|
||||
public MaterialProgressDrawable(@NonNull final Context context, final int size) {
|
||||
super();
|
||||
|
||||
this.size = size;
|
||||
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
paint.setStyle(Paint.Style.STROKE);
|
||||
paint.setStrokeWidth(UiUtils.OfMetrics.dpToPixels(context, DEFAULT_STROKE_WIDTH_DP));
|
||||
paint.setColor(Color.BLACK);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntrinsicWidth() {
|
||||
return size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntrinsicHeight() {
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns width of arc.
|
||||
*
|
||||
* @return Width.
|
||||
*/
|
||||
public float getStrokeWidth() {
|
||||
return paint.getStrokeWidth();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets width of arc.
|
||||
*
|
||||
* @param strokeWidth Width.
|
||||
*/
|
||||
public void setStrokeWidth(final float strokeWidth) {
|
||||
paint.setStrokeWidth(strokeWidth);
|
||||
updateArcBounds();
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets color of arc.
|
||||
*
|
||||
* @param color Color.
|
||||
*/
|
||||
public void setColor(@ColorInt final int color) {
|
||||
paint.setColor(color);
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns magic parameters of spinning.
|
||||
*
|
||||
* @return Parameters of spinning.
|
||||
*/
|
||||
@NonNull
|
||||
public Parameters getParameters() {
|
||||
return parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets magic parameters of spinning.
|
||||
*
|
||||
* @param parameters Parameters of spinning.
|
||||
*/
|
||||
public void setParameters(@NonNull final Parameters parameters) {
|
||||
this.parameters = parameters;
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBoundsChange(@NonNull final Rect bounds) {
|
||||
super.onBoundsChange(bounds);
|
||||
updateArcBounds();
|
||||
}
|
||||
|
||||
private void updateArcBounds() {
|
||||
arcBounds.set(getBounds());
|
||||
//HACK: + 1 as anti-aliasing drawing bug workaround
|
||||
final int inset = (int) (paint.getStrokeWidth() / 2) + 1;
|
||||
arcBounds.inset(inset, inset);
|
||||
}
|
||||
|
||||
@SuppressWarnings("PMD.NPathComplexity")
|
||||
@Override
|
||||
public void draw(@NonNull final Canvas canvas) {
|
||||
final boolean isGrowingCycle = (((int) (arcSize / parameters.maxAngle)) % 2) == 0;
|
||||
final float angle = arcSize % parameters.maxAngle;
|
||||
final float shift = (angle / parameters.maxAngle) * parameters.gapAngle;
|
||||
canvas.drawArc(arcBounds, isGrowingCycle ? rotationAngle + shift : rotationAngle + parameters.gapAngle - shift,
|
||||
isGrowingCycle ? angle + parameters.gapAngle : parameters.maxAngle - angle + parameters.gapAngle, false, paint);
|
||||
//TODO: compute based on animation start time
|
||||
rotationAngle += isGrowingCycle ? parameters.rotationMagicNumber1 : parameters.rotationMagicNumber2;
|
||||
arcSize += isGrowingCycle ? parameters.arcMagicNumber1 : parameters.arcMagicNumber2;
|
||||
if (arcSize < 0) {
|
||||
arcSize = 0;
|
||||
}
|
||||
if (isRunning()) {
|
||||
scheduleSelf(this, SystemClock.uptimeMillis() + UPDATE_INTERVAL);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlpha(final int alpha) {
|
||||
paint.setAlpha(alpha);
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColorFilter(@Nullable final ColorFilter colorFilter) {
|
||||
paint.setColorFilter(colorFilter);
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOpacity() {
|
||||
return PixelFormat.TRANSLUCENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
if (!running) {
|
||||
running = true;
|
||||
run();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
if (running) {
|
||||
unscheduleSelf(this);
|
||||
running = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRunning() {
|
||||
return running;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (running) {
|
||||
invalidateSelf();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Some parameters which are using to spin progress bar.
|
||||
*/
|
||||
public static class Parameters {
|
||||
|
||||
private final float gapAngle;
|
||||
private final float maxAngle;
|
||||
private final float rotationMagicNumber1;
|
||||
private final float rotationMagicNumber2;
|
||||
private final float arcMagicNumber1;
|
||||
private final float arcMagicNumber2;
|
||||
|
||||
public Parameters(final float gapAngle, final float maxAngle,
|
||||
final float rotationMagicNumber1, final float rotationMagicNumber2,
|
||||
final float arcMagicNumber1, final float arcMagicNumber2) {
|
||||
this.gapAngle = gapAngle;
|
||||
this.maxAngle = maxAngle;
|
||||
this.rotationMagicNumber1 = rotationMagicNumber1;
|
||||
this.rotationMagicNumber2 = rotationMagicNumber2;
|
||||
this.arcMagicNumber1 = arcMagicNumber1;
|
||||
this.arcMagicNumber2 = arcMagicNumber2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns angle of gap of arc.
|
||||
*
|
||||
* @return Angle of gap.
|
||||
*/
|
||||
public float getGapAngle() {
|
||||
return gapAngle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns maximum angle of arc.
|
||||
*
|
||||
* @return Maximum angle of arc.
|
||||
*/
|
||||
public float getMaxAngle() {
|
||||
return maxAngle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic parameter 1.
|
||||
*
|
||||
* @return Magic.
|
||||
*/
|
||||
public float getRotationMagicNumber1() {
|
||||
return rotationMagicNumber1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic parameter 2.
|
||||
*
|
||||
* @return Magic.
|
||||
*/
|
||||
public float getRotationMagicNumber2() {
|
||||
return rotationMagicNumber2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic parameter 3.
|
||||
*
|
||||
* @return Magic.
|
||||
*/
|
||||
public float getArcMagicNumber1() {
|
||||
return arcMagicNumber1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic parameter 4.
|
||||
*
|
||||
* @return Magic.
|
||||
*/
|
||||
public float getArcMagicNumber2() {
|
||||
return arcMagicNumber2;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,345 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov)
|
||||
*
|
||||
* This file is part of RoboSwag library.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package ru.touchin.roboswag.components.views;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.design.widget.TextInputLayout;
|
||||
import android.support.v7.widget.AppCompatEditText;
|
||||
import android.text.Editable;
|
||||
import android.text.InputType;
|
||||
import android.text.TextUtils;
|
||||
import android.text.TextWatcher;
|
||||
import android.text.method.SingleLineTransformationMethod;
|
||||
import android.text.method.TransformationMethod;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.ViewParent;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputConnection;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import ru.touchin.roboswag.components.R;
|
||||
import ru.touchin.roboswag.components.views.internal.AttributesUtils;
|
||||
import ru.touchin.roboswag.core.log.Lc;
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 18/07/2014.
|
||||
* TextView that supports fonts from Typefaces class
|
||||
*/
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 18/07/2014.
|
||||
* EditText that supports custom typeface and forces developer to specify if this view multiline or not.
|
||||
* Also in debug mode it has common checks for popular bugs.
|
||||
*/
|
||||
@SuppressWarnings("PMD.ConstructorCallsOverridableMethod")
|
||||
//ConstructorCallsOverridableMethod: it's ok as we need to setTypeface
|
||||
public class TypefacedEditText extends AppCompatEditText {
|
||||
|
||||
private static boolean inDebugMode;
|
||||
|
||||
/**
|
||||
* Enables debugging features like checking attributes on inflation.
|
||||
*/
|
||||
public static void setInDebugMode() {
|
||||
inDebugMode = true;
|
||||
}
|
||||
|
||||
private boolean multiline;
|
||||
private boolean constructed;
|
||||
|
||||
@Nullable
|
||||
private OnTextChangedListener onTextChangedListener;
|
||||
|
||||
public TypefacedEditText(@NonNull final Context context) {
|
||||
super(context);
|
||||
initialize(context, null);
|
||||
}
|
||||
|
||||
public TypefacedEditText(@NonNull final Context context, @Nullable final AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
initialize(context, attrs);
|
||||
}
|
||||
|
||||
public TypefacedEditText(@NonNull final Context context, @Nullable final AttributeSet attrs, final int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
initialize(context, attrs);
|
||||
}
|
||||
|
||||
private void initialize(@NonNull final Context context, @Nullable final AttributeSet attrs) {
|
||||
constructed = true;
|
||||
super.setIncludeFontPadding(false);
|
||||
initializeTextChangedListener();
|
||||
if (attrs != null) {
|
||||
final TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.TypefacedEditText);
|
||||
final boolean multiline = typedArray.getBoolean(R.styleable.TypefacedEditText_isMultiline, false);
|
||||
if (multiline) {
|
||||
setMultiline(AttributesUtils.getMaxLinesFromAttrs(context, attrs));
|
||||
} else {
|
||||
setSingleLine();
|
||||
}
|
||||
typedArray.recycle();
|
||||
if (inDebugMode) {
|
||||
checkAttributes(context, attrs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public InputConnection onCreateInputConnection(@NonNull final EditorInfo attrs) {
|
||||
final InputConnection inputConnection = super.onCreateInputConnection(attrs);
|
||||
if (inputConnection != null && attrs.hintText == null) {
|
||||
for (ViewParent parent = getParent(); parent instanceof View; parent = parent.getParent()) {
|
||||
if (parent instanceof TextInputLayout) {
|
||||
attrs.hintText = ((TextInputLayout) parent).getHint();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return inputConnection;
|
||||
}
|
||||
|
||||
private void checkAttributes(@NonNull final Context context, @NonNull final AttributeSet attrs) {
|
||||
final List<String> errors = new ArrayList<>();
|
||||
Boolean multiline = null;
|
||||
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.TypefacedEditText);
|
||||
AttributesUtils.checkAttribute(typedArray, errors, R.styleable.TypefacedEditText_isMultiline, true,
|
||||
"isMultiline required parameter");
|
||||
if (typedArray.hasValue(R.styleable.TypefacedEditText_isMultiline)) {
|
||||
multiline = typedArray.getBoolean(R.styleable.TypefacedEditText_isMultiline, false);
|
||||
}
|
||||
typedArray.recycle();
|
||||
|
||||
try {
|
||||
final Class androidRes = Class.forName("com.android.internal.R$styleable");
|
||||
|
||||
typedArray = context.obtainStyledAttributes(attrs, AttributesUtils.getField(androidRes, "TextView"));
|
||||
AttributesUtils.checkRegularTextViewAttributes(typedArray, androidRes, errors, "isMultiline");
|
||||
checkEditTextSpecificAttributes(typedArray, androidRes, errors);
|
||||
if (multiline != null) {
|
||||
checkMultilineAttributes(typedArray, androidRes, errors, multiline);
|
||||
}
|
||||
} catch (final Exception exception) {
|
||||
Lc.e(exception, "Error during checking attributes");
|
||||
}
|
||||
AttributesUtils.handleErrors(this, errors);
|
||||
typedArray.recycle();
|
||||
}
|
||||
|
||||
private void checkEditTextSpecificAttributes(@NonNull final TypedArray typedArray, @NonNull final Class androidRes,
|
||||
@NonNull final List<String> errors)
|
||||
throws NoSuchFieldException, IllegalAccessException {
|
||||
AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_singleLine"), false,
|
||||
"remove singleLine and use isMultiline");
|
||||
AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_includeFontPadding"), false,
|
||||
"includeFontPadding forbid parameter");
|
||||
AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_ellipsize"), false,
|
||||
"ellipsize forbid parameter");
|
||||
|
||||
if (typedArray.hasValue(AttributesUtils.getField(androidRes, "TextView_hint"))) {
|
||||
AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_textColorHint"), true,
|
||||
"textColorHint required parameter if hint is not null");
|
||||
}
|
||||
|
||||
AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_textSize"), true,
|
||||
"textSize required parameter. If it's dynamic then use '0sp'");
|
||||
AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_inputType"), true,
|
||||
"inputType required parameter");
|
||||
|
||||
final int inputType = typedArray.getInt(AttributesUtils.getField(androidRes, "TextView_inputType"), -1);
|
||||
if (AttributesUtils.isNumberInputType(inputType)) {
|
||||
errors.add("use inputType phone instead of number");
|
||||
}
|
||||
|
||||
AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_imeOptions"), true,
|
||||
"imeOptions required parameter");
|
||||
}
|
||||
|
||||
private void checkMultilineAttributes(@NonNull final TypedArray typedArray, @NonNull final Class androidRes,
|
||||
@NonNull final List<String> errors, final boolean multiline)
|
||||
throws NoSuchFieldException, IllegalAccessException {
|
||||
if (multiline) {
|
||||
if (typedArray.getInt(AttributesUtils.getField(androidRes, "TextView_lines"), -1) == 1) {
|
||||
errors.add("lines should be more than 1 if isMultiline is true");
|
||||
}
|
||||
if (typedArray.getInt(AttributesUtils.getField(androidRes, "TextView_maxLines"), -1) == 1) {
|
||||
errors.add("maxLines should be more than 1 if isMultiline is true");
|
||||
}
|
||||
if (!typedArray.hasValue(AttributesUtils.getField(androidRes, "TextView_maxLines"))
|
||||
&& !typedArray.hasValue(AttributesUtils.getField(androidRes, "TextView_maxLength"))) {
|
||||
errors.add("specify maxLines or maxLength if isMultiline is true");
|
||||
}
|
||||
} else {
|
||||
AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_lines"), false,
|
||||
"remove lines and use isMultiline");
|
||||
AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_maxLines"), false,
|
||||
"maxLines remove and use isMultiline");
|
||||
AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_minLines"), false,
|
||||
"minLines remove and use isMultiline");
|
||||
AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_maxLength"), true,
|
||||
"maxLength required parameter if isMultiline is false");
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeTextChangedListener() {
|
||||
addTextChangedListener(new TextWatcher() {
|
||||
|
||||
@Override
|
||||
public void beforeTextChanged(@NonNull final CharSequence oldText, final int start, final int count, final int after) {
|
||||
//do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(@NonNull final CharSequence inputText, final int start, final int before, final int count) {
|
||||
if (onTextChangedListener != null) {
|
||||
onTextChangedListener.onTextChanged(inputText);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(@NonNull final Editable editable) {
|
||||
//do nothing
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets if view supports multiline text alignment.
|
||||
*
|
||||
* @param maxLines Maximum lines to be set.
|
||||
*/
|
||||
public void setMultiline(final int maxLines) {
|
||||
if (maxLines <= 1) {
|
||||
Lc.assertion("Wrong maxLines: " + maxLines);
|
||||
return;
|
||||
}
|
||||
multiline = true;
|
||||
final TransformationMethod transformationMethod = getTransformationMethod();
|
||||
super.setSingleLine(false);
|
||||
super.setMaxLines(maxLines);
|
||||
if (!(transformationMethod instanceof SingleLineTransformationMethod)) {
|
||||
setTransformationMethod(transformationMethod);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSingleLine(final boolean singleLine) {
|
||||
if (singleLine) {
|
||||
setSingleLine();
|
||||
} else {
|
||||
setMultiline(Integer.MAX_VALUE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSingleLine() {
|
||||
final TransformationMethod transformationMethod = getTransformationMethod();
|
||||
super.setSingleLine(true);
|
||||
if (transformationMethod != null) {
|
||||
/*DEBUG if (!(transformationMethod instanceof SingleLineTransformationMethod)) {
|
||||
Lc.w("SingleLineTransformationMethod method ignored because of previous transformation method: " + transformationMethod);
|
||||
}*/
|
||||
setTransformationMethod(transformationMethod);
|
||||
}
|
||||
setLines(1);
|
||||
multiline = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLines(final int lines) {
|
||||
if (constructed && multiline && lines == 1) {
|
||||
Lc.assertion(new IllegalStateException(AttributesUtils.viewError(this, "lines = 1 is illegal if multiline is set to true")));
|
||||
return;
|
||||
}
|
||||
super.setLines(lines);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMaxLines(final int maxLines) {
|
||||
if (constructed && !multiline && maxLines > 1) {
|
||||
Lc.assertion(new IllegalStateException(AttributesUtils.viewError(this, "maxLines > 1 is illegal if multiline is set to false")));
|
||||
return;
|
||||
}
|
||||
super.setMaxLines(maxLines);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMinLines(final int minLines) {
|
||||
if (constructed && !multiline && minLines > 1) {
|
||||
Lc.assertion(new IllegalStateException(AttributesUtils.viewError(this, "minLines > 1 is illegal if multiline is set to false")));
|
||||
return;
|
||||
}
|
||||
super.setMinLines(minLines);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void setIncludeFontPadding(final boolean includeFontPadding) {
|
||||
if (!constructed) {
|
||||
return;
|
||||
}
|
||||
Lc.assertion(new IllegalStateException(
|
||||
AttributesUtils.viewError(this, "Do not specify font padding as it is hard to make pixel-perfect design with such option")));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEllipsize(@NonNull final TextUtils.TruncateAt ellipsis) {
|
||||
if (!constructed) {
|
||||
return;
|
||||
}
|
||||
Lc.assertion(new IllegalStateException(AttributesUtils.viewError(this, "Do not specify ellipsize for EditText")));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInputType(final int type) {
|
||||
if (AttributesUtils.isNumberInputType(type)) {
|
||||
Lc.assertion(new IllegalStateException(AttributesUtils.viewError(this,
|
||||
"Do not specify number InputType for EditText, use phone instead")));
|
||||
super.setInputType(InputType.TYPE_CLASS_PHONE);
|
||||
return;
|
||||
}
|
||||
super.setInputType(type);
|
||||
}
|
||||
|
||||
public void setOnTextChangedListener(@Nullable final OnTextChangedListener onTextChangedListener) {
|
||||
this.onTextChangedListener = onTextChangedListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simplified variant of {@link TextWatcher}.
|
||||
*/
|
||||
public interface OnTextChangedListener {
|
||||
|
||||
/**
|
||||
* Calls when text have changed.
|
||||
*
|
||||
* @param text New text.
|
||||
*/
|
||||
void onTextChanged(@NonNull CharSequence text);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,467 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov)
|
||||
*
|
||||
* This file is part of RoboSwag library.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package ru.touchin.roboswag.components.views;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.widget.AppCompatTextView;
|
||||
import android.text.TextUtils;
|
||||
import android.text.method.TransformationMethod;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.TypedValue;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import ru.touchin.roboswag.components.R;
|
||||
import ru.touchin.roboswag.components.utils.UiUtils;
|
||||
import ru.touchin.roboswag.components.views.internal.AttributesUtils;
|
||||
import ru.touchin.roboswag.core.log.Lc;
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 18/07/2014.
|
||||
* TextView that supports custom typeface and forces developer to specify {@link LineStrategy}.
|
||||
* Also in debug mode it has common checks for popular bugs.
|
||||
*/
|
||||
@SuppressWarnings("PMD.ConstructorCallsOverridableMethod")
|
||||
//ConstructorCallsOverridableMethod: it's ok as we need to setTypeface
|
||||
public class TypefacedTextView extends AppCompatTextView {
|
||||
|
||||
private static final int SIZE_THRESHOLD = 10000;
|
||||
|
||||
private static final int UNSPECIFIED_MEASURE_SPEC = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
|
||||
private static final int START_SCALABLE_DIFFERENCE = 4;
|
||||
|
||||
private static boolean inDebugMode;
|
||||
|
||||
/**
|
||||
* Enables debugging features like checking attributes on inflation.
|
||||
*/
|
||||
public static void setInDebugMode() {
|
||||
inDebugMode = true;
|
||||
}
|
||||
|
||||
private boolean constructed;
|
||||
@NonNull
|
||||
private LineStrategy lineStrategy = LineStrategy.SINGLE_LINE_ELLIPSIZE;
|
||||
|
||||
public TypefacedTextView(@NonNull final Context context) {
|
||||
super(context);
|
||||
initialize(context, null);
|
||||
}
|
||||
|
||||
public TypefacedTextView(@NonNull final Context context, @Nullable final AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
initialize(context, attrs);
|
||||
}
|
||||
|
||||
public TypefacedTextView(@NonNull final Context context, @Nullable final AttributeSet attrs, final int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
initialize(context, attrs);
|
||||
}
|
||||
|
||||
private void initialize(@NonNull final Context context, @Nullable final AttributeSet attrs) {
|
||||
constructed = true;
|
||||
super.setIncludeFontPadding(false);
|
||||
if (attrs != null) {
|
||||
final TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.TypefacedTextView);
|
||||
final LineStrategy lineStrategy = LineStrategy
|
||||
.byResIndex(typedArray.getInt(R.styleable.TypefacedTextView_lineStrategy, LineStrategy.MULTILINE_ELLIPSIZE.ordinal()));
|
||||
if (lineStrategy.multiline) {
|
||||
setLineStrategy(lineStrategy, AttributesUtils.getMaxLinesFromAttrs(context, attrs));
|
||||
} else {
|
||||
setLineStrategy(lineStrategy);
|
||||
}
|
||||
typedArray.recycle();
|
||||
if (inDebugMode) {
|
||||
checkAttributes(context, attrs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void checkAttributes(@NonNull final Context context, @NonNull final AttributeSet attrs) {
|
||||
final List<String> errors = new ArrayList<>();
|
||||
LineStrategy lineStrategy = null;
|
||||
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.TypefacedTextView);
|
||||
AttributesUtils.checkAttribute(typedArray, errors, R.styleable.TypefacedTextView_lineStrategy, true,
|
||||
"lineStrategy required parameter");
|
||||
if (typedArray.hasValue(R.styleable.TypefacedTextView_lineStrategy)) {
|
||||
lineStrategy = LineStrategy.byResIndex(typedArray.getInt(R.styleable.TypefacedTextView_lineStrategy, -1));
|
||||
}
|
||||
typedArray.recycle();
|
||||
|
||||
try {
|
||||
final Class androidRes = Class.forName("com.android.internal.R$styleable");
|
||||
|
||||
typedArray = context.obtainStyledAttributes(attrs, AttributesUtils.getField(androidRes, "TextView"));
|
||||
AttributesUtils.checkRegularTextViewAttributes(typedArray, androidRes, errors, "lineStrategy");
|
||||
checkTextViewSpecificAttributes(typedArray, androidRes, errors);
|
||||
|
||||
if (lineStrategy != null) {
|
||||
checkLineStrategyAttributes(typedArray, androidRes, errors, lineStrategy);
|
||||
}
|
||||
} catch (final Exception exception) {
|
||||
Lc.e(exception, "Error during checking attributes");
|
||||
}
|
||||
AttributesUtils.handleErrors(this, errors);
|
||||
typedArray.recycle();
|
||||
}
|
||||
|
||||
private void checkTextViewSpecificAttributes(@NonNull final TypedArray typedArray, @NonNull final Class androidRes,
|
||||
@NonNull final List<String> errors)
|
||||
throws NoSuchFieldException, IllegalAccessException {
|
||||
AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_phoneNumber"), false,
|
||||
"phoneNumber forbid parameter");
|
||||
AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_password"), false,
|
||||
"password forbid parameter");
|
||||
AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_numeric"), false,
|
||||
"numeric forbid parameter");
|
||||
AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_inputType"), false,
|
||||
"inputType forbid parameter");
|
||||
AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_imeOptions"), false,
|
||||
"imeOptions forbid parameter");
|
||||
AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_imeActionId"), false,
|
||||
"imeActionId forbid parameter");
|
||||
AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_imeActionLabel"), false,
|
||||
"imeActionLabel forbid parameter");
|
||||
AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_hint"), false,
|
||||
"hint forbid parameter");
|
||||
AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_editable"), false,
|
||||
"editable forbid parameter");
|
||||
AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_digits"), false,
|
||||
"digits forbid parameter");
|
||||
AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_cursorVisible"), false,
|
||||
"cursorVisible forbid parameter");
|
||||
}
|
||||
|
||||
private void checkLineStrategyAttributes(@NonNull final TypedArray typedArray, @NonNull final Class androidRes,
|
||||
@NonNull final List<String> errors, @NonNull final LineStrategy lineStrategy)
|
||||
throws NoSuchFieldException, IllegalAccessException {
|
||||
if (!lineStrategy.scalable) {
|
||||
AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_textSize"), true,
|
||||
"textSize required parameter. If it's dynamic then use '0sp'");
|
||||
}
|
||||
if (lineStrategy.multiline) {
|
||||
if (typedArray.getInt(AttributesUtils.getField(androidRes, "TextView_lines"), -1) == 1) {
|
||||
errors.add("lines should be more than 1 if lineStrategy is true");
|
||||
}
|
||||
if (typedArray.getInt(AttributesUtils.getField(androidRes, "TextView_maxLines"), -1) == 1) {
|
||||
errors.add("maxLines should be more than 1 if lineStrategy is true");
|
||||
}
|
||||
} else {
|
||||
AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_lines"), false,
|
||||
"remove lines and use lineStrategy");
|
||||
AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_maxLines"), false,
|
||||
"remove maxLines and use lineStrategy");
|
||||
AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_minLines"), false,
|
||||
"remove minLines and use lineStrategy");
|
||||
AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_textAllCaps"), false,
|
||||
"remove textAllCaps and use app:textAllCaps");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets behavior of text if there is no space for it in one line.
|
||||
*
|
||||
* @param lineStrategy Specific {@link LineStrategy}.
|
||||
*/
|
||||
public void setLineStrategy(@NonNull final LineStrategy lineStrategy) {
|
||||
setLineStrategy(lineStrategy, Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets behavior of text if there is no space for it in one line.
|
||||
*
|
||||
* @param lineStrategy Specific {@link LineStrategy};
|
||||
* @param maxLines Max lines if line strategy is multiline.
|
||||
*/
|
||||
public void setLineStrategy(@NonNull final LineStrategy lineStrategy, final int maxLines) {
|
||||
this.lineStrategy = lineStrategy;
|
||||
final TransformationMethod transformationMethod = getTransformationMethod();
|
||||
super.setSingleLine(!lineStrategy.multiline);
|
||||
if (transformationMethod != null) {
|
||||
/*DEBUG if (!(transformationMethod instanceof SingleLineTransformationMethod)) {
|
||||
Lc.w("SingleLineTransformationMethod method ignored because of previous transformation method: " + transformationMethod);
|
||||
}*/
|
||||
setTransformationMethod(transformationMethod);
|
||||
}
|
||||
if (lineStrategy.multiline) {
|
||||
super.setMaxLines(maxLines);
|
||||
}
|
||||
switch (lineStrategy) {
|
||||
case SINGLE_LINE_ELLIPSIZE:
|
||||
case MULTILINE_ELLIPSIZE:
|
||||
super.setEllipsize(TextUtils.TruncateAt.END);
|
||||
break;
|
||||
case SINGLE_LINE_MARQUEE:
|
||||
case MULTILINE_MARQUEE:
|
||||
super.setEllipsize(TextUtils.TruncateAt.MARQUEE);
|
||||
break;
|
||||
case SINGLE_LINE_AUTO_SCALE:
|
||||
super.setEllipsize(null);
|
||||
break;
|
||||
case SINGLE_LINE_ELLIPSIZE_MIDDLE:
|
||||
case MULTILINE_ELLIPSIZE_MIDDLE:
|
||||
super.setEllipsize(TextUtils.TruncateAt.MIDDLE);
|
||||
break;
|
||||
default:
|
||||
Lc.assertion("Unknown line strategy: " + lineStrategy);
|
||||
break;
|
||||
}
|
||||
if (lineStrategy.scalable) {
|
||||
requestLayout();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns behavior of text if there is no space for it in one line.
|
||||
*
|
||||
* @return Specific {@link LineStrategy}.
|
||||
*/
|
||||
@NonNull
|
||||
public LineStrategy getLineStrategy() {
|
||||
return lineStrategy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSingleLine() {
|
||||
if (!constructed) {
|
||||
return;
|
||||
}
|
||||
Lc.assertion(new IllegalStateException(AttributesUtils.viewError(this, "Do not specify setSingleLine use setLineStrategy instead")));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSingleLine(final boolean singleLine) {
|
||||
if (!constructed) {
|
||||
return;
|
||||
}
|
||||
Lc.assertion(new IllegalStateException(AttributesUtils.viewError(this, "Do not specify setSingleLine use setLineStrategy instead")));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLines(final int lines) {
|
||||
if (constructed && lineStrategy.multiline && lines == 1) {
|
||||
Lc.assertion(new IllegalStateException(AttributesUtils.viewError(this, "lines = 1 is illegal if lineStrategy is multiline")));
|
||||
return;
|
||||
}
|
||||
super.setLines(lines);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMaxLines(final int maxLines) {
|
||||
if (constructed && !lineStrategy.multiline && maxLines > 1) {
|
||||
Lc.assertion(new IllegalStateException(
|
||||
AttributesUtils.viewError(this, "maxLines > 1 is illegal if lineStrategy is single line")));
|
||||
return;
|
||||
}
|
||||
super.setMaxLines(maxLines);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void setIncludeFontPadding(final boolean includeFontPadding) {
|
||||
if (!constructed) {
|
||||
return;
|
||||
}
|
||||
Lc.assertion(new IllegalStateException(
|
||||
AttributesUtils.viewError(this, "Do not specify font padding as it is hard to make pixel-perfect design with such option")));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMinLines(final int minLines) {
|
||||
if (constructed && !lineStrategy.multiline && minLines > 1) {
|
||||
Lc.assertion(new IllegalStateException(
|
||||
AttributesUtils.viewError(this, "minLines > 1 is illegal if lineStrategy is single line")));
|
||||
return;
|
||||
}
|
||||
super.setMinLines(minLines);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEllipsize(@NonNull final TextUtils.TruncateAt ellipsize) {
|
||||
if (!constructed) {
|
||||
return;
|
||||
}
|
||||
Lc.assertion(new IllegalStateException(AttributesUtils.viewError(this, "Do not specify ellipsize use setLineStrategy instead")));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setText(@Nullable final CharSequence text, @Nullable final BufferType type) {
|
||||
super.setText(text, type);
|
||||
if (constructed && lineStrategy.scalable) {
|
||||
requestLayout();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTextSize(final float size) {
|
||||
if (constructed && lineStrategy.scalable) {
|
||||
Lc.cutAssertion(new IllegalStateException(AttributesUtils.viewError(this, "textSize call is illegal if lineStrategy is scalable")));
|
||||
return;
|
||||
}
|
||||
super.setTextSize(size);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTextSize(final int unit, final float size) {
|
||||
if (constructed && lineStrategy.scalable) {
|
||||
Lc.assertion(new IllegalStateException(AttributesUtils.viewError(this, "textSize call is illegal if lineStrategy is scalable")));
|
||||
return;
|
||||
}
|
||||
super.setTextSize(unit, size);
|
||||
}
|
||||
|
||||
@SuppressLint("WrongCall")
|
||||
//WrongCall: actually this method is always calling from onMeasure
|
||||
private void computeScalableTextSize(final int maxWidth, final int maxHeight) {
|
||||
final int minDifference = (int) UiUtils.OfMetrics.dpToPixels(getContext(), 1);
|
||||
int difference = (int) UiUtils.OfMetrics.dpToPixels(getContext(), START_SCALABLE_DIFFERENCE);
|
||||
ScaleAction scaleAction = ScaleAction.DO_NOTHING;
|
||||
ScaleAction previousScaleAction = ScaleAction.DO_NOTHING;
|
||||
do {
|
||||
switch (scaleAction) {
|
||||
case SCALE_DOWN:
|
||||
if (difference > minDifference) {
|
||||
difference -= minDifference;
|
||||
super.setTextSize(TypedValue.COMPLEX_UNIT_PX, Math.max(0, getTextSize() - difference));
|
||||
} else {
|
||||
super.setTextSize(TypedValue.COMPLEX_UNIT_PX, Math.max(0, getTextSize() - minDifference));
|
||||
if (previousScaleAction == ScaleAction.SCALE_UP) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case SCALE_UP:
|
||||
if (difference > minDifference) {
|
||||
difference -= minDifference;
|
||||
super.setTextSize(TypedValue.COMPLEX_UNIT_PX, Math.max(0, getTextSize() + difference));
|
||||
} else {
|
||||
if (previousScaleAction == ScaleAction.SCALE_DOWN) {
|
||||
return;
|
||||
}
|
||||
super.setTextSize(TypedValue.COMPLEX_UNIT_PX, Math.max(0, getTextSize() + minDifference));
|
||||
}
|
||||
break;
|
||||
case DO_NOTHING:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
super.onMeasure(UNSPECIFIED_MEASURE_SPEC, UNSPECIFIED_MEASURE_SPEC);
|
||||
previousScaleAction = scaleAction;
|
||||
scaleAction = computeScaleAction(maxWidth, maxHeight);
|
||||
}
|
||||
while (scaleAction != ScaleAction.DO_NOTHING);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private ScaleAction computeScaleAction(final int maxWidth, final int maxHeight) {
|
||||
ScaleAction result = ScaleAction.DO_NOTHING;
|
||||
if (maxWidth < getMeasuredWidth()) {
|
||||
result = ScaleAction.SCALE_DOWN;
|
||||
} else if (maxWidth > getMeasuredWidth()) {
|
||||
result = ScaleAction.SCALE_UP;
|
||||
}
|
||||
|
||||
if (maxHeight < getMeasuredHeight()) {
|
||||
result = ScaleAction.SCALE_DOWN;
|
||||
} else if (maxHeight > getMeasuredHeight() && result != ScaleAction.SCALE_DOWN) {
|
||||
result = ScaleAction.SCALE_UP;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
|
||||
final int maxWidth = MeasureSpec.getSize(widthMeasureSpec);
|
||||
final int maxHeight = MeasureSpec.getSize(heightMeasureSpec);
|
||||
if (!constructed || !lineStrategy.scalable || (maxWidth <= 0 && maxHeight <= 0) || TextUtils.isEmpty(getText())) {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
return;
|
||||
}
|
||||
|
||||
computeScalableTextSize(MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.UNSPECIFIED ? maxWidth : SIZE_THRESHOLD,
|
||||
MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.UNSPECIFIED ? maxHeight : SIZE_THRESHOLD);
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
}
|
||||
|
||||
private enum ScaleAction {
|
||||
SCALE_DOWN,
|
||||
SCALE_UP,
|
||||
DO_NOTHING
|
||||
}
|
||||
|
||||
/**
|
||||
* Specific behavior, mostly based on combination of {@link #getEllipsize()} and {@link #getMaxLines()} to specify how view should show text
|
||||
* if there is no space for it on one line.
|
||||
*/
|
||||
public enum LineStrategy {
|
||||
|
||||
/**
|
||||
* Not more than one line and ellipsize text with dots at the end.
|
||||
*/
|
||||
SINGLE_LINE_ELLIPSIZE(false, false),
|
||||
/**
|
||||
* Not more than one line and ellipsize text with marquee at the end.
|
||||
*/
|
||||
SINGLE_LINE_MARQUEE(false, false),
|
||||
/**
|
||||
* Not more than one line and scale text to maximum possible size.
|
||||
*/
|
||||
SINGLE_LINE_AUTO_SCALE(false, true),
|
||||
/**
|
||||
* More than one line and ellipsize text with dots at the end.
|
||||
*/
|
||||
MULTILINE_ELLIPSIZE(true, false),
|
||||
/**
|
||||
* More than one line and ellipsize text with marquee at the end.
|
||||
*/
|
||||
MULTILINE_MARQUEE(true, false),
|
||||
/**
|
||||
* Not more than one line and ellipsize text with dots in the middle.
|
||||
*/
|
||||
SINGLE_LINE_ELLIPSIZE_MIDDLE(false, false),
|
||||
/**
|
||||
* More than one line and ellipsize text with dots in the middle.
|
||||
*/
|
||||
MULTILINE_ELLIPSIZE_MIDDLE(true, false);
|
||||
|
||||
@NonNull
|
||||
public static LineStrategy byResIndex(final int resIndex) {
|
||||
if (resIndex < 0 || resIndex >= values().length) {
|
||||
Lc.assertion("Unexpected resIndex " + resIndex);
|
||||
return MULTILINE_ELLIPSIZE;
|
||||
}
|
||||
return values()[resIndex];
|
||||
}
|
||||
|
||||
private final boolean multiline;
|
||||
private final boolean scalable;
|
||||
|
||||
LineStrategy(final boolean multiline, final boolean scalable) {
|
||||
this.multiline = multiline;
|
||||
this.scalable = scalable;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,164 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov)
|
||||
*
|
||||
* This file is part of RoboSwag library.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package ru.touchin.roboswag.components.views.internal;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.StyleableRes;
|
||||
import android.text.InputType;
|
||||
import android.text.TextUtils;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Collection;
|
||||
|
||||
import ru.touchin.roboswag.components.utils.UiUtils;
|
||||
import ru.touchin.roboswag.core.log.Lc;
|
||||
import ru.touchin.roboswag.core.utils.ShouldNotHappenException;
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 13/06/2016.
|
||||
* Bunch of inner helper library methods to validate attributes of custom views.
|
||||
*/
|
||||
public final class AttributesUtils {
|
||||
|
||||
/**
|
||||
* Gets static field of class.
|
||||
*
|
||||
* @param resourcesClass Class to get field from;
|
||||
* @param fieldName name of field;
|
||||
* @param <T> Type of object that is stored in field;
|
||||
* @return Field value;
|
||||
* @throws NoSuchFieldException Throws on reflection call;
|
||||
* @throws IllegalAccessException Throws on reflection call.
|
||||
*/
|
||||
@NonNull
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T getField(@NonNull final Class resourcesClass, @NonNull final String fieldName)
|
||||
throws NoSuchFieldException, IllegalAccessException {
|
||||
final Field field = resourcesClass.getDeclaredField(fieldName);
|
||||
field.setAccessible(true);
|
||||
return (T) field.get(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if attribute is in array or not and collecterror if attribute missed.
|
||||
*
|
||||
* @param typedArray Array of attributes;
|
||||
* @param errors Errors to collect into;
|
||||
* @param resourceId Id of attribute;
|
||||
* @param required Is parameter have to be in array OR it have not to be in;
|
||||
* @param description Description of error.
|
||||
*/
|
||||
public static void checkAttribute(@NonNull final TypedArray typedArray,
|
||||
@NonNull final Collection<String> errors,
|
||||
@StyleableRes final int resourceId,
|
||||
final boolean required,
|
||||
@NonNull final String description) {
|
||||
if ((required && typedArray.hasValue(resourceId))
|
||||
|| (!required && !typedArray.hasValue(resourceId))) {
|
||||
return;
|
||||
}
|
||||
errors.add(description);
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects regular {@link android.widget.TextView} errors.
|
||||
*
|
||||
* @param typedArray Array of attributes;
|
||||
* @param androidRes Class of styleable attributes;
|
||||
* @param errors Errors to collect into;
|
||||
* @param lineStrategyParameterName name of line strategy parameter;
|
||||
* @throws NoSuchFieldException Throws during getting attribute values through reflection;
|
||||
* @throws IllegalAccessException Throws during getting attribute values through reflection.
|
||||
*/
|
||||
public static void checkRegularTextViewAttributes(@NonNull final TypedArray typedArray, @NonNull final Class androidRes,
|
||||
@NonNull final Collection<String> errors, @NonNull final String lineStrategyParameterName)
|
||||
throws NoSuchFieldException, IllegalAccessException {
|
||||
checkAttribute(typedArray, errors, getField(androidRes, "TextView_fontFamily"), true, "fontFamily required parameter");
|
||||
checkAttribute(typedArray, errors, getField(androidRes, "TextView_includeFontPadding"), false, "includeFontPadding forbid parameter");
|
||||
checkAttribute(typedArray, errors, getField(androidRes, "TextView_singleLine"), false,
|
||||
"remove singleLine and use " + lineStrategyParameterName);
|
||||
checkAttribute(typedArray, errors, getField(androidRes, "TextView_ellipsize"), false,
|
||||
"remove ellipsize and use " + lineStrategyParameterName);
|
||||
checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_textColor"), true,
|
||||
"textColor required parameter. If it's dynamic then use 'android:color/transparent'");
|
||||
}
|
||||
|
||||
/**
|
||||
* Inner helper library method to merge errors in string and assert it.
|
||||
*
|
||||
* @param view View with errors;
|
||||
* @param errors Errors of view.
|
||||
*/
|
||||
public static void handleErrors(@NonNull final View view, @NonNull final Collection<String> errors) {
|
||||
if (!errors.isEmpty()) {
|
||||
final String exceptionText = viewError(view, TextUtils.join("\n", errors));
|
||||
Lc.cutAssertion(new ShouldNotHappenException(exceptionText));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns max lines attribute value for views extended from {@link android.widget.TextView}.
|
||||
*
|
||||
* @param context Context of attributes;
|
||||
* @param attrs TextView based attributes;
|
||||
* @return Max lines value.
|
||||
*/
|
||||
public static int getMaxLinesFromAttrs(@NonNull final Context context, @NonNull final AttributeSet attrs) {
|
||||
try {
|
||||
final Class androidRes = Class.forName("com.android.internal.R$styleable");
|
||||
final TypedArray typedArray = context.obtainStyledAttributes(attrs, AttributesUtils.getField(androidRes, "TextView"));
|
||||
final int result = typedArray.getInt(AttributesUtils.getField(androidRes, "TextView_maxLines"), Integer.MAX_VALUE);
|
||||
typedArray.recycle();
|
||||
return result;
|
||||
} catch (final Exception exception) {
|
||||
return Integer.MAX_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates readable view error.
|
||||
*
|
||||
* @param view View of error;
|
||||
* @param errorText Text of error;
|
||||
* @return Readable error string.
|
||||
*/
|
||||
@NonNull
|
||||
public static String viewError(@NonNull final View view, @NonNull final String errorText) {
|
||||
return "Errors for view id=" + UiUtils.OfViews.getViewIdString(view) + ":\n" + errorText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if input type equals number input type.
|
||||
*
|
||||
* @param inputType Input type to check;
|
||||
* @return true if input type equals number input type.
|
||||
*/
|
||||
public static boolean isNumberInputType(final int inputType) {
|
||||
return inputType == InputType.TYPE_CLASS_NUMBER || inputType == InputType.TYPE_DATETIME_VARIATION_NORMAL;
|
||||
}
|
||||
|
||||
private AttributesUtils() {
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
<translate
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:duration="@integer/fragmentTransitionTime"
|
||||
android:fromXDelta="-50%p"
|
||||
android:interpolator="@android:anim/accelerate_interpolator"
|
||||
android:toXDelta="0"/>
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
<translate
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:duration="@integer/fragmentTransitionTime"
|
||||
android:fromXDelta="100%p"
|
||||
android:interpolator="@android:anim/decelerate_interpolator"
|
||||
android:toXDelta="0"/>
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
<translate
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:duration="@integer/fragmentTransitionTime"
|
||||
android:fromXDelta="0"
|
||||
android:interpolator="@android:anim/decelerate_interpolator"
|
||||
android:toXDelta="-50%p"/>
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
<translate
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:duration="@integer/fragmentTransitionTime"
|
||||
android:fromXDelta="0"
|
||||
android:interpolator="@android:anim/accelerate_interpolator"
|
||||
android:toXDelta="100%p"/>
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2013 The Android Open Source Project
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:interpolator="@android:anim/decelerate_interpolator"
|
||||
android:fromAlpha="0.0" android:toAlpha="1.0"
|
||||
android:duration="@android:integer/config_mediumAnimTime" />
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2013 The Android Open Source Project
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:interpolator="@android:anim/decelerate_interpolator"
|
||||
android:fromAlpha="1.0" android:toAlpha="0.0"
|
||||
android:duration="@android:integer/config_mediumAnimTime" />
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2013 The Android Open Source Project
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<translate
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:duration="@android:integer/config_mediumAnimTime"
|
||||
android:fromXDelta="-100%p"
|
||||
android:toXDelta="0"/>
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2013 The Android Open Source Project
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<translate
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:duration="@android:integer/config_mediumAnimTime"
|
||||
android:fromXDelta="100%p"
|
||||
android:toXDelta="0"/>
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2013 The Android Open Source Project
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<translate
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:duration="@android:integer/config_mediumAnimTime"
|
||||
android:fromXDelta="0"
|
||||
android:toXDelta="-100%p"/>
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2013 The Android Open Source Project
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<translate
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:duration="@android:integer/config_mediumAnimTime"
|
||||
android:fromXDelta="0"
|
||||
android:toXDelta="100%p"/>
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:color="@color/global_black_25">
|
||||
<item android:id="@android:id/mask">
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="@android:color/white"/>
|
||||
</shape>
|
||||
</item>
|
||||
</ripple>
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:color="@color/global_white_25">
|
||||
<item android:id="@android:id/mask">
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="@android:color/white"/>
|
||||
</shape>
|
||||
</item>
|
||||
</ripple>
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@color/global_black_25" android:state_pressed="true"/>
|
||||
</selector>
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@color/global_white_25" android:state_pressed="true"/>
|
||||
</selector>
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<attr name="customTypeface" format="string"/>
|
||||
|
||||
<declare-styleable name="TypefacedTextView">
|
||||
<attr name="lineStrategy" format="enum">
|
||||
<enum name="singleLineEllipsize" value="0"/>
|
||||
<enum name="singleLineMarquee" value="1"/>
|
||||
<enum name="singleLineAutoScale" value="2"/>
|
||||
<enum name="multilineEllipsize" value="3"/>
|
||||
<enum name="multilineMarquee" value="4"/>
|
||||
<enum name="singleLineEllipsizeMiddle" value="5"/>
|
||||
<enum name="multilineLineEllipsizeMiddle" value="6"/>
|
||||
</attr>
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="TypefacedEditText">
|
||||
<attr name="isMultiline" format="boolean"/>
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="MaterialLoadingBar">
|
||||
<attr name="strokeWidth" format="dimension"/>
|
||||
<attr name="color" format="color"/>
|
||||
<attr name="size" format="dimension"/>
|
||||
<attr name="materialLoadingBarStyle" format="reference"/>
|
||||
</declare-styleable>
|
||||
|
||||
</resources>
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<integer name="fragmentTransitionTime">250</integer>
|
||||
</resources>
|
||||
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
compileSdkVersion versions.compileSdk
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 16
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api project(":utils")
|
||||
api project(":logging")
|
||||
|
||||
implementation "com.android.support:support-annotations:$versions.supportLibrary"
|
||||
|
||||
implementation "io.reactivex.rxjava2:rxjava:$versions.rxJava"
|
||||
implementation "io.reactivex.rxjava2:rxandroid:$versions.rxAndroid"
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
<manifest package="ru.touchin.core.observables.storable" />
|
||||
|
|
@ -31,7 +31,6 @@ import ru.touchin.roboswag.core.utils.Optional;
|
|||
import io.reactivex.Completable;
|
||||
import io.reactivex.Single;
|
||||
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 18/03/16.
|
||||
* Store based on {@link SharedPreferences} for {@link ru.touchin.roboswag.core.observables.storable.Storable}.
|
||||
|
|
@ -45,9 +45,13 @@ public final class PreferenceUtils {
|
|||
*/
|
||||
@NonNull
|
||||
public static Storable<String, String, String> stringStorable(@NonNull final String name, @NonNull final SharedPreferences preferences) {
|
||||
return new Storable.Builder<String, String, String>(name, String.class,
|
||||
String.class, new PreferenceStore<>(preferences), new SameTypesConverter<>())
|
||||
.build();
|
||||
return new Storable.Builder<String, String, String>(
|
||||
name,
|
||||
String.class,
|
||||
String.class,
|
||||
new PreferenceStore<String>(preferences),
|
||||
new SameTypesConverter<>()
|
||||
).build();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -59,13 +63,18 @@ public final class PreferenceUtils {
|
|||
* @return {@link Storable} for string.
|
||||
*/
|
||||
@NonNull
|
||||
public static NonNullStorable<String, String, String> stringStorable(@NonNull final String name,
|
||||
@NonNull final SharedPreferences preferences,
|
||||
@NonNull final String defaultValue) {
|
||||
return new Storable.Builder<String, String, String>(name, String.class,
|
||||
String.class, new PreferenceStore<>(preferences), new SameTypesConverter<>())
|
||||
.setDefaultValue(defaultValue)
|
||||
.build();
|
||||
public static NonNullStorable<String, String, String> stringStorable(
|
||||
@NonNull final String name,
|
||||
@NonNull final SharedPreferences preferences,
|
||||
@NonNull final String defaultValue
|
||||
) {
|
||||
return new Storable.Builder<String, String, String>(
|
||||
name,
|
||||
String.class,
|
||||
String.class,
|
||||
new PreferenceStore<String>(preferences),
|
||||
new SameTypesConverter<>()
|
||||
).setDefaultValue(defaultValue).build();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -77,9 +86,13 @@ public final class PreferenceUtils {
|
|||
*/
|
||||
@NonNull
|
||||
public static Storable<String, Long, Long> longStorable(@NonNull final String name, @NonNull final SharedPreferences preferences) {
|
||||
return new Storable.Builder<String, Long, Long>(name, Long.class,
|
||||
Long.class, new PreferenceStore<>(preferences), new SameTypesConverter<>())
|
||||
.build();
|
||||
return new Storable.Builder<String, Long, Long>(
|
||||
name,
|
||||
Long.class,
|
||||
Long.class,
|
||||
new PreferenceStore<Long>(preferences),
|
||||
new SameTypesConverter<>()
|
||||
).build();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -91,13 +104,18 @@ public final class PreferenceUtils {
|
|||
* @return {@link Storable} for long.
|
||||
*/
|
||||
@NonNull
|
||||
public static NonNullStorable<String, Long, Long> longStorable(@NonNull final String name,
|
||||
@NonNull final SharedPreferences preferences,
|
||||
final long defaultValue) {
|
||||
return new Storable.Builder<String, Long, Long>(name, Long.class,
|
||||
Long.class, new PreferenceStore<>(preferences), new SameTypesConverter<>())
|
||||
.setDefaultValue(defaultValue)
|
||||
.build();
|
||||
public static NonNullStorable<String, Long, Long> longStorable(
|
||||
@NonNull final String name,
|
||||
@NonNull final SharedPreferences preferences,
|
||||
final long defaultValue
|
||||
) {
|
||||
return new Storable.Builder<String, Long, Long>(
|
||||
name,
|
||||
Long.class,
|
||||
Long.class,
|
||||
new PreferenceStore<Long>(preferences),
|
||||
new SameTypesConverter<>()
|
||||
).setDefaultValue(defaultValue).build();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -109,9 +127,13 @@ public final class PreferenceUtils {
|
|||
*/
|
||||
@NonNull
|
||||
public static Storable<String, Boolean, Boolean> booleanStorable(@NonNull final String name, @NonNull final SharedPreferences preferences) {
|
||||
return new Storable.Builder<String, Boolean, Boolean>(name, Boolean.class,
|
||||
Boolean.class, new PreferenceStore<>(preferences), new SameTypesConverter<>())
|
||||
.build();
|
||||
return new Storable.Builder<String, Boolean, Boolean>(
|
||||
name,
|
||||
Boolean.class,
|
||||
Boolean.class,
|
||||
new PreferenceStore<Boolean>(preferences),
|
||||
new SameTypesConverter<>()
|
||||
).build();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -123,13 +145,18 @@ public final class PreferenceUtils {
|
|||
* @return {@link Storable} for boolean.
|
||||
*/
|
||||
@NonNull
|
||||
public static NonNullStorable<String, Boolean, Boolean> booleanStorable(@NonNull final String name,
|
||||
@NonNull final SharedPreferences preferences,
|
||||
final boolean defaultValue) {
|
||||
return new Storable.Builder<String, Boolean, Boolean>(name, Boolean.class,
|
||||
Boolean.class, new PreferenceStore<>(preferences), new SameTypesConverter<>())
|
||||
.setDefaultValue(defaultValue)
|
||||
.build();
|
||||
public static NonNullStorable<String, Boolean, Boolean> booleanStorable(
|
||||
@NonNull final String name,
|
||||
@NonNull final SharedPreferences preferences,
|
||||
final boolean defaultValue
|
||||
) {
|
||||
return new Storable.Builder<String, Boolean, Boolean>(
|
||||
name,
|
||||
Boolean.class,
|
||||
Boolean.class,
|
||||
new PreferenceStore<Boolean>(preferences),
|
||||
new SameTypesConverter<>()
|
||||
).setDefaultValue(defaultValue).build();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -141,9 +168,13 @@ public final class PreferenceUtils {
|
|||
*/
|
||||
@NonNull
|
||||
public static Storable<String, Integer, Integer> integerStorable(@NonNull final String name, @NonNull final SharedPreferences preferences) {
|
||||
return new Storable.Builder<String, Integer, Integer>(name, Integer.class,
|
||||
Integer.class, new PreferenceStore<>(preferences), new SameTypesConverter<>())
|
||||
.build();
|
||||
return new Storable.Builder<String, Integer, Integer>(
|
||||
name,
|
||||
Integer.class,
|
||||
Integer.class,
|
||||
new PreferenceStore<Integer>(preferences),
|
||||
new SameTypesConverter<>()
|
||||
).build();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -155,13 +186,18 @@ public final class PreferenceUtils {
|
|||
* @return {@link Storable} for integer.
|
||||
*/
|
||||
@NonNull
|
||||
public static NonNullStorable<String, Integer, Integer> integerStorable(@NonNull final String name,
|
||||
@NonNull final SharedPreferences preferences,
|
||||
final int defaultValue) {
|
||||
return new Storable.Builder<String, Integer, Integer>(name, Integer.class,
|
||||
Integer.class, new PreferenceStore<>(preferences), new SameTypesConverter<>())
|
||||
.setDefaultValue(defaultValue)
|
||||
.build();
|
||||
public static NonNullStorable<String, Integer, Integer> integerStorable(
|
||||
@NonNull final String name,
|
||||
@NonNull final SharedPreferences preferences,
|
||||
final int defaultValue
|
||||
) {
|
||||
return new Storable.Builder<String, Integer, Integer>(
|
||||
name,
|
||||
Integer.class,
|
||||
Integer.class,
|
||||
new PreferenceStore<Integer>(preferences),
|
||||
new SameTypesConverter<>()
|
||||
).setDefaultValue(defaultValue).build();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -173,9 +209,13 @@ public final class PreferenceUtils {
|
|||
*/
|
||||
@NonNull
|
||||
public static Storable<String, Float, Float> floatStorable(@NonNull final String name, @NonNull final SharedPreferences preferences) {
|
||||
return new Storable.Builder<String, Float, Float>(name, Float.class,
|
||||
Float.class, new PreferenceStore<>(preferences), new SameTypesConverter<>())
|
||||
.build();
|
||||
return new Storable.Builder<String, Float, Float>(
|
||||
name,
|
||||
Float.class,
|
||||
Float.class,
|
||||
new PreferenceStore<Float>(preferences),
|
||||
new SameTypesConverter<>()
|
||||
).build();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -187,13 +227,18 @@ public final class PreferenceUtils {
|
|||
* @return {@link Storable} for float.
|
||||
*/
|
||||
@NonNull
|
||||
public static NonNullStorable<String, Float, Float> floatStorable(@NonNull final String name,
|
||||
@NonNull final SharedPreferences preferences,
|
||||
final float defaultValue) {
|
||||
return new Storable.Builder<String, Float, Float>(name, Float.class,
|
||||
Float.class, new PreferenceStore<>(preferences), new SameTypesConverter<>())
|
||||
.setDefaultValue(defaultValue)
|
||||
.build();
|
||||
public static NonNullStorable<String, Float, Float> floatStorable(
|
||||
@NonNull final String name,
|
||||
@NonNull final SharedPreferences preferences,
|
||||
final float defaultValue
|
||||
) {
|
||||
return new Storable.Builder<String, Float, Float>(
|
||||
name,
|
||||
Float.class,
|
||||
Float.class,
|
||||
new PreferenceStore<Float>(preferences),
|
||||
new SameTypesConverter<>()
|
||||
).setDefaultValue(defaultValue).build();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -204,12 +249,18 @@ public final class PreferenceUtils {
|
|||
* @return {@link Storable} for enum.
|
||||
*/
|
||||
@NonNull
|
||||
public static <T extends Enum<T>> Storable<String, T, String> enumStorable(@NonNull final String name,
|
||||
@NonNull final Class<T> enumClass,
|
||||
@NonNull final SharedPreferences preferences) {
|
||||
return new Storable.Builder<String, T, String>(name, enumClass,
|
||||
String.class, new PreferenceStore<>(preferences), new EnumToStringConverter<>())
|
||||
.build();
|
||||
public static <T extends Enum<T>> Storable<String, T, String> enumStorable(
|
||||
@NonNull final String name,
|
||||
@NonNull final Class<T> enumClass,
|
||||
@NonNull final SharedPreferences preferences
|
||||
) {
|
||||
return new Storable.Builder<String, T, String>(
|
||||
name,
|
||||
enumClass,
|
||||
String.class,
|
||||
new PreferenceStore<String>(preferences),
|
||||
new EnumToStringConverter<>()
|
||||
).build();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -221,14 +272,19 @@ public final class PreferenceUtils {
|
|||
* @return {@link Storable} for enum.
|
||||
*/
|
||||
@NonNull
|
||||
public static <T extends Enum<T>> NonNullStorable<String, T, String> enumStorable(@NonNull final String name,
|
||||
@NonNull final Class<T> enumClass,
|
||||
@NonNull final SharedPreferences preferences,
|
||||
@NonNull final T defaultValue) {
|
||||
return new Storable.Builder<String, T, String>(name, enumClass,
|
||||
String.class, new PreferenceStore<>(preferences), new EnumToStringConverter<>())
|
||||
.setDefaultValue(defaultValue)
|
||||
.build();
|
||||
public static <T extends Enum<T>> NonNullStorable<String, T, String> enumStorable(
|
||||
@NonNull final String name,
|
||||
@NonNull final Class<T> enumClass,
|
||||
@NonNull final SharedPreferences preferences,
|
||||
@NonNull final T defaultValue
|
||||
) {
|
||||
return new Storable.Builder<String, T, String>(
|
||||
name,
|
||||
enumClass,
|
||||
String.class,
|
||||
new PreferenceStore<String>(preferences),
|
||||
new EnumToStringConverter<>()
|
||||
).setDefaultValue(defaultValue).build();
|
||||
}
|
||||
|
||||
private PreferenceUtils() {
|
||||
|
|
@ -0,0 +1,299 @@
|
|||
/*
|
||||
Copyright (c) 2016-present, RxJava Contributors.
|
||||
<p>
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
|
||||
compliance with the License. You may obtain a copy of the License at
|
||||
<p>
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
<p>
|
||||
Unless required by applicable law or agreed to in writing, software distributed under the License is
|
||||
distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
|
||||
the License for the specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package ru.touchin.roboswag.core.observables;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.ObservableSource;
|
||||
import io.reactivex.Observer;
|
||||
import io.reactivex.Scheduler;
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.disposables.Disposables;
|
||||
import io.reactivex.functions.Consumer;
|
||||
import io.reactivex.internal.disposables.DisposableHelper;
|
||||
import io.reactivex.internal.fuseable.HasUpstreamObservableSource;
|
||||
import io.reactivex.observables.ConnectableObservable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
|
||||
/**
|
||||
* Returns an observable sequence that stays connected to the source as long as
|
||||
* there is at least one subscription to the observable sequence.
|
||||
*
|
||||
* @param <T> the value type
|
||||
*/
|
||||
@SuppressWarnings({"PMD.CompareObjectsWithEquals", "PMD.AvoidUsingVolatile"})
|
||||
//AvoidUsingVolatile: it's RxJava code
|
||||
public final class ObservableRefCountWithCacheTime<T> extends Observable<T> implements HasUpstreamObservableSource<T> {
|
||||
|
||||
@NonNull
|
||||
private final ConnectableObservable<? extends T> connectableSource;
|
||||
@NonNull
|
||||
private final ObservableSource<T> actualSource;
|
||||
|
||||
@NonNull
|
||||
private volatile CompositeDisposable baseDisposable = new CompositeDisposable();
|
||||
|
||||
@NonNull
|
||||
private final AtomicInteger subscriptionCount = new AtomicInteger();
|
||||
|
||||
/**
|
||||
* Use this lock for every subscription and disconnect action.
|
||||
*/
|
||||
@NonNull
|
||||
private final ReentrantLock lock = new ReentrantLock();
|
||||
|
||||
@NonNull
|
||||
private final Scheduler scheduler = Schedulers.computation();
|
||||
private final long cacheTime;
|
||||
@NonNull
|
||||
private final TimeUnit cacheTimeUnit;
|
||||
@Nullable
|
||||
private Scheduler.Worker worker;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param source observable to apply ref count to
|
||||
*/
|
||||
public ObservableRefCountWithCacheTime(@NonNull final ConnectableObservable<T> source,
|
||||
final long cacheTime, @NonNull final TimeUnit cacheTimeUnit) {
|
||||
super();
|
||||
this.connectableSource = source;
|
||||
this.actualSource = source;
|
||||
this.cacheTime = cacheTime;
|
||||
this.cacheTimeUnit = cacheTimeUnit;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public ObservableSource<T> source() {
|
||||
return actualSource;
|
||||
}
|
||||
|
||||
private void cleanupWorker() {
|
||||
if (worker != null) {
|
||||
worker.dispose();
|
||||
worker = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void subscribeActual(@NonNull final Observer<? super T> subscriber) {
|
||||
|
||||
lock.lock();
|
||||
if (subscriptionCount.incrementAndGet() == 1) {
|
||||
cleanupWorker();
|
||||
final AtomicBoolean writeLocked = new AtomicBoolean(true);
|
||||
|
||||
try {
|
||||
// need to use this overload of connect to ensure that
|
||||
// baseDisposable is set in the case that source is a
|
||||
// synchronous Observable
|
||||
connectableSource.connect(onSubscribe(subscriber, writeLocked));
|
||||
} finally {
|
||||
// need to cover the case where the source is subscribed to
|
||||
// outside of this class thus preventing the Action1 passed
|
||||
// to source.connect above being called
|
||||
if (writeLocked.get()) {
|
||||
// Action1 passed to source.connect was not called
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
// ready to subscribe to source so do it
|
||||
doSubscribe(subscriber, baseDisposable);
|
||||
} finally {
|
||||
// release the read lock
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Consumer<Disposable> onSubscribe(@NonNull final Observer<? super T> observer, @NonNull final AtomicBoolean writeLocked) {
|
||||
return new DisposeConsumer(observer, writeLocked);
|
||||
}
|
||||
|
||||
private void doSubscribe(@NonNull final Observer<? super T> observer, @NonNull final CompositeDisposable currentBase) {
|
||||
// handle disposing from the base CompositeDisposable
|
||||
final Disposable disposable = disconnect(currentBase);
|
||||
|
||||
final ConnectionObserver connectionObserver = new ConnectionObserver(observer, currentBase, disposable);
|
||||
observer.onSubscribe(connectionObserver);
|
||||
|
||||
connectableSource.subscribe(connectionObserver);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Disposable disconnect(@NonNull final CompositeDisposable current) {
|
||||
return Disposables.fromRunnable(new DisposeTask(current));
|
||||
}
|
||||
|
||||
private final class ConnectionObserver extends AtomicReference<Disposable> implements Observer<T>, Disposable {
|
||||
|
||||
private static final long serialVersionUID = 3813126992133394324L;
|
||||
|
||||
@NonNull
|
||||
private final Observer<? super T> subscriber;
|
||||
@NonNull
|
||||
private final CompositeDisposable currentBase;
|
||||
@NonNull
|
||||
private final Disposable resource;
|
||||
|
||||
public ConnectionObserver(@NonNull final Observer<? super T> subscriber, @NonNull final CompositeDisposable currentBase,
|
||||
@NonNull final Disposable resource) {
|
||||
super();
|
||||
this.subscriber = subscriber;
|
||||
this.currentBase = currentBase;
|
||||
this.resource = resource;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSubscribe(@NonNull final Disposable disposable) {
|
||||
DisposableHelper.setOnce(this, disposable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(@NonNull final Throwable throwable) {
|
||||
cleanup();
|
||||
subscriber.onError(throwable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNext(@NonNull final T item) {
|
||||
subscriber.onNext(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete() {
|
||||
cleanup();
|
||||
subscriber.onComplete();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
DisposableHelper.dispose(this);
|
||||
resource.dispose();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDisposed() {
|
||||
return DisposableHelper.isDisposed(get());
|
||||
}
|
||||
|
||||
private void cleanup() {
|
||||
// on error or completion we need to dispose the base CompositeDisposable
|
||||
// and set the subscriptionCount to 0
|
||||
lock.lock();
|
||||
try {
|
||||
if (baseDisposable == currentBase) {
|
||||
cleanupWorker();
|
||||
if (connectableSource instanceof Disposable) {
|
||||
((Disposable) connectableSource).dispose();
|
||||
}
|
||||
|
||||
baseDisposable.dispose();
|
||||
baseDisposable = new CompositeDisposable();
|
||||
subscriptionCount.set(0);
|
||||
}
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private final class DisposeConsumer implements Consumer<Disposable> {
|
||||
|
||||
@NonNull
|
||||
private final Observer<? super T> observer;
|
||||
@NonNull
|
||||
private final AtomicBoolean writeLocked;
|
||||
|
||||
public DisposeConsumer(@NonNull final Observer<? super T> observer, @NonNull final AtomicBoolean writeLocked) {
|
||||
this.observer = observer;
|
||||
this.writeLocked = writeLocked;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(@NonNull final Disposable subscription) {
|
||||
try {
|
||||
baseDisposable.add(subscription);
|
||||
// ready to subscribe to source so do it
|
||||
doSubscribe(observer, baseDisposable);
|
||||
} finally {
|
||||
// release the write lock
|
||||
lock.unlock();
|
||||
writeLocked.set(false);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private final class DisposeTask implements Runnable {
|
||||
|
||||
@NonNull
|
||||
private final CompositeDisposable current;
|
||||
|
||||
public DisposeTask(@NonNull final CompositeDisposable current) {
|
||||
this.current = current;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
lock.lock();
|
||||
try {
|
||||
if (baseDisposable == current && subscriptionCount.decrementAndGet() == 0) {
|
||||
if (worker != null) {
|
||||
worker.dispose();
|
||||
} else {
|
||||
worker = scheduler.createWorker();
|
||||
}
|
||||
worker.schedule(() -> {
|
||||
lock.lock();
|
||||
try {
|
||||
if (subscriptionCount.get() == 0) {
|
||||
cleanupWorker();
|
||||
if (connectableSource instanceof Disposable) {
|
||||
((Disposable) connectableSource).dispose();
|
||||
}
|
||||
|
||||
baseDisposable.dispose();
|
||||
// need a new baseDisposable because once
|
||||
// disposed stays that way
|
||||
baseDisposable = new CompositeDisposable();
|
||||
}
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}, cacheTime, cacheTimeUnit);
|
||||
}
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,472 @@
|
|||
/*
|
||||
* Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov)
|
||||
*
|
||||
* This file is part of RoboSwag library.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package ru.touchin.roboswag.core.observables.storable;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import io.reactivex.Completable;
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.Scheduler;
|
||||
import io.reactivex.Single;
|
||||
import io.reactivex.plugins.RxJavaPlugins;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import io.reactivex.subjects.PublishSubject;
|
||||
import ru.touchin.roboswag.core.log.LcGroup;
|
||||
import ru.touchin.roboswag.core.observables.ObservableRefCountWithCacheTime;
|
||||
import ru.touchin.roboswag.core.utils.ObjectUtils;
|
||||
import ru.touchin.roboswag.core.utils.Optional;
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 04/10/2015.
|
||||
* Base class allows to async access to some store.
|
||||
* Supports conversion between store and actual value. If it is not needed then use {@link SameTypesConverter}
|
||||
* Supports migration from specific version to latest by {@link Migration} object.
|
||||
* Allows to set default value which will be returned if actual value is null.
|
||||
* Allows to declare specific {@link ObserveStrategy}.
|
||||
* Also specific {@link Scheduler} could be specified to not create new scheduler per storable.
|
||||
*
|
||||
* @param <TKey> Type of key to identify object;
|
||||
* @param <TObject> Type of actual object;
|
||||
* @param <TStoreObject> Type of store object. Could be same as {@link TObject};
|
||||
* @param <TReturnObject> Type of actual value operating by Storable. Could be same as {@link TObject}.
|
||||
*/
|
||||
public abstract class BaseStorable<TKey, TObject, TStoreObject, TReturnObject> {
|
||||
|
||||
public static final LcGroup STORABLE_LC_GROUP = new LcGroup("STORABLE");
|
||||
|
||||
private static final long DEFAULT_CACHE_TIME_MILLIS = TimeUnit.SECONDS.toMillis(5);
|
||||
|
||||
@NonNull
|
||||
private static ObserveStrategy getDefaultObserveStrategyFor(@NonNull final Type objectType, @NonNull final Type storeObjectType) {
|
||||
if (objectType instanceof Class && ObjectUtils.isSimpleClass((Class) objectType)) {
|
||||
return ObserveStrategy.CACHE_ACTUAL_VALUE;
|
||||
}
|
||||
if (objectType instanceof Class && ObjectUtils.isSimpleClass((Class) storeObjectType)) {
|
||||
return ObserveStrategy.CACHE_STORE_VALUE;
|
||||
}
|
||||
return ObserveStrategy.NO_CACHE;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private final TKey key;
|
||||
@NonNull
|
||||
private final Type objectType;
|
||||
@NonNull
|
||||
private final Type storeObjectType;
|
||||
@NonNull
|
||||
private final Store<TKey, TStoreObject> store;
|
||||
@NonNull
|
||||
private final Converter<TObject, TStoreObject> converter;
|
||||
@NonNull
|
||||
private final PublishSubject<Optional<TStoreObject>> newStoreValueEvent = PublishSubject.create();
|
||||
@NonNull
|
||||
private final Observable<Optional<TStoreObject>> storeValueObservable;
|
||||
@NonNull
|
||||
private final Observable<Optional<TObject>> valueObservable;
|
||||
@NonNull
|
||||
private final Scheduler scheduler;
|
||||
|
||||
public BaseStorable(@NonNull final BuilderCore<TKey, TObject, TStoreObject> builderCore) {
|
||||
this(builderCore.key, builderCore.objectType, builderCore.storeObjectType,
|
||||
builderCore.store, builderCore.converter, builderCore.observeStrategy,
|
||||
builderCore.migration, builderCore.defaultValue, builderCore.storeScheduler, builderCore.cacheTimeMillis);
|
||||
}
|
||||
|
||||
@SuppressWarnings("PMD.ExcessiveParameterList")
|
||||
//ExcessiveParameterList: that's why we are using builder to create it
|
||||
private BaseStorable(@NonNull final TKey key,
|
||||
@NonNull final Type objectType,
|
||||
@NonNull final Type storeObjectType,
|
||||
@NonNull final Store<TKey, TStoreObject> store,
|
||||
@NonNull final Converter<TObject, TStoreObject> converter,
|
||||
@Nullable final ObserveStrategy observeStrategy,
|
||||
@Nullable final Migration<TKey> migration,
|
||||
@Nullable final TObject defaultValue,
|
||||
@Nullable final Scheduler storeScheduler,
|
||||
final long cacheTimeMillis) {
|
||||
this.key = key;
|
||||
this.objectType = objectType;
|
||||
this.storeObjectType = storeObjectType;
|
||||
this.store = store;
|
||||
this.converter = converter;
|
||||
|
||||
final ObserveStrategy nonNullObserveStrategy
|
||||
= observeStrategy != null ? observeStrategy : getDefaultObserveStrategyFor(objectType, storeObjectType);
|
||||
scheduler = storeScheduler != null ? storeScheduler : Schedulers.from(Executors.newSingleThreadExecutor());
|
||||
storeValueObservable
|
||||
= createStoreValueObservable(nonNullObserveStrategy, migration, defaultValue, cacheTimeMillis);
|
||||
valueObservable = createValueObservable(storeValueObservable, nonNullObserveStrategy, cacheTimeMillis);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Optional<TStoreObject> returnDefaultValueIfNull(@NonNull final Optional<TStoreObject> storeObject, @Nullable final TObject defaultValue)
|
||||
throws Converter.ConversionException {
|
||||
if (storeObject.get() != null || defaultValue == null) {
|
||||
return storeObject;
|
||||
}
|
||||
|
||||
try {
|
||||
return new Optional<>(converter.toStoreObject(objectType, storeObjectType, defaultValue));
|
||||
} catch (final Converter.ConversionException exception) {
|
||||
STORABLE_LC_GROUP.w(exception, "Exception while converting default value of '%s' from '%s' from store %s",
|
||||
key, defaultValue, store);
|
||||
throw exception;
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Observable<Optional<TStoreObject>> createStoreInitialLoadingObservable(@Nullable final Migration<TKey> migration) {
|
||||
final Single<Optional<TStoreObject>> loadObservable = store.loadObject(storeObjectType, key)
|
||||
.doOnError(throwable -> STORABLE_LC_GROUP.w(throwable, "Exception while trying to load value of '%s' from store %s", key, store));
|
||||
return (migration != null ? migration.migrateToLatestVersion(key).andThen(loadObservable) : loadObservable)
|
||||
.subscribeOn(scheduler)
|
||||
.observeOn(scheduler)
|
||||
.toObservable()
|
||||
.replay(1)
|
||||
.refCount()
|
||||
.take(1);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Observable<Optional<TStoreObject>> createStoreValueObservable(@NonNull final ObserveStrategy observeStrategy,
|
||||
@Nullable final Migration<TKey> migration,
|
||||
@Nullable final TObject defaultValue,
|
||||
final long cacheTimeMillis) {
|
||||
final Observable<Optional<TStoreObject>> storeInitialLoadingObservable = createStoreInitialLoadingObservable(migration);
|
||||
final Observable<Optional<TStoreObject>> result = storeInitialLoadingObservable
|
||||
.concatWith(newStoreValueEvent)
|
||||
.map(storeObject -> returnDefaultValueIfNull(storeObject, defaultValue));
|
||||
return observeStrategy == ObserveStrategy.CACHE_STORE_VALUE || observeStrategy == ObserveStrategy.CACHE_STORE_AND_ACTUAL_VALUE
|
||||
? RxJavaPlugins.onAssembly(new ObservableRefCountWithCacheTime<>(result.replay(1), cacheTimeMillis, TimeUnit.MILLISECONDS))
|
||||
: result;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Observable<Optional<TObject>> createValueObservable(@NonNull final Observable<Optional<TStoreObject>> storeValueObservable,
|
||||
@NonNull final ObserveStrategy observeStrategy,
|
||||
final long cacheTimeMillis) {
|
||||
final Observable<Optional<TObject>> result = storeValueObservable
|
||||
.map(storeObject -> {
|
||||
try {
|
||||
return new Optional<>(converter.toObject(objectType, storeObjectType, storeObject.get()));
|
||||
} catch (final Converter.ConversionException exception) {
|
||||
STORABLE_LC_GROUP.w(exception, "Exception while trying to converting value of '%s' from store %s by %s",
|
||||
key, storeObject, store, converter);
|
||||
throw exception;
|
||||
}
|
||||
});
|
||||
return observeStrategy == ObserveStrategy.CACHE_ACTUAL_VALUE || observeStrategy == ObserveStrategy.CACHE_STORE_AND_ACTUAL_VALUE
|
||||
? RxJavaPlugins.onAssembly(new ObservableRefCountWithCacheTime<>(result.replay(1), cacheTimeMillis, TimeUnit.MILLISECONDS))
|
||||
: result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns key of value.
|
||||
*
|
||||
* @return Unique key.
|
||||
*/
|
||||
@NonNull
|
||||
public TKey getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns type of actual object.
|
||||
*
|
||||
* @return Type of actual object.
|
||||
*/
|
||||
@NonNull
|
||||
public Type getObjectType() {
|
||||
return objectType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns type of store object.
|
||||
*
|
||||
* @return Type of store object.
|
||||
*/
|
||||
@NonNull
|
||||
public Type getStoreObjectType() {
|
||||
return storeObjectType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link Store} where store class representation of object is storing.
|
||||
*
|
||||
* @return Store.
|
||||
*/
|
||||
@NonNull
|
||||
public Store<TKey, TStoreObject> getStore() {
|
||||
return store;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link Converter} to convert values from store class to actual and back.
|
||||
*
|
||||
* @return Converter.
|
||||
*/
|
||||
@NonNull
|
||||
public Converter<TObject, TStoreObject> getConverter() {
|
||||
return converter;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Completable internalSet(@Nullable final TObject newValue, final boolean checkForEqualityBeforeSet) {
|
||||
return (checkForEqualityBeforeSet ? storeValueObservable.firstOrError() : Single.just(new Optional<>(null)))
|
||||
.observeOn(scheduler)
|
||||
.flatMapCompletable(oldStoreValue -> {
|
||||
final TStoreObject newStoreValue;
|
||||
try {
|
||||
newStoreValue = converter.toStoreObject(objectType, storeObjectType, newValue);
|
||||
} catch (final Converter.ConversionException exception) {
|
||||
STORABLE_LC_GROUP.w(exception, "Exception while trying to store value of '%s' from store %s by %s",
|
||||
key, newValue, store, converter);
|
||||
return Completable.error(exception);
|
||||
}
|
||||
if (checkForEqualityBeforeSet && ObjectUtils.equals(newStoreValue, oldStoreValue.get())) {
|
||||
return Completable.complete();
|
||||
}
|
||||
return store.storeObject(storeObjectType, key, newStoreValue)
|
||||
.doOnError(throwable -> STORABLE_LC_GROUP.w(throwable,
|
||||
"Exception while trying to store value of '%s' from store %s by %s",
|
||||
key, newValue, store, converter))
|
||||
.observeOn(scheduler)
|
||||
.andThen(Completable.fromAction(() -> {
|
||||
newStoreValueEvent.onNext(new Optional<>(newStoreValue));
|
||||
if (checkForEqualityBeforeSet) {
|
||||
STORABLE_LC_GROUP.i("Value of '%s' changed from '%s' to '%s'", key, oldStoreValue, newStoreValue);
|
||||
} else {
|
||||
STORABLE_LC_GROUP.i("Value of '%s' force changed to '%s'", key, newStoreValue);
|
||||
}
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates observable which is async setting value to store.
|
||||
* It is not checking if stored value equals new value.
|
||||
* In result it will be faster to not get value from store and compare but it will emit item to {@link #observe()} subscribers.
|
||||
* NOTE: It could emit ONLY completed and errors events. It is not providing onNext event!
|
||||
*
|
||||
* @param newValue Value to set;
|
||||
* @return Observable of setting process.
|
||||
*/
|
||||
@NonNull
|
||||
public Completable forceSet(@Nullable final TObject newValue) {
|
||||
return internalSet(newValue, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates observable which is async setting value to store.
|
||||
* It is checking if stored value equals new value.
|
||||
* In result it will take time to get value from store and compare
|
||||
* but it won't emit item to {@link #observe()} subscribers if stored value equals new value.
|
||||
* NOTE: It could emit ONLY completed and errors events. It is not providing onNext event!
|
||||
*
|
||||
* @param newValue Value to set;
|
||||
* @return Observable of setting process.
|
||||
*/
|
||||
@NonNull
|
||||
public Completable set(@Nullable final TObject newValue) {
|
||||
return internalSet(newValue, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets value synchronously. You should NOT use this method normally. Use {@link #set(Object)} asynchronously instead.
|
||||
*
|
||||
* @param newValue Value to set;
|
||||
*/
|
||||
@Deprecated
|
||||
//deprecation: it should be used for debug only and in very rare cases.
|
||||
public void setSync(@Nullable final TObject newValue) {
|
||||
set(newValue).blockingAwait();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
protected Observable<Optional<TObject>> observeOptionalValue() {
|
||||
return valueObservable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns Observable which is emitting item on subscribe and every time when someone have changed value.
|
||||
* It could emit next and error events but not completed.
|
||||
*
|
||||
* @return Returns observable of value.
|
||||
*/
|
||||
@NonNull
|
||||
public abstract Observable<TReturnObject> observe();
|
||||
|
||||
/**
|
||||
* Returns Observable which is emitting only one item on subscribe.
|
||||
* It could emit next and error events but not completed.
|
||||
*
|
||||
* @return Returns observable of value.
|
||||
*/
|
||||
@NonNull
|
||||
public Single<TReturnObject> get() {
|
||||
return observe().firstOrError();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets value synchronously. You should NOT use this method normally. Use {@link #get()} or {@link #observe()} asynchronously instead.
|
||||
*
|
||||
* @return Returns value;
|
||||
*/
|
||||
@Deprecated
|
||||
//deprecation: it should be used for debug only and in very rare cases.
|
||||
@Nullable
|
||||
public TReturnObject getSync() {
|
||||
return get().blockingGet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enum that is representing strategy of observing item from store.
|
||||
*/
|
||||
public enum ObserveStrategy {
|
||||
|
||||
/**
|
||||
* Not caching value so on every {@link #get()} emit it will get value from {@link #getStore()} and converts it with {@link #getConverter()}.
|
||||
*/
|
||||
NO_CACHE,
|
||||
/**
|
||||
* Caching only store value so on every {@link #get()} emit it will converts it with {@link #getConverter()}.
|
||||
* Do not use such strategy if store object could be big (like byte-array of file).
|
||||
*/
|
||||
CACHE_STORE_VALUE,
|
||||
/**
|
||||
* Caching value so it won't spend time for getting value from {@link #getStore()} and converts it by {@link #getConverter()}.
|
||||
* But it will take time for getting value from {@link #getStore()} to set value.
|
||||
* Do not use such strategy if object could be big (like Bitmap or long string).
|
||||
* Do not use such strategy if object is mutable because multiple subscribers could then change it's state.
|
||||
*/
|
||||
CACHE_ACTUAL_VALUE,
|
||||
/**
|
||||
* Caching value so it won't spend time for getting value from {@link #getStore()} and converts it by {@link #getConverter()}.
|
||||
* It won't take time or getting value from {@link #getStore()} to set value.
|
||||
* Do not use such strategy if store object could be big (like byte-array of file).
|
||||
* Do not use such strategy if object could be big (like Bitmap or long string).
|
||||
* Do not use such strategy if object is mutable because multiple subscribers could then change it's state.
|
||||
*/
|
||||
CACHE_STORE_AND_ACTUAL_VALUE
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper class to create various builders.
|
||||
*
|
||||
* @param <TKey> Type of key to identify object;
|
||||
* @param <TObject> Type of actual object;
|
||||
* @param <TStoreObject> Type of store object. Could be same as {@link TObject}.
|
||||
*/
|
||||
public static class BuilderCore<TKey, TObject, TStoreObject> {
|
||||
|
||||
@NonNull
|
||||
protected final TKey key;
|
||||
@NonNull
|
||||
protected final Type objectType;
|
||||
@NonNull
|
||||
private final Type storeObjectType;
|
||||
@NonNull
|
||||
private final Store<TKey, TStoreObject> store;
|
||||
@NonNull
|
||||
private final Converter<TObject, TStoreObject> converter;
|
||||
@Nullable
|
||||
private ObserveStrategy observeStrategy;
|
||||
@Nullable
|
||||
private Migration<TKey> migration;
|
||||
@Nullable
|
||||
private TObject defaultValue;
|
||||
@Nullable
|
||||
private Scheduler storeScheduler;
|
||||
private long cacheTimeMillis;
|
||||
|
||||
protected BuilderCore(@NonNull final TKey key,
|
||||
@NonNull final Type objectType,
|
||||
@NonNull final Type storeObjectType,
|
||||
@NonNull final Store<TKey, TStoreObject> store,
|
||||
@NonNull final Converter<TObject, TStoreObject> converter) {
|
||||
this(key, objectType, storeObjectType, store, converter, null, null, null, null, DEFAULT_CACHE_TIME_MILLIS);
|
||||
}
|
||||
|
||||
protected BuilderCore(@NonNull final BuilderCore<TKey, TObject, TStoreObject> sourceBuilder) {
|
||||
this(sourceBuilder.key, sourceBuilder.objectType, sourceBuilder.storeObjectType,
|
||||
sourceBuilder.store, sourceBuilder.converter, sourceBuilder.observeStrategy,
|
||||
sourceBuilder.migration, sourceBuilder.defaultValue, sourceBuilder.storeScheduler, sourceBuilder.cacheTimeMillis);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"PMD.ExcessiveParameterList", "CPD-START"})
|
||||
//CPD: it is same code as constructor of Storable
|
||||
//ExcessiveParameterList: that's why we are using builder to create it
|
||||
private BuilderCore(@NonNull final TKey key,
|
||||
@NonNull final Type objectType,
|
||||
@NonNull final Type storeObjectType,
|
||||
@NonNull final Store<TKey, TStoreObject> store,
|
||||
@NonNull final Converter<TObject, TStoreObject> converter,
|
||||
@Nullable final ObserveStrategy observeStrategy,
|
||||
@Nullable final Migration<TKey> migration,
|
||||
@Nullable final TObject defaultValue,
|
||||
@Nullable final Scheduler storeScheduler,
|
||||
final long cacheTimeMillis) {
|
||||
this.key = key;
|
||||
this.objectType = objectType;
|
||||
this.storeObjectType = storeObjectType;
|
||||
this.store = store;
|
||||
this.converter = converter;
|
||||
this.observeStrategy = observeStrategy;
|
||||
this.migration = migration;
|
||||
this.defaultValue = defaultValue;
|
||||
this.storeScheduler = storeScheduler;
|
||||
this.cacheTimeMillis = cacheTimeMillis;
|
||||
}
|
||||
|
||||
@SuppressWarnings("CPD-END")
|
||||
protected void setStoreSchedulerInternal(@Nullable final Scheduler storeScheduler) {
|
||||
this.storeScheduler = storeScheduler;
|
||||
}
|
||||
|
||||
protected void setObserveStrategyInternal(@Nullable final ObserveStrategy observeStrategy) {
|
||||
this.observeStrategy = observeStrategy;
|
||||
}
|
||||
|
||||
protected void setMigrationInternal(@NonNull final Migration<TKey> migration) {
|
||||
this.migration = migration;
|
||||
}
|
||||
|
||||
protected void setCacheTimeInternal(final long cacheTime, @NonNull final TimeUnit timeUnit) {
|
||||
this.cacheTimeMillis = timeUnit.toMillis(cacheTime);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected TObject getDefaultValue() {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
protected void setDefaultValueInternal(@NonNull final TObject defaultValue) {
|
||||
this.defaultValue = defaultValue;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov)
|
||||
*
|
||||
* This file is part of RoboSwag library.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package ru.touchin.roboswag.core.observables.storable;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 04/10/2015.
|
||||
* Interface that is providing logic to convert value from specific type to type allowed to store in {@link Store} object and back.
|
||||
*
|
||||
* @param <TObject> Type of original objects;
|
||||
* @param <TStoreObject> Type of objects in store.
|
||||
*/
|
||||
public interface Converter<TObject, TStoreObject> {
|
||||
|
||||
/**
|
||||
* Converts specific object of objectType to object of storeObjectClass allowed to store.
|
||||
*
|
||||
* @param objectType Type of object;
|
||||
* @param storeObjectType Type of store object allowed to store;
|
||||
* @param object Object to be converted to store object;
|
||||
* @return Object that is allowed to store into specific {@link Store};
|
||||
* @throws ConversionException Exception during conversion. Usually it indicates illegal state.
|
||||
*/
|
||||
@Nullable
|
||||
TStoreObject toStoreObject(@NonNull Type objectType, @NonNull Type storeObjectType, @Nullable TObject object)
|
||||
throws ConversionException;
|
||||
|
||||
/**
|
||||
* Converts specific store object of storeObjectClass to object of objectType.
|
||||
*
|
||||
* @param objectType Type of object;
|
||||
* @param storeObjectType Type of store object allowed to store;
|
||||
* @param storeObject Object from specific {@link Store};
|
||||
* @return Object converted from store object;
|
||||
* @throws ConversionException Exception during conversion. Usually it indicates illegal state.
|
||||
*/
|
||||
@Nullable
|
||||
TObject toObject(@NonNull Type objectType, @NonNull Type storeObjectType, @Nullable TStoreObject storeObject)
|
||||
throws ConversionException;
|
||||
|
||||
class ConversionException extends Exception {
|
||||
|
||||
public ConversionException(@NonNull final String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public ConversionException(@NonNull final String message, @NonNull final Throwable throwable) {
|
||||
super(message, throwable);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,165 @@
|
|||
/*
|
||||
* Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov)
|
||||
*
|
||||
* This file is part of RoboSwag library.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package ru.touchin.roboswag.core.observables.storable;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import io.reactivex.Completable;
|
||||
import io.reactivex.Flowable;
|
||||
import io.reactivex.Single;
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 06/10/2015.
|
||||
* Object that allows to migrate some store objects from one version to another by migrators passed into constructor.
|
||||
* Migrating objects should have same types of store key.
|
||||
*
|
||||
* @param <TKey> Type of key of store objects.
|
||||
*/
|
||||
public class Migration<TKey> {
|
||||
|
||||
public static final long DEFAULT_VERSION = -1L;
|
||||
|
||||
private final long latestVersion;
|
||||
@NonNull
|
||||
private final Store<TKey, Long> versionsStore;
|
||||
@NonNull
|
||||
private final List<Migrator<TKey, ?, ?>> migrators;
|
||||
|
||||
@SafeVarargs
|
||||
public Migration(@NonNull final Store<TKey, Long> versionsStore,
|
||||
final long latestVersion,
|
||||
@NonNull final Migrator<TKey, ?, ?>... migrators) {
|
||||
this.versionsStore = versionsStore;
|
||||
this.latestVersion = latestVersion;
|
||||
this.migrators = Arrays.asList(migrators);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Single<Long> loadCurrentVersion(@NonNull final TKey key) {
|
||||
return versionsStore.loadObject(Long.class, key)
|
||||
.map(version -> version.get() != null ? version.get() : DEFAULT_VERSION)
|
||||
.onErrorResumeNext(throwable
|
||||
-> Single.error(new MigrationException(String.format("Can't get version of '%s' from %s", key, versionsStore), throwable)));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Single<Long> makeMigrationChain(@NonNull final TKey key, @NonNull final VersionUpdater versionUpdater) {
|
||||
Single<Long> chain = Single.fromCallable(() -> versionUpdater.initialVersion);
|
||||
for (final Migrator<TKey, ?, ?> migrator : migrators) {
|
||||
chain = chain.flatMap(updatedVersion ->
|
||||
migrator.canMigrate(key, updatedVersion)
|
||||
.flatMap(canMigrate -> canMigrate
|
||||
? migrator.migrate(key, updatedVersion)
|
||||
.doOnSuccess(newVersion
|
||||
-> versionUpdater.updateVersion(newVersion, latestVersion, migrator))
|
||||
: Single.just(updatedVersion)));
|
||||
}
|
||||
return chain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrates some object by key to latest version.
|
||||
*
|
||||
* @param key Key of object to migrate.
|
||||
*/
|
||||
@NonNull
|
||||
public Completable migrateToLatestVersion(@NonNull final TKey key) {
|
||||
return loadCurrentVersion(key)
|
||||
.flatMap(currentVersion -> {
|
||||
final VersionUpdater versionUpdater = new VersionUpdater<>(key, versionsStore, currentVersion);
|
||||
return makeMigrationChain(key, versionUpdater)
|
||||
.doOnSuccess(lastUpdatedVersion -> {
|
||||
if (lastUpdatedVersion < latestVersion) {
|
||||
throw new NextLoopMigrationException();
|
||||
}
|
||||
if (versionUpdater.initialVersion == versionUpdater.oldVersion) {
|
||||
throw new MigrationException(String.format("Version of '%s' not updated from %s",
|
||||
key, versionUpdater.initialVersion));
|
||||
}
|
||||
})
|
||||
.retryWhen(attempts -> attempts
|
||||
.switchMap(throwable -> throwable instanceof NextLoopMigrationException
|
||||
? Flowable.just(new Object()) : Flowable.error(throwable)));
|
||||
})
|
||||
.toCompletable()
|
||||
.andThen(versionsStore.storeObject(Long.class, key, latestVersion))
|
||||
.onErrorResumeNext(throwable -> {
|
||||
if (throwable instanceof MigrationException) {
|
||||
return Completable.error(throwable);
|
||||
}
|
||||
return Completable.error(new MigrationException(String.format("Can't migrate '%s'", key), throwable));
|
||||
});
|
||||
}
|
||||
|
||||
private static class VersionUpdater<TKey> {
|
||||
|
||||
@NonNull
|
||||
private final TKey key;
|
||||
@NonNull
|
||||
private final Store versionsStore;
|
||||
private long oldVersion;
|
||||
private long initialVersion;
|
||||
|
||||
public VersionUpdater(@NonNull final TKey key, @NonNull final Store versionsStore, final long initialVersion) {
|
||||
this.key = key;
|
||||
this.versionsStore = versionsStore;
|
||||
this.oldVersion = initialVersion;
|
||||
this.initialVersion = initialVersion;
|
||||
}
|
||||
|
||||
public void updateVersion(final long updateVersion, final long latestVersion, @NonNull final Migrator migrator) {
|
||||
if (initialVersion > updateVersion) {
|
||||
throw new MigrationException(String.format("Version of '%s' downgraded from %s to %s [from %s by %s]",
|
||||
key, initialVersion, updateVersion, versionsStore, migrator));
|
||||
}
|
||||
if (updateVersion > latestVersion) {
|
||||
throw new MigrationException(
|
||||
String.format("Version of '%s' is %s and higher than latest version %s [from %s by %s]",
|
||||
key, initialVersion, updateVersion, versionsStore, migrator));
|
||||
}
|
||||
if (updateVersion == initialVersion) {
|
||||
throw new MigrationException(String.format("Update version of '%s' equals current version '%s' [from %s by %s]",
|
||||
key, updateVersion, versionsStore, migrator));
|
||||
}
|
||||
oldVersion = initialVersion;
|
||||
initialVersion = updateVersion;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class NextLoopMigrationException extends Exception {
|
||||
}
|
||||
|
||||
public static class MigrationException extends RuntimeException {
|
||||
|
||||
public MigrationException(@NonNull final String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public MigrationException(@NonNull final String message, @NonNull final Throwable throwable) {
|
||||
super(message, throwable);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
* Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov)
|
||||
*
|
||||
* This file is part of RoboSwag library.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package ru.touchin.roboswag.core.observables.storable;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import io.reactivex.Single;
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 05/10/2015.
|
||||
* Abstract class of objects which are able to migrate some values from one version to another.
|
||||
* Also it is able to move objects from one store to another.
|
||||
*
|
||||
* @param <TKey> Type of keys of migrating values;
|
||||
* @param <TOldStoreObject> Type of values from current store;
|
||||
* @param <TNewStoreObject> Type of values from new store. Could be same as {@link TOldStoreObject}.
|
||||
*/
|
||||
public abstract class Migrator<TKey, TOldStoreObject, TNewStoreObject> {
|
||||
|
||||
@NonNull
|
||||
private final Store<TKey, TOldStoreObject> oldStore;
|
||||
@NonNull
|
||||
private final Store<TKey, TNewStoreObject> newStore;
|
||||
|
||||
public Migrator(@NonNull final Store<TKey, TOldStoreObject> oldStore,
|
||||
@NonNull final Store<TKey, TNewStoreObject> newStore) {
|
||||
this.oldStore = oldStore;
|
||||
this.newStore = newStore;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if this migrator can migrate from specific version to some new version.
|
||||
*
|
||||
* @param version Version to migrate from;
|
||||
* @return True if migrator supports migration from this version.
|
||||
*/
|
||||
public abstract boolean supportsMigrationFor(long version);
|
||||
|
||||
/**
|
||||
* Returns {@link Single} that emits if specific object with key of specific version could be migrated by this migrator.
|
||||
*
|
||||
* @param key Key of object to migrate;
|
||||
* @param version Current version of object;
|
||||
* @return {@link Single} that emits true if object with such key and version could be migrated.
|
||||
*/
|
||||
@NonNull
|
||||
public Single<Boolean> canMigrate(@NonNull final TKey key, final long version) {
|
||||
return supportsMigrationFor(version) ? oldStore.contains(key) : Single.just(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Single that migrates object with specific key from some version to migrator's version.
|
||||
*
|
||||
* @param key Key of object to migrate;
|
||||
* @param version Current version of object;
|
||||
* @return {@link Single} that emits new version of object after migration process.
|
||||
*/
|
||||
@NonNull
|
||||
public Single<Long> migrate(@NonNull final TKey key, final long version) {
|
||||
return supportsMigrationFor(version)
|
||||
? migrateInternal(key, version, oldStore, newStore)
|
||||
: Single.error(new Migration.MigrationException(String.format("Version %s of '%s' is not supported by %s", version, key, this)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Single that represents internal migration logic specified by implementation.
|
||||
*
|
||||
* @param key Key of object to migrate;
|
||||
* @param version Current version of object;
|
||||
* @param oldStore Old store of object;
|
||||
* @param newStore new store of object;
|
||||
* @return {@link Single} that emits new version of object after migration process.
|
||||
*/
|
||||
@NonNull
|
||||
protected abstract Single<Long> migrateInternal(@NonNull TKey key,
|
||||
long version,
|
||||
@NonNull Store<TKey, TOldStoreObject> oldStore,
|
||||
@NonNull Store<TKey, TNewStoreObject> newStore);
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,145 @@
|
|||
/*
|
||||
* Copyright (c) 2016 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov)
|
||||
*
|
||||
* This file is part of RoboSwag library.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package ru.touchin.roboswag.core.observables.storable;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.Scheduler;
|
||||
import ru.touchin.roboswag.core.utils.ShouldNotHappenException;
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 04/10/2015.
|
||||
* {@link Storable} that should return not null value on get.
|
||||
* If this rule is violated then it will throw {@link ShouldNotHappenException}.
|
||||
*
|
||||
* @param <TKey> Type of key to identify object;
|
||||
* @param <TObject> Type of actual object;
|
||||
* @param <TStoreObject> Type of store object. Could be same as {@link TObject}.
|
||||
*/
|
||||
public class NonNullStorable<TKey, TObject, TStoreObject> extends BaseStorable<TKey, TObject, TStoreObject, TObject> {
|
||||
|
||||
public NonNullStorable(@NonNull final Builder<TKey, TObject, TStoreObject> builderCore) {
|
||||
super(builderCore);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Observable<TObject> observe() {
|
||||
return observeOptionalValue()
|
||||
.map(optional -> {
|
||||
if (optional.get() == null) {
|
||||
throw new ShouldNotHappenException();
|
||||
}
|
||||
return optional.get();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 15/05/2016.
|
||||
* Builder that is already contains not null default value.
|
||||
*
|
||||
* @param <TKey> Type of key to identify object;
|
||||
* @param <TObject> Type of actual object;
|
||||
* @param <TStoreObject> Type of store object. Could be same as {@link TObject}.
|
||||
*/
|
||||
@SuppressWarnings("CPD-START")
|
||||
//CPD: it is same code as Builder of Storable because it's methods returning this and can't be inherited
|
||||
public static class Builder<TKey, TObject, TStoreObject> extends BuilderCore<TKey, TObject, TStoreObject> {
|
||||
|
||||
public Builder(@NonNull final Storable.Builder<TKey, TObject, TStoreObject> sourceBuilder,
|
||||
@NonNull final TObject defaultValue) {
|
||||
super(sourceBuilder);
|
||||
if (defaultValue == null) {
|
||||
throw new ShouldNotHappenException();
|
||||
}
|
||||
setDefaultValueInternal(defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets specific {@link Scheduler} to store/load/convert values on it.
|
||||
*
|
||||
* @param storeScheduler Scheduler;
|
||||
* @return Builder that allows to specify other fields.
|
||||
*/
|
||||
@NonNull
|
||||
public Builder<TKey, TObject, TStoreObject> setStoreScheduler(@Nullable final Scheduler storeScheduler) {
|
||||
setStoreSchedulerInternal(storeScheduler);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets specific {@link ObserveStrategy} to cache value in memory in specific way.
|
||||
*
|
||||
* @param observeStrategy ObserveStrategy;
|
||||
* @return Builder that allows to specify other fields.
|
||||
*/
|
||||
@NonNull
|
||||
public Builder<TKey, TObject, TStoreObject> setObserveStrategy(@Nullable final ObserveStrategy observeStrategy) {
|
||||
setObserveStrategyInternal(observeStrategy);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets cache time for while value that cached by {@link #setObserveStrategy(ObserveStrategy)}
|
||||
* will be in memory after everyone unsubscribe.
|
||||
* It is important for example for cases when user switches between screens and hide/open app very fast.
|
||||
*
|
||||
* @param cacheTime Cache time value;
|
||||
* @param timeUnit Cache time units.
|
||||
* @return Builder that allows to specify other fields.
|
||||
*/
|
||||
@NonNull
|
||||
public Builder<TKey, TObject, TStoreObject> setCacheTime(final long cacheTime, @NonNull final TimeUnit timeUnit) {
|
||||
setCacheTimeInternal(cacheTime, timeUnit);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets specific {@link Migration} to migrate values from specific version to latest version.
|
||||
*
|
||||
* @param migration Migration;
|
||||
* @return Builder that allows to specify other fields.
|
||||
*/
|
||||
@NonNull
|
||||
public Builder<TKey, TObject, TStoreObject> setMigration(@NonNull final Migration<TKey> migration) {
|
||||
setMigrationInternal(migration);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Building {@link NonNullStorable} object.
|
||||
*
|
||||
* @return New {@link NonNullStorable}.
|
||||
*/
|
||||
@NonNull
|
||||
@SuppressWarnings("CPD-END")
|
||||
public NonNullStorable<TKey, TObject, TStoreObject> build() {
|
||||
if (getDefaultValue() == null) {
|
||||
throw new ShouldNotHappenException();
|
||||
}
|
||||
return new NonNullStorable<>(this);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||