├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── images ├── appertize.png ├── demo.gif ├── google-play.png ├── header.jpg ├── header.png ├── logo-footer.png ├── social │ ├── facebook.png │ ├── google.png │ ├── linkedin.png │ ├── twitter.png │ └── youtube.png └── youtube.png ├── library ├── .gitignore ├── build.gradle ├── gradle-mvn-push.gradle ├── gradle.properties ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── cleveroad │ │ └── adaptivetablelayout │ │ ├── AdaptiveTableAdapter.java │ │ ├── AdaptiveTableDataSetObserver.java │ │ ├── AdaptiveTableLayout.java │ │ ├── AdaptiveTableLayoutSettings.java │ │ ├── AdaptiveTableManager.java │ │ ├── AdaptiveTableManagerRTL.java │ │ ├── AdaptiveTableState.java │ │ ├── BaseDataAdaptiveTableLayoutAdapter.java │ │ ├── DataAdaptiveTableLayoutAdapter.java │ │ ├── DataSetObserverProxy.java │ │ ├── DragAndDropPoints.java │ │ ├── DragAndDropScrollRunnable.java │ │ ├── LayoutDirection.java │ │ ├── LayoutDirectionHelper.java │ │ ├── LinkedAdaptiveTableAdapter.java │ │ ├── LinkedAdaptiveTableAdapterImpl.java │ │ ├── OnItemClickListener.java │ │ ├── OnItemLongClickListener.java │ │ ├── Recycler.java │ │ ├── ScrollHelper.java │ │ ├── ScrollType.java │ │ ├── ShadowHelper.java │ │ ├── SmoothScrollRunnable.java │ │ ├── SparseMatrix.java │ │ ├── ViewHolder.java │ │ ├── ViewHolderImpl.java │ │ └── ViewHolderType.java │ └── res │ ├── drawable │ ├── shadow_bottom.xml │ ├── shadow_left.xml │ ├── shadow_right.xml │ └── shadow_top.xml │ └── values │ ├── attrs.xml │ ├── colors.xml │ ├── dimens.xml │ ├── ids.xml │ └── strings.xml ├── sample ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── assets │ ├── artists.csv │ ├── example.csv │ └── fifa100.csv │ ├── java │ └── com │ │ └── cleveroad │ │ └── sample │ │ ├── SampleApplication.java │ │ ├── adapter │ │ └── SampleLinkedTableAdapter.java │ │ ├── datasource │ │ ├── Constants.java │ │ ├── CsvFileDataSourceImpl.java │ │ ├── TableDataSource.java │ │ ├── UpdateCsvFileLoader.java │ │ └── UpdateFileCallback.java │ │ ├── provider │ │ └── DocumentsProvider.java │ │ ├── ui │ │ ├── CsvPickerFragment.java │ │ ├── SampleActivity.java │ │ ├── TableLayoutFragment.java │ │ └── dialogs │ │ │ ├── AddColumnDialog.java │ │ │ ├── AddRowDialog.java │ │ │ ├── DeleteDialog.java │ │ │ ├── EditItemDialog.java │ │ │ └── SettingsDialog.java │ │ └── utils │ │ ├── ClosableUtil.java │ │ ├── CsvUtils.java │ │ ├── FileUtils.java │ │ ├── PermissionHelper.java │ │ ├── StringUtils.java │ │ └── ValueInterpolator.java │ └── res │ ├── drawable-hdpi │ ├── bg_csv_file.png │ ├── ic_csv_file.png │ ├── ic_csv_new.png │ ├── ic_launcher.png │ └── icon_csv.png │ ├── drawable-ldpi │ └── icon_csv.png │ ├── drawable-mdpi │ ├── bg_csv_file.png │ ├── ic_csv_file.png │ ├── ic_csv_new.png │ ├── ic_launcher.png │ └── icon_csv.png │ ├── drawable-xhdpi │ ├── bg_csv_file.png │ ├── ic_csv_file.png │ ├── ic_csv_new.png │ ├── ic_launcher.png │ └── icon_csv.png │ ├── drawable-xxhdpi │ ├── bg_csv_file.png │ ├── ic_csv_file.png │ ├── ic_csv_new.png │ └── icon_csv.png │ ├── drawable-xxxhdpi │ ├── bg_csv_file.png │ ├── ic_csv_file.png │ ├── ic_csv_new.png │ └── icon_csv.png │ ├── drawable │ ├── ic_arrow_back_white_24dp.xml │ ├── ic_down_black_24dp.xml │ ├── ic_up_black_24dp.xml │ ├── separator_table_first.xml │ ├── separator_table_header.xml │ ├── separator_table_header_first.xml │ └── shadow.xml │ ├── layout │ ├── activity_sample.xml │ ├── dialog_add_column.xml │ ├── dialog_add_row.xml │ ├── dialog_delete.xml │ ├── dialog_edit_item.xml │ ├── dialog_edit_row.xml │ ├── dialog_settings.xml │ ├── fragment_csv_picker.xml │ ├── fragment_tab_layout.xml │ ├── item_body.xml │ ├── item_card.xml │ ├── item_header_column.xml │ ├── item_header_left_top.xml │ └── item_header_row.xml │ ├── menu │ └── table_layout.xml │ ├── mipmap-hdpi │ └── ic_launcher.png │ ├── mipmap-mdpi │ └── ic_launcher.png │ ├── mipmap-xhdpi │ └── ic_launcher.png │ ├── mipmap-xxhdpi │ └── ic_launcher.png │ ├── mipmap-xxxhdpi │ └── ic_launcher.png │ └── values │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Android template 3 | # Built application files 4 | *.apk 5 | *.ap_ 6 | 7 | # Files for the ART/Dalvik VM 8 | *.dex 9 | 10 | # Java class files 11 | *.class 12 | 13 | # Generated files 14 | bin/ 15 | gen/ 16 | out/ 17 | .idea 18 | # Gradle files 19 | .gradle/ 20 | build/ 21 | 22 | # Local configuration file (sdk path, etc) 23 | local.properties 24 | 25 | # Proguard folder generated by Eclipse 26 | proguard/ 27 | 28 | # Log Files 29 | *.log 30 | 31 | # Android Studio Navigation editor temp files 32 | .navigation/ 33 | 34 | # Android Studio captures folder 35 | captures/ 36 | 37 | # Intellij 38 | *.iml 39 | .idea/workspace.xml 40 | 41 | # Keystore files 42 | *.jks 43 | 44 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # AdaptiveTableLayout [![Awesome](https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg)](https://github.com/sindresorhus/awesome) 2 | ![Header image](/images/header.png) 3 | 4 | ## Welcome the new CSV Library AdaptiveTableLayout for Android by Cleveroad 5 | 6 | Pay your attention to our new library that makes it possible to read, edit and write CSV files. If you use Android-based device, you can easily use our library for implementation of all aforementioned actions. In addition, you will be able to change rows and columns, display the picture via link, and align the required data. It will surely help you cope with your tasks faster and make your output higher. AdaptiveTableLayout library is at your disposal. 7 | 8 | ![Demo image](/images/demo.gif) 9 | 10 | #### Take a look at the animation of AdaptiveTableLayout for Android on YouTube in HD quality. For using this library in a valuable way, you can find our CSV Editor app on the Google Play Store or on Appetize. 11 | [![Awesome](/images/youtube.png)](https://www.youtube.com/watch?v=YTwpEPIlhuE)[![Awesome](/images/google-play.png)](https://play.google.com/store/apps/details?id=com.cleveroad.tablelayout)[![Awesome](/images/appertize.png)](https://appetize.io/app/wgacjavwr57fec241bq802gzcg?device=nexus5&scale=75&orientation=portrait&osVersion=7.0) 12 | 13 | The main goal of the library is to apply all its functions in the process of working with CSV files. Moreover, it will give you a competitive edge over others. 14 | 15 | [![Awesome](/images/logo-footer.png)](https://www.cleveroad.com/?utm_source=github&utm_medium=label&utm_campaign=contacts) 16 | 17 | ## Changelog 18 | 19 | Version | Changes 20 | --- | --- 21 | v.1.1.1 | 22 | v.1.1.2 | 23 | v.1.2.1 | 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Cleveroad Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | ext.gradle_tools_version = '3.2.0' 5 | ext.support_version = '28.0.0' 6 | ext.glide_version = '4.7.1' 7 | 8 | repositories { 9 | jcenter() 10 | google() 11 | } 12 | dependencies { 13 | classpath "com.android.tools.build:gradle:$gradle_tools_version" 14 | 15 | // NOTE: Do not place your application dependencies here; they belong 16 | // in the individual module build.gradle files 17 | } 18 | } 19 | 20 | allprojects { 21 | repositories { 22 | jcenter() 23 | google() 24 | } 25 | } 26 | 27 | task clean(type: Delete) { 28 | delete rootProject.buildDir 29 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | VERSION_NAME=1.2.0 19 | VERSION_CODE=8 20 | GROUP=com.cleveroad 21 | 22 | POM_DESCRIPTION=The main goal of the library is to work with CSV files. 23 | POM_URL=https://github.com/Cleveroad/AdaptiveTableLayout 24 | POM_SCM_URL=https://github.com/Cleveroad/AdaptiveTableLayout 25 | POM_SCM_CONNECTION=scm:git@github.com:Cleveroad/AdaptiveTableLayout.git 26 | POM_SCM_DEV_CONNECTION=scm:git@github.com:Cleveroad/AdaptiveTableLayout.git 27 | 28 | POM_LICENCE_NAME=MIT License 29 | POM_LICENCE_URL=http://opensource.org/licenses/MIT 30 | POM_LICENCE_DIST=repo 31 | 32 | POM_DEVELOPER_ID=cleveroad 33 | POM_DEVELOPER_NAME=Cleveroad -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cleveroad/AdaptiveTableLayout/188213b35e05e635497b03fe741799782dde7208/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Nov 15 14:27:29 EET 2018 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /images/appertize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cleveroad/AdaptiveTableLayout/188213b35e05e635497b03fe741799782dde7208/images/appertize.png -------------------------------------------------------------------------------- /images/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cleveroad/AdaptiveTableLayout/188213b35e05e635497b03fe741799782dde7208/images/demo.gif -------------------------------------------------------------------------------- /images/google-play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cleveroad/AdaptiveTableLayout/188213b35e05e635497b03fe741799782dde7208/images/google-play.png -------------------------------------------------------------------------------- /images/header.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cleveroad/AdaptiveTableLayout/188213b35e05e635497b03fe741799782dde7208/images/header.jpg -------------------------------------------------------------------------------- /images/header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cleveroad/AdaptiveTableLayout/188213b35e05e635497b03fe741799782dde7208/images/header.png -------------------------------------------------------------------------------- /images/logo-footer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cleveroad/AdaptiveTableLayout/188213b35e05e635497b03fe741799782dde7208/images/logo-footer.png -------------------------------------------------------------------------------- /images/social/facebook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cleveroad/AdaptiveTableLayout/188213b35e05e635497b03fe741799782dde7208/images/social/facebook.png -------------------------------------------------------------------------------- /images/social/google.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cleveroad/AdaptiveTableLayout/188213b35e05e635497b03fe741799782dde7208/images/social/google.png -------------------------------------------------------------------------------- /images/social/linkedin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cleveroad/AdaptiveTableLayout/188213b35e05e635497b03fe741799782dde7208/images/social/linkedin.png -------------------------------------------------------------------------------- /images/social/twitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cleveroad/AdaptiveTableLayout/188213b35e05e635497b03fe741799782dde7208/images/social/twitter.png -------------------------------------------------------------------------------- /images/social/youtube.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cleveroad/AdaptiveTableLayout/188213b35e05e635497b03fe741799782dde7208/images/social/youtube.png -------------------------------------------------------------------------------- /images/youtube.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cleveroad/AdaptiveTableLayout/188213b35e05e635497b03fe741799782dde7208/images/youtube.png -------------------------------------------------------------------------------- /library/.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Android template 3 | # Built application files 4 | *.apk 5 | *.ap_ 6 | 7 | # Files for the ART/Dalvik VM 8 | *.dex 9 | 10 | # Java class files 11 | *.class 12 | 13 | # Generated files 14 | bin/ 15 | gen/ 16 | out/ 17 | 18 | # Gradle files 19 | .gradle/ 20 | build/ 21 | 22 | # Local configuration file (sdk path, etc) 23 | local.properties 24 | 25 | # Proguard folder generated by Eclipse 26 | proguard/ 27 | 28 | # Log Files 29 | *.log 30 | 31 | # Android Studio Navigation editor temp files 32 | .navigation/ 33 | 34 | # Android Studio captures folder 35 | captures/ 36 | 37 | # Intellij 38 | *.iml 39 | .idea/workspace.xml 40 | 41 | # Keystore files 42 | *.jks 43 | 44 | -------------------------------------------------------------------------------- /library/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 28 5 | 6 | defaultConfig { 7 | minSdkVersion 16 8 | targetSdkVersion 28 9 | versionCode 8 10 | versionName "1.2.0" 11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 12 | 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled true 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | implementation fileTree(include: ['*.jar'], dir: 'libs') 24 | implementation "com.android.support:appcompat-v7:$support_version" 25 | } 26 | //apply from: 'gradle-mvn-push.gradle' 27 | -------------------------------------------------------------------------------- /library/gradle-mvn-push.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Chris Banes 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | apply plugin: 'maven' 18 | apply plugin: 'signing' 19 | 20 | def isReleaseBuild() { 21 | return VERSION_NAME.contains("SNAPSHOT") == false 22 | } 23 | 24 | def getReleaseRepositoryUrl() { 25 | return hasProperty('RELEASE_REPOSITORY_URL') ? RELEASE_REPOSITORY_URL 26 | : "https://oss.sonatype.org/service/local/staging/deploy/maven2/" 27 | } 28 | 29 | def getSnapshotRepositoryUrl() { 30 | return hasProperty('SNAPSHOT_REPOSITORY_URL') ? SNAPSHOT_REPOSITORY_URL 31 | : "https://oss.sonatype.org/content/repositories/snapshots/" 32 | } 33 | 34 | def getRepositoryUsername() { 35 | return hasProperty('NEXUS_USERNAME') ? NEXUS_USERNAME : "" 36 | } 37 | 38 | def getRepositoryPassword() { 39 | return hasProperty('NEXUS_PASSWORD') ? NEXUS_PASSWORD : "" 40 | } 41 | 42 | afterEvaluate { project -> 43 | uploadArchives { 44 | repositories { 45 | mavenDeployer { 46 | beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) } 47 | 48 | pom.groupId = GROUP 49 | pom.artifactId = POM_ARTIFACT_ID 50 | pom.version = VERSION_NAME 51 | 52 | repository(url: getReleaseRepositoryUrl()) { 53 | authentication(userName: getRepositoryUsername(), password: getRepositoryPassword()) 54 | } 55 | snapshotRepository(url: getSnapshotRepositoryUrl()) { 56 | authentication(userName: getRepositoryUsername(), password: getRepositoryPassword()) 57 | } 58 | 59 | pom.project { 60 | name POM_NAME 61 | packaging POM_PACKAGING 62 | description POM_DESCRIPTION 63 | url POM_URL 64 | 65 | scm { 66 | url POM_SCM_URL 67 | connection POM_SCM_CONNECTION 68 | developerConnection POM_SCM_DEV_CONNECTION 69 | } 70 | 71 | licenses { 72 | license { 73 | name POM_LICENCE_NAME 74 | url POM_LICENCE_URL 75 | distribution POM_LICENCE_DIST 76 | } 77 | } 78 | 79 | developers { 80 | developer { 81 | id POM_DEVELOPER_ID 82 | name POM_DEVELOPER_NAME 83 | } 84 | } 85 | } 86 | } 87 | } 88 | } 89 | 90 | signing { 91 | required { isReleaseBuild() && gradle.taskGraph.hasTask("uploadArchives") } 92 | sign configurations.archives 93 | } 94 | 95 | task apklib(type: Zip){ 96 | appendix = extension = 'apklib' 97 | 98 | from 'AndroidManifest.xml' 99 | into('res') { 100 | from 'res' 101 | } 102 | into('src') { 103 | from 'src' 104 | } 105 | } 106 | 107 | 108 | task androidJavadocs(type: Javadoc) { 109 | source = android.sourceSets.main.java.srcDirs 110 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) 111 | options.links("http://docs.oracle.com/javase/7/docs/api/"); 112 | options.linksOffline "http://d.android.com/reference","${android.sdkDirectory}/docs/reference" 113 | exclude '**/BuildConfig.java' 114 | exclude '**/R.java' 115 | failOnError = false 116 | } 117 | 118 | task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) { 119 | classifier = 'javadoc' 120 | from androidJavadocs.destinationDir 121 | } 122 | 123 | task androidSourcesJar(type: Jar) { 124 | classifier = 'sources' 125 | from android.sourceSets.main.java.sourceFiles 126 | } 127 | 128 | artifacts { 129 | archives androidSourcesJar 130 | archives androidJavadocsJar 131 | archives apklib 132 | } 133 | } -------------------------------------------------------------------------------- /library/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=Adaptive Table Layout 2 | POM_ARTIFACT_ID=adaptivetablelayout 3 | POM_PACKAGING=aar -------------------------------------------------------------------------------- /library/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /home/illichenko_cr/Android/Sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /library/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /library/src/main/java/com/cleveroad/adaptivetablelayout/AdaptiveTableAdapter.java: -------------------------------------------------------------------------------- 1 | package com.cleveroad.adaptivetablelayout; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.support.annotation.Nullable; 5 | import android.view.ViewGroup; 6 | 7 | /** 8 | * Base AdaptiveTableAdapter Interface for AdaptiveTableLayout's adapter. 9 | * 10 | * @param Implementation of ViewHolder {@link ViewHolder} 11 | */ 12 | public interface AdaptiveTableAdapter extends AdaptiveTableDataSetObserver { 13 | /** 14 | * @return Item click listener 15 | */ 16 | @Nullable 17 | OnItemClickListener getOnItemClickListener(); 18 | 19 | /** 20 | * Set new item click listener 21 | * 22 | * @param onItemClickListener new item click listener 23 | */ 24 | void setOnItemClickListener(@Nullable OnItemClickListener onItemClickListener); 25 | 26 | /** 27 | * @return Item long click listener 28 | */ 29 | @Nullable 30 | OnItemLongClickListener getOnItemLongClickListener(); 31 | 32 | /** 33 | * Set new item long click listener 34 | * 35 | * @param onItemLongClickListener new item long click listener 36 | */ 37 | void setOnItemLongClickListener(@Nullable OnItemLongClickListener onItemLongClickListener); 38 | 39 | /** 40 | * Register an observer that is called when changes happen to the data used 41 | * by this adapter. 42 | * 43 | * @param observer the object that gets notified when the data set changes. 44 | */ 45 | void registerDataSetObserver(@NonNull AdaptiveTableDataSetObserver observer); 46 | 47 | /** 48 | * Unregister an observer that has previously been registered with this 49 | * adapter via {@link #registerDataSetObserver}. 50 | * 51 | * @param observer the object to unregister. 52 | */ 53 | void unregisterDataSetObserver(@NonNull AdaptiveTableDataSetObserver observer); 54 | 55 | 56 | /** 57 | * How many rows are in the data table represented by this Adapter. 58 | * 59 | * @return count of rows with header 60 | */ 61 | int getRowCount(); 62 | 63 | /** 64 | * How many columns are in the data table represented by this Adapter. 65 | * 66 | * @return count of columns with header 67 | */ 68 | int getColumnCount(); 69 | 70 | /** 71 | * Called when {@link AdaptiveTableLayout} needs a new ITEM {@link ViewHolder} 72 | * 73 | * @param parent The ViewGroup into which the new View will be added after it is bound to 74 | * an adapter position. 75 | * @return A new ViewHolder that holds a View of the given view type. 76 | * @see #onBindViewHolder(ViewHolder, int, int) 77 | */ 78 | @NonNull 79 | VH onCreateItemViewHolder(@NonNull ViewGroup parent); 80 | 81 | /** 82 | * Called when {@link AdaptiveTableLayout} needs a new COLUMN HEADER ITEM {@link ViewHolder} 83 | * 84 | * @param parent The ViewGroup into which the new View will be added after it is bound to 85 | * an adapter position. 86 | * @return A new ViewHolder that holds a View of the given view type. 87 | * @see #onBindHeaderColumnViewHolder(ViewHolder, int) 88 | */ 89 | @NonNull 90 | VH onCreateColumnHeaderViewHolder(@NonNull ViewGroup parent); 91 | 92 | /** 93 | * Called when {@link AdaptiveTableLayout} needs a new ROW HEADER ITEM {@link ViewHolder} 94 | * 95 | * @param parent The ViewGroup into which the new View will be added after it is bound to 96 | * an adapter position. 97 | * @return A new ViewHolder that holds a View of the given view type. 98 | * @see #onBindHeaderRowViewHolder(ViewHolder, int) 99 | */ 100 | @NonNull 101 | VH onCreateRowHeaderViewHolder(@NonNull ViewGroup parent); 102 | 103 | /** 104 | * Called when {@link AdaptiveTableLayout} needs a new LEFT TOP HEADER ITEM {@link ViewHolder} 105 | * 106 | * @param parent The ViewGroup into which the new View will be added after it is bound to 107 | * an adapter position. 108 | * @return A new ViewHolder that holds a View of the given view type. 109 | * @see #onBindLeftTopHeaderViewHolder(ViewHolder) 110 | */ 111 | @NonNull 112 | VH onCreateLeftTopHeaderViewHolder(@NonNull ViewGroup parent); 113 | 114 | 115 | /** 116 | * Called by {@link AdaptiveTableLayout} to display the data at the specified position. This method should 117 | * update the contents of the {@link ViewHolder#getItemView()} to reflect the item at the given 118 | * position. 119 | * 120 | * @param viewHolder The ITEM {@link ViewHolder} which should be updated to represent the contents of the 121 | * item at the given position in the data set. 122 | * @param row The row index of the item within the adapter's data set. 123 | * @param column The column index of the item within the adapter's data set. 124 | */ 125 | void onBindViewHolder(@NonNull VH viewHolder, int row, int column); 126 | 127 | /** 128 | * Called by {@link AdaptiveTableLayout} to display the data at the specified position. This method should 129 | * update the contents of the {@link ViewHolder#getItemView()} to reflect the item at the given 130 | * position. 131 | * 132 | * @param viewHolder The COLUMN HEADER ITEM {@link ViewHolder} which should be updated to represent the contents of the 133 | * item at the given position in the data set. 134 | * @param column The column index of the item within the adapter's data set. 135 | */ 136 | void onBindHeaderColumnViewHolder(@NonNull VH viewHolder, int column); 137 | 138 | /** 139 | * Called by {@link AdaptiveTableLayout} to display the data at the specified position. This method should 140 | * update the contents of the {@link ViewHolder#getItemView()} to reflect the item at the given 141 | * position. 142 | * 143 | * @param viewHolder The ROW HEADER ITEM {@link ViewHolder} which should be updated to represent the contents of the 144 | * item at the given position in the data set. 145 | * @param row The row index of the item within the adapter's data set. 146 | */ 147 | void onBindHeaderRowViewHolder(@NonNull VH viewHolder, int row); 148 | 149 | /** 150 | * Called by {@link AdaptiveTableLayout} to display the data at the specified position. This method should 151 | * update the contents of the {@link ViewHolder#getItemView()} to reflect the item at the given 152 | * position. 153 | * 154 | * @param viewHolder The TOP LEFT HEADER ITEM{@link ViewHolder} which should be updated to represent the contents of the 155 | * item at the given position in the data set. 156 | */ 157 | void onBindLeftTopHeaderViewHolder(@NonNull VH viewHolder); 158 | 159 | 160 | /** 161 | * Return the width of the column. 162 | * 163 | * @param column the column index. 164 | * @return The width of the column, in pixels. 165 | */ 166 | int getColumnWidth(int column); 167 | 168 | 169 | /** 170 | * Return the header column height. 171 | * 172 | * @return The header height of the columns, in pixels. 173 | */ 174 | int getHeaderColumnHeight(); 175 | 176 | /** 177 | * Return the height of the row. 178 | * 179 | * @param row the row index. 180 | * @return The height of the row, in pixels. 181 | */ 182 | int getRowHeight(int row); 183 | 184 | /** 185 | * Return the header row width. 186 | * 187 | * @return The header width of the rows, in pixels. 188 | */ 189 | int getHeaderRowWidth(); 190 | 191 | /** 192 | * Called when a view created by this adapter has been recycled. 193 | *

194 | *

A view is recycled when a {@link AdaptiveTableLayout} decides that it no longer 195 | * needs to be attached. This can be because it has 196 | * fallen out of visibility or a set of cached views represented by views still 197 | * attached to the parent RecyclerView. If an item view has large or expensive data 198 | * bound to it such as large bitmaps, this may be a good place to release those 199 | * resources.

200 | *

201 | * {@link AdaptiveTableLayout} calls this method right before clearing ViewHolder's internal data and 202 | * sending it to Recycler. 203 | * 204 | * @param viewHolder The {@link ViewHolder} for the view being recycled 205 | */ 206 | void onViewHolderRecycled(@NonNull VH viewHolder); 207 | 208 | 209 | } 210 | -------------------------------------------------------------------------------- /library/src/main/java/com/cleveroad/adaptivetablelayout/AdaptiveTableDataSetObserver.java: -------------------------------------------------------------------------------- 1 | package com.cleveroad.adaptivetablelayout; 2 | 3 | interface AdaptiveTableDataSetObserver { 4 | /** 5 | * Notify any registered observers that the data set has changed. 6 | * If you change the size of the data set, you must call {@link AdaptiveTableDataSetObserver#notifyLayoutChanged()} instead 7 | */ 8 | void notifyDataSetChanged(); 9 | 10 | /** 11 | * You must call this when something has changed which has invalidated the layout of this view 12 | * or the size of the data set was changed 13 | */ 14 | void notifyLayoutChanged(); 15 | 16 | /** 17 | * Notify any registered observers that the item at 18 | * (rowIndex;columnIndex) has changed. 19 | * 20 | * @param rowIndex the row index 21 | * @param columnIndex the column index 22 | */ 23 | void notifyItemChanged(int rowIndex, int columnIndex); 24 | 25 | /** 26 | * Notify any registered observers that the row with rowIndex has changed. 27 | * 28 | * @param rowIndex the row index 29 | */ 30 | void notifyRowChanged(int rowIndex); 31 | 32 | /** 33 | * Notify any registered observers that the column with columnIndex has changed. 34 | * 35 | * @param columnIndex the column index 36 | */ 37 | void notifyColumnChanged(int columnIndex); 38 | 39 | 40 | } 41 | -------------------------------------------------------------------------------- /library/src/main/java/com/cleveroad/adaptivetablelayout/AdaptiveTableLayoutSettings.java: -------------------------------------------------------------------------------- 1 | package com.cleveroad.adaptivetablelayout; 2 | 3 | /** 4 | * Settings keeper class. 5 | */ 6 | class AdaptiveTableLayoutSettings { 7 | 8 | /** 9 | * Layout width 10 | */ 11 | private int mLayoutWidth; 12 | /** 13 | * Layout height 14 | */ 15 | private int mLayoutHeight; 16 | 17 | private boolean mIsHeaderFixed; 18 | 19 | private int mCellMargin; 20 | 21 | /** 22 | * if true - value of row header fixed to the row. Fixed to the data 23 | * if false - fixed to the number of row. Fixed to the row' number from 0 to n. 24 | */ 25 | private boolean mSolidRowHeader; 26 | 27 | /** 28 | * If true, then the table can be edited, otherwise it is impossible 29 | */ 30 | private boolean mDragAndDropEnabled; 31 | 32 | 33 | AdaptiveTableLayoutSettings() { 34 | } 35 | 36 | int getLayoutWidth() { 37 | return mLayoutWidth; 38 | } 39 | 40 | AdaptiveTableLayoutSettings setLayoutWidth(int layoutWidth) { 41 | mLayoutWidth = layoutWidth; 42 | return this; 43 | } 44 | 45 | int getLayoutHeight() { 46 | return mLayoutHeight; 47 | } 48 | 49 | AdaptiveTableLayoutSettings setLayoutHeight(int layoutHeight) { 50 | mLayoutHeight = layoutHeight; 51 | return this; 52 | } 53 | 54 | public boolean isHeaderFixed() { 55 | return mIsHeaderFixed; 56 | } 57 | 58 | public AdaptiveTableLayoutSettings setHeaderFixed(boolean headerFixed) { 59 | mIsHeaderFixed = headerFixed; 60 | return this; 61 | } 62 | 63 | public int getCellMargin() { 64 | return mCellMargin; 65 | } 66 | 67 | public AdaptiveTableLayoutSettings setCellMargin(int cellMargin) { 68 | mCellMargin = cellMargin; 69 | return this; 70 | } 71 | 72 | public boolean isSolidRowHeader() { 73 | return mSolidRowHeader; 74 | } 75 | 76 | public AdaptiveTableLayoutSettings setSolidRowHeader(boolean solidRowHeader) { 77 | mSolidRowHeader = solidRowHeader; 78 | return this; 79 | } 80 | 81 | public boolean isDragAndDropEnabled() { 82 | return mDragAndDropEnabled; 83 | } 84 | 85 | public void setDragAndDropEnabled(boolean dragAndDropEnabled) { 86 | mDragAndDropEnabled = dragAndDropEnabled; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /library/src/main/java/com/cleveroad/adaptivetablelayout/AdaptiveTableManagerRTL.java: -------------------------------------------------------------------------------- 1 | package com.cleveroad.adaptivetablelayout; 2 | 3 | import android.annotation.TargetApi; 4 | import android.os.Build; 5 | 6 | /** 7 | * same as {@link AdaptiveTableManager}, but support rtl direction 8 | */ 9 | @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) 10 | class AdaptiveTableManagerRTL extends AdaptiveTableManager { 11 | 12 | private LayoutDirectionHelper mLayoutDirectionHelper; 13 | 14 | AdaptiveTableManagerRTL(LayoutDirectionHelper layoutDirectionHelper) { 15 | mLayoutDirectionHelper = layoutDirectionHelper; 16 | } 17 | 18 | @Override 19 | int getColumnByXWithShift(int x, int shiftEveryStep) { 20 | if (!mLayoutDirectionHelper.isRTL()) { 21 | return super.getColumnByXWithShift(x, shiftEveryStep); 22 | } else { 23 | checkForInit(); 24 | int sum = 0; 25 | if (x <= sum) { 26 | return 0; 27 | } 28 | for (int count = getColumnWidths().length, i = 0; i < count; i++) { 29 | int nextSum = sum + getColumnWidths()[i] + shiftEveryStep; 30 | if (x > sum && x < nextSum) { 31 | return i; 32 | } else if (x < nextSum) { 33 | return i - 1; 34 | } 35 | sum = nextSum; 36 | } 37 | return getColumnWidths().length - 1; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /library/src/main/java/com/cleveroad/adaptivetablelayout/AdaptiveTableState.java: -------------------------------------------------------------------------------- 1 | package com.cleveroad.adaptivetablelayout; 2 | 3 | /** 4 | * Layout state holder. 5 | */ 6 | class AdaptiveTableState { 7 | 8 | static final int NO_DRAGGING_POSITION = -1; 9 | /** 10 | * Current scroll position. 11 | */ 12 | private int mScrollX; 13 | private int mScrollY; 14 | 15 | /** 16 | * Dragging row flag 17 | */ 18 | private boolean mIsRowDragging; 19 | 20 | /** 21 | * Dragging column flag 22 | */ 23 | private boolean mIsColumnDragging; 24 | 25 | /** 26 | * Dragging column index 27 | */ 28 | private int mColumnDraggingIndex; 29 | /** 30 | * Dragging row index 31 | */ 32 | private int mRowDraggingIndex; 33 | 34 | 35 | int getScrollX() { 36 | return mScrollX; 37 | } 38 | 39 | void setScrollX(int scrollX) { 40 | mScrollX = scrollX; 41 | } 42 | 43 | int getScrollY() { 44 | return mScrollY; 45 | } 46 | 47 | void setScrollY(int scrollY) { 48 | mScrollY = scrollY; 49 | } 50 | 51 | boolean isRowDragging() { 52 | return mIsRowDragging; 53 | } 54 | 55 | void setRowDragging(boolean rowDragging, int rowIndex) { 56 | mIsRowDragging = rowDragging; 57 | mRowDraggingIndex = rowIndex; 58 | } 59 | 60 | boolean isColumnDragging() { 61 | return mIsColumnDragging; 62 | } 63 | 64 | void setColumnDragging(boolean columnDragging, int columnIndex) { 65 | mIsColumnDragging = columnDragging; 66 | mColumnDraggingIndex = columnIndex; 67 | } 68 | 69 | boolean isDragging() { 70 | return mIsColumnDragging || mIsRowDragging; 71 | } 72 | 73 | int getColumnDraggingIndex() { 74 | return mColumnDraggingIndex; 75 | } 76 | 77 | int getRowDraggingIndex() { 78 | return mRowDraggingIndex; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /library/src/main/java/com/cleveroad/adaptivetablelayout/BaseDataAdaptiveTableLayoutAdapter.java: -------------------------------------------------------------------------------- 1 | package com.cleveroad.adaptivetablelayout; 2 | 3 | public abstract class BaseDataAdaptiveTableLayoutAdapter extends LinkedAdaptiveTableAdapter implements 4 | DataAdaptiveTableLayoutAdapter { 5 | 6 | /** 7 | * @return data matrix 8 | */ 9 | protected abstract Object[][] getItems(); 10 | 11 | /** 12 | * @return row's headers array 13 | */ 14 | protected abstract Object[] getRowHeaders(); 15 | 16 | /** 17 | * @return column's headers array 18 | */ 19 | protected abstract Object[] getColumnHeaders(); 20 | 21 | @Override 22 | public void changeColumns(int columnIndex, int columnToIndex) { 23 | // switch data 24 | switchTwoColumns(columnIndex, columnToIndex); 25 | // switch headers 26 | switchTwoColumnHeaders(columnIndex, columnToIndex); 27 | } 28 | 29 | /** 30 | * Switch 2 columns with data 31 | * 32 | * @param columnIndex column from 33 | * @param columnToIndex column to 34 | */ 35 | void switchTwoColumns(int columnIndex, int columnToIndex) { 36 | for (int i = 0; i < getRowCount() - 1; i++) { 37 | Object cellData = getItems()[i][columnToIndex]; 38 | getItems()[i][columnToIndex] = getItems()[i][columnIndex]; 39 | getItems()[i][columnIndex] = cellData; 40 | } 41 | } 42 | 43 | /** 44 | * Switch 2 columns headers with data 45 | * 46 | * @param columnIndex column header from 47 | * @param columnToIndex column header to 48 | */ 49 | void switchTwoColumnHeaders(int columnIndex, int columnToIndex) { 50 | Object cellData = getColumnHeaders()[columnToIndex]; 51 | getColumnHeaders()[columnToIndex] = getColumnHeaders()[columnIndex]; 52 | getColumnHeaders()[columnIndex] = cellData; 53 | } 54 | 55 | @Override 56 | public void changeRows(int rowIndex, int rowToIndex, boolean solidRowHeader) { 57 | // switch data 58 | switchTwoRows(rowIndex, rowToIndex); 59 | if (solidRowHeader) { 60 | // switch headers 61 | switchTwoRowHeaders(rowIndex, rowToIndex); 62 | } 63 | } 64 | 65 | /** 66 | * Switch 2 rows with data 67 | * 68 | * @param rowIndex row from 69 | * @param rowToIndex row to 70 | */ 71 | void switchTwoRows(int rowIndex, int rowToIndex) { 72 | for (int i = 0; i < getItems().length; i++) { 73 | Object cellData = getItems()[rowToIndex][i]; 74 | getItems()[rowToIndex][i] = getItems()[rowIndex][i]; 75 | getItems()[rowIndex][i] = cellData; 76 | } 77 | } 78 | 79 | /** 80 | * Switch 2 rows headers with data 81 | * 82 | * @param rowIndex row header from 83 | * @param rowToIndex row header to 84 | */ 85 | void switchTwoRowHeaders(int rowIndex, int rowToIndex) { 86 | Object cellData = getRowHeaders()[rowToIndex]; 87 | getRowHeaders()[rowToIndex] = getRowHeaders()[rowIndex]; 88 | getRowHeaders()[rowIndex] = cellData; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /library/src/main/java/com/cleveroad/adaptivetablelayout/DataAdaptiveTableLayoutAdapter.java: -------------------------------------------------------------------------------- 1 | package com.cleveroad.adaptivetablelayout; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.NonNull; 5 | 6 | interface DataAdaptiveTableLayoutAdapter extends AdaptiveTableAdapter { 7 | /** 8 | * Method calls when need to need to switch 2 columns with each other in the data matrix 9 | * 10 | * @param columnIndex column from 11 | * @param columnToIndex column to 12 | */ 13 | void changeColumns(int columnIndex, int columnToIndex); 14 | 15 | /** 16 | * Method calls when need to need to switch 2 rows with each other in the data matrix 17 | * 18 | * @param rowIndex row from 19 | * @param rowToIndex row to 20 | * @param solidRowHeader fixed to the data or to the row number. 21 | */ 22 | void changeRows(int rowIndex, int rowToIndex, boolean solidRowHeader); 23 | 24 | void onSaveInstanceState(@NonNull Bundle bundle); 25 | 26 | void onRestoreInstanceState(@NonNull Bundle bundle); 27 | } 28 | -------------------------------------------------------------------------------- /library/src/main/java/com/cleveroad/adaptivetablelayout/DataSetObserverProxy.java: -------------------------------------------------------------------------------- 1 | package com.cleveroad.adaptivetablelayout; 2 | 3 | class DataSetObserverProxy implements AdaptiveTableDataSetObserver { 4 | private final AdaptiveTableAdapter mAdaptiveTableAdapter; 5 | 6 | DataSetObserverProxy(AdaptiveTableAdapter adaptiveTableAdapter) { 7 | mAdaptiveTableAdapter = adaptiveTableAdapter; 8 | } 9 | 10 | @Override 11 | public void notifyDataSetChanged() { 12 | mAdaptiveTableAdapter.notifyDataSetChanged(); 13 | } 14 | 15 | @Override 16 | public void notifyLayoutChanged() { 17 | mAdaptiveTableAdapter.notifyLayoutChanged(); 18 | } 19 | 20 | @Override 21 | public void notifyItemChanged(int rowIndex, int columnIndex) { 22 | mAdaptiveTableAdapter.notifyItemChanged(rowIndex, columnIndex); 23 | } 24 | 25 | @Override 26 | public void notifyRowChanged(int rowIndex) { 27 | mAdaptiveTableAdapter.notifyRowChanged(rowIndex); 28 | } 29 | 30 | @Override 31 | public void notifyColumnChanged(int columnIndex) { 32 | mAdaptiveTableAdapter.notifyColumnChanged(columnIndex); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /library/src/main/java/com/cleveroad/adaptivetablelayout/DragAndDropPoints.java: -------------------------------------------------------------------------------- 1 | package com.cleveroad.adaptivetablelayout; 2 | 3 | import android.graphics.Point; 4 | 5 | /** 6 | * Helps implement dragging feature. 7 | * Contains start, offset and end point in drag and drop mode. 8 | */ 9 | class DragAndDropPoints { 10 | /** 11 | * Start dragging touch point 12 | */ 13 | private final Point mStart; 14 | /** 15 | * Screen offset (touch position) 16 | */ 17 | private final Point mOffset; 18 | 19 | /** 20 | * End dragging touch point 21 | */ 22 | private final Point mEnd; 23 | 24 | DragAndDropPoints() { 25 | mStart = new Point(); 26 | mOffset = new Point(); 27 | mEnd = new Point(); 28 | } 29 | 30 | Point getStart() { 31 | return mStart; 32 | } 33 | 34 | Point getOffset() { 35 | return mOffset; 36 | } 37 | 38 | Point getEnd() { 39 | return mEnd; 40 | } 41 | 42 | void setStart(int x, int y) { 43 | mStart.set(x, y); 44 | } 45 | 46 | void setOffset(int x, int y) { 47 | mOffset.set(x, y); 48 | } 49 | 50 | void setEnd(int x, int y) { 51 | mEnd.set(x, y); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /library/src/main/java/com/cleveroad/adaptivetablelayout/DragAndDropScrollRunnable.java: -------------------------------------------------------------------------------- 1 | package com.cleveroad.adaptivetablelayout; 2 | 3 | import android.view.View; 4 | 5 | /** 6 | * Move table layout logic in dragging mode 7 | */ 8 | class DragAndDropScrollRunnable implements Runnable { 9 | private View mView; 10 | private boolean isFinished; 11 | 12 | private int mDiffX; 13 | private int mDiffY; 14 | 15 | DragAndDropScrollRunnable(View view) { 16 | mView = view; 17 | isFinished = true; 18 | } 19 | 20 | synchronized void touch(int touchX, int touchY, @ScrollType int orientation) { 21 | 22 | int partOfWidth = mView.getWidth() / 4; 23 | // vertical scroll area (top, bottom) 24 | int partOfHeight = mView.getHeight() / 4; 25 | 26 | 27 | if (orientation == ScrollType.SCROLL_HORIZONTAL) { 28 | if (touchX < partOfWidth) { 29 | // if touch in left horizontal area -> scroll to left 30 | start(touchX - partOfWidth, 0); 31 | } else if (touchX > mView.getWidth() - partOfWidth) { 32 | // if touch in right horizontal area -> scroll to right 33 | start(touchX - mView.getWidth() + partOfWidth, 0); 34 | } else { 35 | // touch between scroll left and right areas. 36 | mDiffX = 0; 37 | mDiffY = 0; 38 | } 39 | } else if (orientation == ScrollType.SCROLL_VERTICAL) { 40 | if (touchY < partOfHeight) { 41 | // if touch in top vertical area -> scroll to top 42 | start(0, touchY - partOfHeight); 43 | } else if (touchY > mView.getHeight() - partOfHeight) { 44 | // if touch in bottom vertical area -> scroll to bottom 45 | start(0, touchY - mView.getHeight() + partOfHeight); 46 | } else { 47 | // touch between scroll top and bottom areas. 48 | mDiffX = 0; 49 | mDiffY = 0; 50 | } 51 | } 52 | } 53 | 54 | private synchronized void start(int diffX, int diffY) { 55 | // save start data 56 | mDiffX = diffX; 57 | mDiffY = diffY; 58 | // check if scrolling in the process 59 | if (isFinished) { 60 | // start scroll 61 | isFinished = false; 62 | mView.post(this); 63 | } 64 | } 65 | 66 | @Override 67 | public void run() { 68 | // scroll speed. Calculated by hand. 69 | int shiftDistanceX = mDiffX / 5; 70 | int shiftDistanceY = mDiffY / 5; 71 | 72 | if ((shiftDistanceX != 0 || shiftDistanceY != 0) && !isFinished) { 73 | // change state 74 | isFinished = false; 75 | // scroll view 76 | mView.scrollBy(shiftDistanceX, shiftDistanceY); 77 | // start self again 78 | mView.post(this); 79 | } else { 80 | // have no shift distance or need to finish. 81 | stop(); 82 | } 83 | } 84 | 85 | synchronized void stop() { 86 | // vars to default 87 | mDiffX = 0; 88 | mDiffY = 0; 89 | 90 | // change state 91 | isFinished = true; 92 | 93 | // remove callbacks 94 | mView.removeCallbacks(this); 95 | } 96 | 97 | boolean isFinished() { 98 | return isFinished; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /library/src/main/java/com/cleveroad/adaptivetablelayout/LayoutDirection.java: -------------------------------------------------------------------------------- 1 | package com.cleveroad.adaptivetablelayout; 2 | 3 | import android.support.annotation.IntDef; 4 | 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | 8 | @IntDef({ 9 | LayoutDirection.LTR, 10 | LayoutDirection.RTL 11 | }) 12 | /** 13 | * Type of layout directions. Same values as in android.util.LayoutDirection 14 | * This interface needed because project min API is 16 15 | */ 16 | @Retention(RetentionPolicy.SOURCE) 17 | @interface LayoutDirection { 18 | /** 19 | * Top left header 20 | */ 21 | int LTR = 0; 22 | /** 23 | * Vertical header 24 | */ 25 | int RTL = 1; 26 | } 27 | -------------------------------------------------------------------------------- /library/src/main/java/com/cleveroad/adaptivetablelayout/LayoutDirectionHelper.java: -------------------------------------------------------------------------------- 1 | package com.cleveroad.adaptivetablelayout; 2 | 3 | /** 4 | * Helper for convenient work with layout direction 5 | */ 6 | class LayoutDirectionHelper { 7 | private int mLayoutDirection; 8 | 9 | LayoutDirectionHelper(int direction) { 10 | mLayoutDirection = direction; 11 | } 12 | 13 | private int getLayoutDirection() { 14 | return mLayoutDirection; 15 | } 16 | 17 | void setLayoutDirection(int direction) { 18 | mLayoutDirection = direction; 19 | } 20 | 21 | boolean isRTL() { 22 | return getLayoutDirection() == LayoutDirection.RTL; 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /library/src/main/java/com/cleveroad/adaptivetablelayout/LinkedAdaptiveTableAdapter.java: -------------------------------------------------------------------------------- 1 | package com.cleveroad.adaptivetablelayout; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.support.annotation.Nullable; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | /** 10 | * {@inheritDoc} 11 | * Common base class of common implementation for an {@link AdaptiveTableAdapter} that 12 | * can be used in {@link AdaptiveTableLayout}. 13 | */ 14 | public abstract class LinkedAdaptiveTableAdapter implements AdaptiveTableAdapter { 15 | protected boolean mIsRtl; 16 | /** 17 | * Set with observers 18 | */ 19 | @NonNull 20 | private final List mAdaptiveTableDataSetObservers = new ArrayList<>(); 21 | 22 | /** 23 | * Need to throw item click action 24 | */ 25 | @Nullable 26 | private OnItemClickListener mOnItemClickListener; 27 | 28 | /** 29 | * Need to throw long item click action 30 | */ 31 | @Nullable 32 | private OnItemLongClickListener mOnItemLongClickListener; 33 | 34 | @Override 35 | @Nullable 36 | public OnItemClickListener getOnItemClickListener() { 37 | return mOnItemClickListener; 38 | } 39 | 40 | @Override 41 | public void setOnItemClickListener(@Nullable OnItemClickListener onItemClickListener) { 42 | mOnItemClickListener = onItemClickListener; 43 | } 44 | 45 | @Override 46 | @Nullable 47 | public OnItemLongClickListener getOnItemLongClickListener() { 48 | return mOnItemLongClickListener; 49 | } 50 | 51 | @Override 52 | public void setOnItemLongClickListener(@Nullable OnItemLongClickListener onItemLongClickListener) { 53 | mOnItemLongClickListener = onItemLongClickListener; 54 | } 55 | 56 | @NonNull 57 | public List getAdaptiveTableDataSetObservers() { 58 | return mAdaptiveTableDataSetObservers; 59 | } 60 | 61 | /** 62 | * {@inheritDoc} 63 | * 64 | * @param observer the object that gets notified when the data set changes. 65 | */ 66 | @Override 67 | public void registerDataSetObserver(@NonNull AdaptiveTableDataSetObserver observer) { 68 | mAdaptiveTableDataSetObservers.add(observer); 69 | } 70 | 71 | /** 72 | * {@inheritDoc} 73 | * 74 | * @param observer the object to unregister. 75 | */ 76 | @Override 77 | public void unregisterDataSetObserver(@NonNull AdaptiveTableDataSetObserver observer) { 78 | mAdaptiveTableDataSetObservers.remove(observer); 79 | } 80 | 81 | /** 82 | * {@inheritDoc} 83 | */ 84 | @Override 85 | public void notifyDataSetChanged() { 86 | for (AdaptiveTableDataSetObserver observer : mAdaptiveTableDataSetObservers) { 87 | observer.notifyDataSetChanged(); 88 | } 89 | } 90 | 91 | /** 92 | * {@inheritDoc} 93 | * 94 | * @param rowIndex the row index 95 | * @param columnIndex the column index 96 | */ 97 | @Override 98 | public void notifyItemChanged(int rowIndex, int columnIndex) { 99 | for (AdaptiveTableDataSetObserver observer : mAdaptiveTableDataSetObservers) { 100 | observer.notifyItemChanged(rowIndex, columnIndex); 101 | } 102 | } 103 | 104 | /** 105 | * {@inheritDoc} 106 | * 107 | * @param rowIndex the row index 108 | */ 109 | @Override 110 | public void notifyRowChanged(int rowIndex) { 111 | for (AdaptiveTableDataSetObserver observer : mAdaptiveTableDataSetObservers) { 112 | observer.notifyRowChanged(rowIndex); 113 | } 114 | } 115 | 116 | /** 117 | * {@inheritDoc} 118 | * 119 | * @param columnIndex the column index 120 | */ 121 | @Override 122 | public void notifyColumnChanged(int columnIndex) { 123 | for (AdaptiveTableDataSetObserver observer : mAdaptiveTableDataSetObservers) { 124 | observer.notifyColumnChanged(columnIndex); 125 | } 126 | } 127 | 128 | /** 129 | * {@inheritDoc} 130 | */ 131 | @Override 132 | public void notifyLayoutChanged() { 133 | for (AdaptiveTableDataSetObserver observer : mAdaptiveTableDataSetObservers) { 134 | observer.notifyLayoutChanged(); 135 | } 136 | } 137 | 138 | @Override 139 | public void onViewHolderRecycled(@NonNull VH viewHolder) { 140 | //do something 141 | } 142 | 143 | public boolean isRtl() { 144 | return mIsRtl; 145 | } 146 | 147 | public void setRtl(boolean rtl) { 148 | mIsRtl = rtl; 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /library/src/main/java/com/cleveroad/adaptivetablelayout/OnItemClickListener.java: -------------------------------------------------------------------------------- 1 | package com.cleveroad.adaptivetablelayout; 2 | 3 | public interface OnItemClickListener { 4 | /** 5 | * Click item callback. 6 | * 7 | * @param row clicked item row 8 | * @param column clicked item column 9 | */ 10 | void onItemClick(int row, int column); 11 | 12 | /** 13 | * Click row header item callback 14 | * 15 | * @param row clicked row header 16 | */ 17 | void onRowHeaderClick(int row); 18 | 19 | /** 20 | * Click column header item callback 21 | * 22 | * @param column clicked column header 23 | */ 24 | void onColumnHeaderClick(int column); 25 | 26 | 27 | /** 28 | * Click left top item callback 29 | */ 30 | void onLeftTopHeaderClick(); 31 | } 32 | -------------------------------------------------------------------------------- /library/src/main/java/com/cleveroad/adaptivetablelayout/OnItemLongClickListener.java: -------------------------------------------------------------------------------- 1 | package com.cleveroad.adaptivetablelayout; 2 | 3 | public interface OnItemLongClickListener { 4 | /** 5 | * Long click item callback. 6 | * 7 | * @param row clicked item row 8 | * @param column clicked item column 9 | */ 10 | void onItemLongClick(int row, int column); 11 | 12 | /** 13 | * Long click left top item callback 14 | */ 15 | void onLeftTopHeaderLongClick(); 16 | } 17 | -------------------------------------------------------------------------------- /library/src/main/java/com/cleveroad/adaptivetablelayout/Recycler.java: -------------------------------------------------------------------------------- 1 | package com.cleveroad.adaptivetablelayout; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.support.annotation.Nullable; 5 | import android.util.SparseArray; 6 | 7 | import java.util.ArrayDeque; 8 | import java.util.Deque; 9 | 10 | /** 11 | * The Recycler facilitates reuse of mViewHolders across layouts. 12 | */ 13 | class Recycler { 14 | 15 | private SparseArray> mViewHolders; 16 | 17 | /** 18 | * Constructor 19 | */ 20 | @SuppressWarnings("unchecked") 21 | Recycler() { 22 | mViewHolders = new SparseArray<>(3); 23 | } 24 | 25 | /** 26 | * Add a view to the Recycler. This view may be reused in the function 27 | * {@link #popRecycledViewHolder(int)} 28 | * 29 | * @param viewHolder A viewHolder to add to the Recycler. It can no longer be used. 30 | */ 31 | void pushRecycledView(@NonNull ViewHolder viewHolder) { 32 | Deque deque = mViewHolders.get(viewHolder.getItemType()); 33 | if (deque == null) { 34 | deque = new ArrayDeque<>(); 35 | mViewHolders.put(viewHolder.getItemType(), deque); 36 | } 37 | deque.push(viewHolder); 38 | } 39 | 40 | /** 41 | * Returns, if exists, a view of the type typeView. 42 | * 43 | * @param itemType the type of view that you want. 44 | * @return a viewHolder of the type typeView. null if 45 | * not found. 46 | */ 47 | @Nullable 48 | ViewHolder popRecycledViewHolder(int itemType) { 49 | Deque deque = mViewHolders.get(itemType); 50 | return deque == null || deque.isEmpty() ? null : deque.pop(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /library/src/main/java/com/cleveroad/adaptivetablelayout/ScrollHelper.java: -------------------------------------------------------------------------------- 1 | package com.cleveroad.adaptivetablelayout; 2 | 3 | import android.content.Context; 4 | import android.support.annotation.Nullable; 5 | import android.support.v4.view.GestureDetectorCompat; 6 | import android.view.GestureDetector; 7 | import android.view.MotionEvent; 8 | 9 | 10 | class ScrollHelper implements GestureDetector.OnGestureListener { 11 | /** 12 | * Gesture detector -> Scroll, Fling, Tap, LongPress, ... 13 | * Using when user need to scroll table 14 | */ 15 | private final GestureDetectorCompat mGestureDetectorCompat; 16 | 17 | @Nullable 18 | private ScrollHelperListener mListener; 19 | 20 | ScrollHelper(Context context) { 21 | mGestureDetectorCompat = new GestureDetectorCompat(context, this); 22 | mGestureDetectorCompat.setIsLongpressEnabled(true); 23 | } 24 | 25 | void setListener(@Nullable ScrollHelperListener listener) { 26 | mListener = listener; 27 | } 28 | 29 | @Override 30 | public boolean onDown(MotionEvent e) { 31 | // catch down action 32 | return mListener == null || mListener.onDown(e); 33 | } 34 | 35 | @Override 36 | public void onShowPress(MotionEvent e) { 37 | // nothing to do 38 | } 39 | 40 | @Override 41 | public boolean onSingleTapUp(MotionEvent e) { 42 | // catch click action 43 | return mListener != null && mListener.onSingleTapUp(e); 44 | } 45 | 46 | @Override 47 | public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { 48 | // catch scroll action 49 | return mListener != null && mListener.onScroll(e1, e2, distanceX, distanceY); 50 | } 51 | 52 | @Override 53 | public void onLongPress(MotionEvent e) { 54 | // catch long click action 55 | if (mListener != null) { 56 | mListener.onLongPress(e); 57 | } 58 | } 59 | 60 | @Override 61 | public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { 62 | // catch fling action 63 | return mListener == null || mListener.onFling(e1, e2, velocityX, velocityY); 64 | } 65 | 66 | boolean onTouch(MotionEvent event) { 67 | 68 | if (event.getAction() == MotionEvent.ACTION_UP && mListener != null) { 69 | // stop drag and drop mode 70 | mListener.onActionUp(event); 71 | } 72 | // connect GestureDetector with our touch events 73 | return mGestureDetectorCompat.onTouchEvent(event); 74 | } 75 | 76 | 77 | interface ScrollHelperListener { 78 | 79 | boolean onDown(MotionEvent e); 80 | 81 | boolean onSingleTapUp(MotionEvent e); 82 | 83 | void onLongPress(MotionEvent e); 84 | 85 | boolean onActionUp(MotionEvent e); 86 | 87 | boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY); 88 | 89 | boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /library/src/main/java/com/cleveroad/adaptivetablelayout/ScrollType.java: -------------------------------------------------------------------------------- 1 | package com.cleveroad.adaptivetablelayout; 2 | 3 | import android.support.annotation.IntDef; 4 | 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | 8 | import static com.cleveroad.adaptivetablelayout.ScrollType.SCROLL_HORIZONTAL; 9 | import static com.cleveroad.adaptivetablelayout.ScrollType.SCROLL_VERTICAL; 10 | 11 | @IntDef({ 12 | SCROLL_HORIZONTAL, 13 | SCROLL_VERTICAL 14 | }) 15 | /** 16 | * Scroll type for drag and drop mode. 17 | */ 18 | @Retention(RetentionPolicy.SOURCE) 19 | @interface ScrollType { 20 | int SCROLL_HORIZONTAL = 0; 21 | int SCROLL_VERTICAL = 1; 22 | } 23 | -------------------------------------------------------------------------------- /library/src/main/java/com/cleveroad/adaptivetablelayout/ShadowHelper.java: -------------------------------------------------------------------------------- 1 | package com.cleveroad.adaptivetablelayout; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.support.annotation.Nullable; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | 8 | class ShadowHelper { 9 | 10 | @Nullable 11 | private View mRightShadow; 12 | @Nullable 13 | private View mLeftShadow; 14 | @Nullable 15 | private View mTopShadow; 16 | @Nullable 17 | private View mBottomShadow; 18 | @Nullable 19 | private View mColumnsHeadersShadow; 20 | @Nullable 21 | private View mRowsHeadersShadow; 22 | 23 | private LayoutDirectionHelper mLayoutDirectionHelper; 24 | 25 | ShadowHelper(LayoutDirectionHelper layoutDirectionHelper) { 26 | mLayoutDirectionHelper = layoutDirectionHelper; 27 | } 28 | 29 | @NonNull 30 | View addColumnsHeadersShadow(ViewGroup group) { 31 | if (mColumnsHeadersShadow == null) { 32 | mColumnsHeadersShadow = new View(group.getContext()); 33 | mColumnsHeadersShadow.setBackgroundResource(R.drawable.shadow_bottom); 34 | group.addView(mColumnsHeadersShadow, 0); 35 | } 36 | return mColumnsHeadersShadow; 37 | } 38 | 39 | @Nullable 40 | View getColumnsHeadersShadow() { 41 | return mColumnsHeadersShadow; 42 | } 43 | 44 | @NonNull 45 | View addRowsHeadersShadow(ViewGroup group) { 46 | if (mRowsHeadersShadow == null) { 47 | mRowsHeadersShadow = new View(group.getContext()); 48 | mRowsHeadersShadow.setBackgroundResource(!mLayoutDirectionHelper.isRTL() 49 | ? R.drawable.shadow_right 50 | : R.drawable.shadow_left); 51 | group.addView(mRowsHeadersShadow, 0); 52 | } 53 | return mRowsHeadersShadow; 54 | } 55 | 56 | @Nullable 57 | View getRowsHeadersShadow() { 58 | return mRowsHeadersShadow; 59 | } 60 | 61 | void removeColumnsHeadersShadow(ViewGroup group) { 62 | if (mColumnsHeadersShadow != null) { 63 | group.removeView(mColumnsHeadersShadow); 64 | mColumnsHeadersShadow = null; 65 | } 66 | } 67 | 68 | void removeRowsHeadersShadow(ViewGroup group) { 69 | if (mRowsHeadersShadow != null) { 70 | group.removeView(mRowsHeadersShadow); 71 | mRowsHeadersShadow = null; 72 | } 73 | } 74 | 75 | @NonNull 76 | View addLeftShadow(ViewGroup group) { 77 | if (mLeftShadow == null) { 78 | mLeftShadow = new View(group.getContext()); 79 | mLeftShadow.setBackgroundResource(R.drawable.shadow_left); 80 | group.addView(mLeftShadow, 0); 81 | } 82 | return mLeftShadow; 83 | } 84 | 85 | @Nullable 86 | View getLeftShadow() { 87 | return mLeftShadow; 88 | } 89 | 90 | @NonNull 91 | View addRightShadow(ViewGroup group) { 92 | if (mRightShadow == null) { 93 | mRightShadow = new View(group.getContext()); 94 | mRightShadow.setBackgroundResource(R.drawable.shadow_right); 95 | group.addView(mRightShadow, 0); 96 | } 97 | return mRightShadow; 98 | } 99 | 100 | @Nullable 101 | View getRightShadow() { 102 | return mRightShadow; 103 | } 104 | 105 | @NonNull 106 | View addTopShadow(ViewGroup group) { 107 | if (mTopShadow == null) { 108 | mTopShadow = new View(group.getContext()); 109 | mTopShadow.setBackgroundResource(R.drawable.shadow_top); 110 | group.addView(mTopShadow, 0); 111 | } 112 | return mTopShadow; 113 | } 114 | 115 | @Nullable 116 | View getTopShadow() { 117 | return mTopShadow; 118 | } 119 | 120 | @NonNull 121 | View addBottomShadow(ViewGroup group) { 122 | if (mBottomShadow == null) { 123 | mBottomShadow = new View(group.getContext()); 124 | mBottomShadow.setBackgroundResource(R.drawable.shadow_bottom); 125 | group.addView(mBottomShadow, 0); 126 | } 127 | return mBottomShadow; 128 | } 129 | 130 | @Nullable 131 | View getBottomShadow() { 132 | return mBottomShadow; 133 | } 134 | 135 | void removeAllDragAndDropShadows(ViewGroup group) { 136 | if (mLeftShadow != null) { 137 | group.removeView(mLeftShadow); 138 | mLeftShadow = null; 139 | } 140 | 141 | if (mRightShadow != null) { 142 | group.removeView(mRightShadow); 143 | mRightShadow = null; 144 | } 145 | 146 | if (mTopShadow != null) { 147 | group.removeView(mTopShadow); 148 | mTopShadow = null; 149 | } 150 | 151 | if (mBottomShadow != null) { 152 | group.removeView(mBottomShadow); 153 | mBottomShadow = null; 154 | } 155 | } 156 | 157 | void onLayoutDirectionChanged() { 158 | if (getRowsHeadersShadow() != null) { 159 | getRowsHeadersShadow().setBackgroundResource( 160 | !mLayoutDirectionHelper.isRTL() 161 | ? R.drawable.shadow_right 162 | : R.drawable.shadow_left); 163 | getRowsHeadersShadow().requestLayout(); 164 | } 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /library/src/main/java/com/cleveroad/adaptivetablelayout/SmoothScrollRunnable.java: -------------------------------------------------------------------------------- 1 | package com.cleveroad.adaptivetablelayout; 2 | 3 | import android.view.View; 4 | import android.widget.Scroller; 5 | 6 | /** 7 | * Fling table layout logic 8 | * {@see http://stackoverflow.com/a/6219382/842697 } 9 | */ 10 | class SmoothScrollRunnable implements Runnable { 11 | /** 12 | * Scrollable view 13 | */ 14 | private final View mView; 15 | /** 16 | * Need to calculate offset. 17 | */ 18 | private Scroller mScroller; 19 | 20 | private int mLastX; 21 | private int mLastY; 22 | 23 | SmoothScrollRunnable(View view) { 24 | mView = view; 25 | mScroller = new Scroller(view.getContext()); 26 | } 27 | 28 | void start(int initX, int initY, int initialVelocityX, int initialVelocityY, int maxX, int maxY) { 29 | // start smooth scrolling 30 | mScroller.fling(initX, initY, initialVelocityX, initialVelocityY, 0, maxX, 0, maxY); 31 | 32 | // save new data 33 | mLastX = initX; 34 | mLastY = initY; 35 | 36 | // run self 37 | mView.post(this); 38 | } 39 | 40 | @Override 41 | public void run() { 42 | if (mScroller.isFinished()) { 43 | return; 44 | } 45 | // calculate offset 46 | boolean more = mScroller.computeScrollOffset(); 47 | int x = mScroller.getCurrX(); 48 | int y = mScroller.getCurrY(); 49 | int diffX = mLastX - x; 50 | int diffY = mLastY - y; 51 | if (diffX != 0 || diffY != 0) { 52 | mView.scrollBy(diffX, diffY); 53 | mLastX = x; 54 | mLastY = y; 55 | } 56 | 57 | if (more) { 58 | // run self 59 | mView.post(this); 60 | } 61 | } 62 | 63 | boolean isFinished() { 64 | return mScroller.isFinished(); 65 | } 66 | 67 | void forceFinished() { 68 | if (!mScroller.isFinished()) { 69 | mScroller.forceFinished(true); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /library/src/main/java/com/cleveroad/adaptivetablelayout/SparseMatrix.java: -------------------------------------------------------------------------------- 1 | package com.cleveroad.adaptivetablelayout; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.support.annotation.Nullable; 5 | import android.support.v4.util.SparseArrayCompat; 6 | 7 | import java.util.Collection; 8 | import java.util.LinkedList; 9 | 10 | /** 11 | * Custom matrix realisation to hold Objects 12 | * 13 | * @param Object 14 | */ 15 | class SparseMatrix { 16 | private final SparseArrayCompat> mData; 17 | 18 | SparseMatrix() { 19 | mData = new SparseArrayCompat<>(); 20 | } 21 | 22 | /** 23 | * Put item to the matrix in row, column position. 24 | * 25 | * @param row item row position 26 | * @param column item column position 27 | * @param item Object 28 | */ 29 | void put(int row, int column, @NonNull TObj item) { 30 | SparseArrayCompat array = mData.get(row); 31 | if (array == null) { 32 | array = new SparseArrayCompat<>(); 33 | array.put(column, item); 34 | mData.put(row, array); 35 | } else { 36 | array.put(column, item); 37 | } 38 | } 39 | 40 | /** 41 | * Get Object from matrix by row and column. 42 | * 43 | * @param row item row position 44 | * @param column item column position 45 | * @return Object in row, column position in the matrix 46 | */ 47 | @Nullable 48 | TObj get(int row, int column) { 49 | SparseArrayCompat array = mData.get(row); 50 | return array == null ? null : array.get(column); 51 | } 52 | 53 | /** 54 | * Get all row's items 55 | * 56 | * @param row row index 57 | * @return Collection with row's Objects 58 | */ 59 | @NonNull 60 | Collection getRowItems(int row) { 61 | Collection result = new LinkedList<>(); 62 | SparseArrayCompat array = mData.get(row); 63 | for (int count = array.size(), i = 0; i < count; i++) { 64 | int key = array.keyAt(i); 65 | TObj columnObj = array.get(key); 66 | if (columnObj != null) { 67 | result.add(columnObj); 68 | } 69 | 70 | } 71 | return result; 72 | } 73 | 74 | /** 75 | * Get all column's items 76 | * 77 | * @param column column index 78 | * @return Collection with column's Objects 79 | */ 80 | @NonNull 81 | Collection getColumnItems(int column) { 82 | Collection result = new LinkedList<>(); 83 | for (int count = mData.size(), i = 0; i < count; i++) { 84 | int key = mData.keyAt(i); 85 | TObj columnObj = mData.get(key).get(column); 86 | if (columnObj != null) { 87 | result.add(columnObj); 88 | } 89 | 90 | } 91 | return result; 92 | } 93 | 94 | /** 95 | * Get all matrix's items 96 | * 97 | * @return Collection with column's Objects 98 | */ 99 | @NonNull 100 | Collection getAll() { 101 | Collection result = new LinkedList<>(); 102 | for (int countR = mData.size(), i = 0; i < countR; i++) { 103 | int rowKey = mData.keyAt(i); 104 | SparseArrayCompat columns = mData.get(rowKey); 105 | for (int countC = columns.size(), j = 0; j < countC; j++) { 106 | int key = columns.keyAt(j); 107 | result.add(columns.get(key)); 108 | } 109 | } 110 | return result; 111 | } 112 | 113 | /** 114 | * Remove item in row, column position int the matrix 115 | * 116 | * @param row item row position 117 | * @param column item column position 118 | */ 119 | void remove(int row, int column) { 120 | SparseArrayCompat array = mData.get(row); 121 | if (array != null) { 122 | array.remove(column); 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /library/src/main/java/com/cleveroad/adaptivetablelayout/ViewHolder.java: -------------------------------------------------------------------------------- 1 | package com.cleveroad.adaptivetablelayout; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.view.View; 5 | 6 | /** 7 | * A {@link ViewHolder} describes an item view and metadata about its place within the {@link AdaptiveTableLayout}. 8 | */ 9 | interface ViewHolder { 10 | /** 11 | * @return item represents the item of the {@link AdaptiveTableLayout} 12 | */ 13 | @NonNull 14 | View getItemView(); 15 | 16 | /** 17 | * @return the type of the View 18 | */ 19 | int getItemType(); 20 | 21 | /** 22 | * @param itemType is the type of the View 23 | */ 24 | void setItemType(int itemType); 25 | 26 | /** 27 | * @return the row index. 28 | */ 29 | int getRowIndex(); 30 | 31 | /** 32 | * @param rowIndex the row index. 33 | */ 34 | void setRowIndex(int rowIndex); 35 | 36 | /** 37 | * @return the column index. 38 | */ 39 | int getColumnIndex(); 40 | 41 | /** 42 | * @param columnIndex the column index. 43 | */ 44 | void setColumnIndex(int columnIndex); 45 | 46 | 47 | /** 48 | * @return dragging flag 49 | */ 50 | boolean isDragging(); 51 | 52 | /** 53 | * @param isDragging dragging param 54 | */ 55 | void setIsDragging(boolean isDragging); 56 | } 57 | -------------------------------------------------------------------------------- /library/src/main/java/com/cleveroad/adaptivetablelayout/ViewHolderImpl.java: -------------------------------------------------------------------------------- 1 | package com.cleveroad.adaptivetablelayout; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.view.View; 5 | 6 | /** 7 | * {@inheritDoc} 8 | */ 9 | public abstract class ViewHolderImpl implements ViewHolder { 10 | /** 11 | * Holder view 12 | */ 13 | private final View mItemView; 14 | /** 15 | * ViewHolder's table row index 16 | */ 17 | private int mRowIndex; 18 | /** 19 | * ViewHolder's table column index 20 | */ 21 | private int mColIndex; 22 | /** 23 | * ViewHolder's table item type param 24 | */ 25 | private int mItemType; 26 | /** 27 | * ViewHolder's dragging flag 28 | */ 29 | private boolean mIsDragging; 30 | 31 | public ViewHolderImpl(@NonNull View itemView) { 32 | mItemView = itemView; 33 | } 34 | 35 | @NonNull 36 | @Override 37 | public View getItemView() { 38 | return mItemView; 39 | } 40 | 41 | @Override 42 | public int getRowIndex() { 43 | return mRowIndex; 44 | } 45 | 46 | @Override 47 | public void setRowIndex(int rowIndex) { 48 | mRowIndex = rowIndex; 49 | } 50 | 51 | @Override 52 | public int getColumnIndex() { 53 | return mColIndex; 54 | } 55 | 56 | @Override 57 | public void setColumnIndex(int columnIndex) { 58 | mColIndex = columnIndex; 59 | } 60 | 61 | @Override 62 | public int getItemType() { 63 | return mItemType; 64 | } 65 | 66 | @Override 67 | public void setItemType(int itemType) { 68 | mItemType = itemType; 69 | } 70 | 71 | @Override 72 | public int hashCode() { 73 | int result = mItemView.hashCode(); 74 | result = 31 * result + mRowIndex; 75 | result = 31 * result + mColIndex; 76 | result = 31 * result + mItemType; 77 | result = 31 * result + (mIsDragging ? 1 : 0); 78 | return result; 79 | } 80 | 81 | @Override 82 | public boolean equals(Object obj) { 83 | if (!(obj instanceof ViewHolder)) { 84 | return false; 85 | } 86 | ViewHolder vh = (ViewHolder) obj; 87 | return vh.getColumnIndex() == this.getColumnIndex() && vh.getRowIndex() == this.getRowIndex(); 88 | } 89 | 90 | @Override 91 | public boolean isDragging() { 92 | return mIsDragging; 93 | } 94 | 95 | @Override 96 | public void setIsDragging(boolean isDragging) { 97 | mIsDragging = isDragging; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /library/src/main/java/com/cleveroad/adaptivetablelayout/ViewHolderType.java: -------------------------------------------------------------------------------- 1 | package com.cleveroad.adaptivetablelayout; 2 | 3 | import android.support.annotation.IntDef; 4 | 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | 8 | @IntDef({ 9 | ViewHolderType.FIRST_HEADER, 10 | ViewHolderType.ROW_HEADER, 11 | ViewHolderType.COLUMN_HEADER, 12 | ViewHolderType.ITEM 13 | }) 14 | /** 15 | * Type of adapter's ViewHolders 16 | */ 17 | @Retention(RetentionPolicy.SOURCE) 18 | @interface ViewHolderType { 19 | /** 20 | * Top left header 21 | */ 22 | int FIRST_HEADER = 0; 23 | /** 24 | * Vertical header 25 | */ 26 | int ROW_HEADER = 1; 27 | /** 28 | * Horizontal header 29 | */ 30 | int COLUMN_HEADER = 2; 31 | /** 32 | * Normal scrollable item 33 | */ 34 | int ITEM = 3; 35 | } 36 | -------------------------------------------------------------------------------- /library/src/main/res/drawable/shadow_bottom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /library/src/main/res/drawable/shadow_left.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /library/src/main/res/drawable/shadow_right.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /library/src/main/res/drawable/shadow_top.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /library/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /library/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #40000000 4 | #00000000 5 | 6 | 7 | -------------------------------------------------------------------------------- /library/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /library/src/main/res/values/ids.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /library/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | AdaptiveTableLayout 3 | 4 | -------------------------------------------------------------------------------- /sample/.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Android template 3 | # Built application files 4 | *.apk 5 | *.ap_ 6 | 7 | # Files for the ART/Dalvik VM 8 | *.dex 9 | 10 | # Java class files 11 | *.class 12 | 13 | # Generated files 14 | bin/ 15 | gen/ 16 | out/ 17 | 18 | # Gradle files 19 | .gradle/ 20 | build/ 21 | 22 | # Local configuration file (sdk path, etc) 23 | local.properties 24 | 25 | # Proguard folder generated by Eclipse 26 | proguard/ 27 | 28 | # Log Files 29 | *.log 30 | 31 | # Android Studio Navigation editor temp files 32 | .navigation/ 33 | 34 | # Android Studio captures folder 35 | captures/ 36 | 37 | # Intellij 38 | *.iml 39 | .idea/workspace.xml 40 | 41 | # Keystore files 42 | *.jks 43 | 44 | /src/main/res/values/keys.xml 45 | -------------------------------------------------------------------------------- /sample/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 28 5 | defaultConfig { 6 | applicationId "com.cleveroad.tablelayout" 7 | minSdkVersion 16 8 | targetSdkVersion 28 9 | versionCode 8 10 | versionName "1.2.0" 11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled true 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | } 20 | 21 | dependencies { 22 | implementation fileTree(include: ['*.jar'], dir: 'libs') 23 | implementation "com.android.support:appcompat-v7:$support_version" 24 | implementation "com.android.support:cardview-v7:$support_version" 25 | implementation "com.android.support:support-v4:$support_version" 26 | implementation "com.android.support:design:$support_version" 27 | // compile 'com.facebook.android:facebook-android-sdk:4.24.0' 28 | 29 | //Glide 30 | implementation "com.github.bumptech.glide:glide:$glide_version" 31 | annotationProcessor "com.github.bumptech.glide:compiler:$glide_version" 32 | 33 | implementation project(':library') 34 | } 35 | -------------------------------------------------------------------------------- /sample/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /home/illichenko_cr/Android/Sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | -keep public class * implements com.bumptech.glide.module.GlideModule 20 | -keep public class * extends com.bumptech.glide.module.AppGlideModule 21 | -keep public enum com.bumptech.glide.load.ImageHeaderParser$** { 22 | **[] $VALUES; 23 | public *; 24 | } 25 | 26 | # for DexGuard only 27 | -keepresourcexmlelements manifest/application/meta-data@value=GlideModule -------------------------------------------------------------------------------- /sample/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 15 | 16 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /sample/src/main/assets/artists.csv: -------------------------------------------------------------------------------- 1 | №, Photo, Name, Song, Genres 2 | 1, https://upload.wikimedia.org/wikipedia/commons/e/e4/Gabriel_batistuta.jpg,Name1,Song1,Rock;Jazz 3 | 2, https://upload.wikimedia.org/wikipedia/commons/e/e2/Hernan.png,Name2,Song2,Rock;Jazz 4 | 3, https://upload.wikimedia.org/wikipedia/commons/b/bb/Mcu_Alfredo_Di_Stefano.jpg,Name3,Song3,Rock;Jazz 5 | 4, https://upload.wikimedia.org/wikipedia/commons/d/db/Mario_Kempes_2006.jpg,Name4,Song4,Rock;Jazz 6 | 5, https://upload.wikimedia.org/wikipedia/commons/thumb/9/97/Maradona_at_2012_GCC_Champions_League_final.JPG/228px-Maradona_at_2012_GCC_Champions_League_final.JPG,Name5,Song5,Rock;Jazz 7 | 6, https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/Passarella_world_cup.jpg/362px-Passarella_world_cup.jpg,Name6,Song6,Rock;Jazz 8 | 7, https://upload.wikimedia.org/wikipedia/commons/thumb/a/a7/Saviola_after_Goal.jpg/202px-Saviola_after_Goal.jpg,Name7,Song7,Rock;Jazz 9 | 8, https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/EnriqueSivori_1956.jpeg/174px-EnriqueSivori_1956.jpeg,Name8,Song8,Rock;Jazz 10 | 9, https://upload.wikimedia.org/wikipedia/commons/2/20/JSV-Presidente-Estudiantes-2014.JPG,Name9,Song9,Rock;Jazz 11 | 10, https://upload.wikimedia.org/wikipedia/commons/thumb/e/e7/Metalist-Inter_%282%29.jpg/159px-Metalist-Inter_%282%29.jpg,Name10,Song10,Rock;Jazz 12 | 11, https://upload.wikimedia.org/wikipedia/commons/thumb/c/c5/Jan_Ceulemans.jpg/159px-Jan_Ceulemans.jpg,Name11,Song11,Rock;Jazz 13 | 12, https://upload.wikimedia.org/wikipedia/commons/thumb/1/16/Jean-Marie_Pfaff_at_Runa_Ralley_2007_cropped.jpg/163px-Jean-Marie_Pfaff_at_Runa_Ralley_2007_cropped.jpg,Name12,Song12,Rock;Jazz 14 | 13, https://upload.wikimedia.org/wikipedia/commons/thumb/9/92/Franky_Van_der_Elst_Lommel_United.JPG/161px-Franky_Van_der_Elst_Lommel_United.JPG,Name13,Song13,Rock;Jazz 15 | 14, https://upload.wikimedia.org/wikipedia/commons/thumb/4/4a/Pele200802FabioRodriguesPozzebomAgenciaBrasil.jpg/155px-Pele200802FabioRodriguesPozzebomAgenciaBrasil.jpg,Name14,Song14,Rock;Jazz 16 | 15, https://upload.wikimedia.org/wikipedia/commons/thumb/6/67/Roberto_Carlos_in_Moscow_3.jpg/171px-Roberto_Carlos_in_Moscow_3.jpg,Name15,Song15,Rock;Jazz 17 | 16, https://upload.wikimedia.org/wikipedia/commons/thumb/e/e4/Ronaldinho_72.jpg/159px-Ronaldinho_72.jpg,Name16,Song16,Rock;Jazz 18 | 17, https://upload.wikimedia.org/wikipedia/commons/thumb/4/4d/Ronaldo-14-05-2013.jpg/180px-Ronaldo-14-05-2013.jpg,Name17,Song17,Rock;Jazz 19 | 18, https://upload.wikimedia.org/wikipedia/commons/b/b9/Dasaev_Rinat.JPG,Name18,Song18,Rock;Jazz 20 | 19, https://upload.wikimedia.org/wikipedia/commons/thumb/a/a1/Andriy_Shevchenko_Nike_Clash_Collection.JPG/159px-Andriy_Shevchenko_Nike_Clash_Collection.JPG,Name19,Song19,Rock;Jazz 21 | 20, https://upload.wikimedia.org/wikipedia/commons/d/d2/Mia_Hamm_signing_an_autograph.jpg,Name20,Song20,Rock;Jazz -------------------------------------------------------------------------------- /sample/src/main/assets/fifa100.csv: -------------------------------------------------------------------------------- 1 | №, Photo, Name, Position, Date of birth, Football team 2 | 1, https://upload.wikimedia.org/wikipedia/commons/e/e4/Gabriel_batistuta.jpg, Gabriel Batistuta, FW, 1969-02-01, https://upload.wikimedia.org/wikipedia/en/8/84/Afa_logo_jerseys.png 3 | 2, https://upload.wikimedia.org/wikipedia/commons/e/e2/Hernan.png, Hernán Crespo, FW, 1975-07-05, https://upload.wikimedia.org/wikipedia/en/8/84/Afa_logo_jerseys.png 4 | 3, https://upload.wikimedia.org/wikipedia/commons/b/bb/Mcu_Alfredo_Di_Stefano.jpg, Alfredo Di Stéfano, FW, 1926-07-04, https://upload.wikimedia.org/wikipedia/en/8/84/Afa_logo_jerseys.png 5 | 4, https://upload.wikimedia.org/wikipedia/commons/d/db/Mario_Kempes_2006.jpg, Mario Kempes, FW, 1954-07-15, https://upload.wikimedia.org/wikipedia/en/8/84/Afa_logo_jerseys.png 6 | 5, https://upload.wikimedia.org/wikipedia/commons/thumb/9/97/Maradona_at_2012_GCC_Champions_League_final.JPG/228px-Maradona_at_2012_GCC_Champions_League_final.JPG, Diego Maradona, FW/MF, 1960-10-30, https://upload.wikimedia.org/wikipedia/en/8/84/Afa_logo_jerseys.png 7 | 6, https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/Passarella_world_cup.jpg/362px-Passarella_world_cup.jpg, Daniel Passarella, DF, 1953-05-25, https://upload.wikimedia.org/wikipedia/en/8/84/Afa_logo_jerseys.png 8 | 7, https://upload.wikimedia.org/wikipedia/commons/thumb/a/a7/Saviola_after_Goal.jpg/202px-Saviola_after_Goal.jpg, Javier Saviola, FW, 1981-12-11, https://upload.wikimedia.org/wikipedia/en/8/84/Afa_logo_jerseys.png 9 | 8, https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/EnriqueSivori_1956.jpeg/174px-EnriqueSivori_1956.jpeg, Omar Sivori, FW, 1935-10-02, https://upload.wikimedia.org/wikipedia/en/8/84/Afa_logo_jerseys.png 10 | 9, https://upload.wikimedia.org/wikipedia/commons/2/20/JSV-Presidente-Estudiantes-2014.JPG, Juan Sebastián Verón, MF, 1975-03-09,https://upload.wikimedia.org/wikipedia/en/8/84/Afa_logo_jerseys.png 11 | 10, https://upload.wikimedia.org/wikipedia/commons/thumb/e/e7/Metalist-Inter_%282%29.jpg/159px-Metalist-Inter_%282%29.jpg, Javier Zanetti, DF/MF, 1973-08-10, https://upload.wikimedia.org/wikipedia/en/8/84/Afa_logo_jerseys.png 12 | 11, https://upload.wikimedia.org/wikipedia/commons/thumb/c/c5/Jan_Ceulemans.jpg/159px-Jan_Ceulemans.jpg, Jan Ceulemans, MF, 1957-02-28, https://upload.wikimedia.org/wikipedia/en/f/f5/Belgium_urbsfa.png 13 | 12, https://upload.wikimedia.org/wikipedia/commons/thumb/1/16/Jean-Marie_Pfaff_at_Runa_Ralley_2007_cropped.jpg/163px-Jean-Marie_Pfaff_at_Runa_Ralley_2007_cropped.jpg, Jean-Marie Pfaff, GK, 1953-12-04, https://upload.wikimedia.org/wikipedia/en/f/f5/Belgium_urbsfa.png 14 | 13, https://upload.wikimedia.org/wikipedia/commons/thumb/9/92/Franky_Van_der_Elst_Lommel_United.JPG/161px-Franky_Van_der_Elst_Lommel_United.JPG, Franky Van der Elst, MF, 1961-04-30, https://upload.wikimedia.org/wikipedia/en/f/f5/Belgium_urbsfa.png 15 | 14, https://upload.wikimedia.org/wikipedia/commons/thumb/4/4a/Pele200802FabioRodriguesPozzebomAgenciaBrasil.jpg/155px-Pele200802FabioRodriguesPozzebomAgenciaBrasil.jpg, Pelé, FW, 1940-10-23, https://upload.wikimedia.org/wikipedia/en/thumb/9/99/Confedera%C3%A7%C3%A3o_Brasileira_de_Futebol_%28escudo%29.svg/179px-Confedera%C3%A7%C3%A3o_Brasileira_de_Futebol_%28escudo%29.svg.png 16 | 15, https://upload.wikimedia.org/wikipedia/commons/thumb/6/67/Roberto_Carlos_in_Moscow_3.jpg/171px-Roberto_Carlos_in_Moscow_3.jpg, Roberto Carlos, DF, 1973-04-10, https://upload.wikimedia.org/wikipedia/en/thumb/9/99/Confedera%C3%A7%C3%A3o_Brasileira_de_Futebol_%28escudo%29.svg/143px-Confedera%C3%A7%C3%A3o_Brasileira_de_Futebol_%28escudo%29.svg.png 17 | 16, https://upload.wikimedia.org/wikipedia/commons/thumb/e/e4/Ronaldinho_72.jpg/159px-Ronaldinho_72.jpg, Ronaldinho, MF/FW, 1980-03-21, https://upload.wikimedia.org/wikipedia/en/thumb/9/99/Confedera%C3%A7%C3%A3o_Brasileira_de_Futebol_%28escudo%29.svg/143px-Confedera%C3%A7%C3%A3o_Brasileira_de_Futebol_%28escudo%29.svg.png 18 | 17, https://upload.wikimedia.org/wikipedia/commons/thumb/4/4d/Ronaldo-14-05-2013.jpg/180px-Ronaldo-14-05-2013.jpg, Ronaldo, FW, 1976-09-18,https://upload.wikimedia.org/wikipedia/en/thumb/9/99/Confedera%C3%A7%C3%A3o_Brasileira_de_Futebol_%28escudo%29.svg/143px-Confedera%C3%A7%C3%A3o_Brasileira_de_Futebol_%28escudo%29.svg.png 19 | 18, https://upload.wikimedia.org/wikipedia/commons/b/b9/Dasaev_Rinat.JPG, Rinat Dasayev, GK, 1957-06-13, https://upload.wikimedia.org/wikipedia/en/7/77/Russian_Football_Union.png 20 | 19, https://upload.wikimedia.org/wikipedia/commons/thumb/a/a1/Andriy_Shevchenko_Nike_Clash_Collection.JPG/159px-Andriy_Shevchenko_Nike_Clash_Collection.JPG, Andriy Shevchenko, FW, 1976-09-29, https://upload.wikimedia.org/wikipedia/en/thumb/c/c5/Football_Federation_of_Ukraine.svg/106px-Football_Federation_of_Ukraine.svg.png 21 | 20, https://upload.wikimedia.org/wikipedia/commons/d/d2/Mia_Hamm_signing_an_autograph.jpg, Mia Hamm, FW, 1972-03-17, https://upload.wikimedia.org/wikipedia/commons/thumb/5/57/USA_Soccer_Team_logo.svg/174px-USA_Soccer_Team_logo.svg.png -------------------------------------------------------------------------------- /sample/src/main/java/com/cleveroad/sample/SampleApplication.java: -------------------------------------------------------------------------------- 1 | package com.cleveroad.sample; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | 6 | public class SampleApplication extends Application { 7 | 8 | private static Context context; 9 | 10 | @Override 11 | public void onCreate() { 12 | super.onCreate(); 13 | SampleApplication.context = getApplicationContext(); 14 | } 15 | 16 | public static Context getAppContext() { 17 | return SampleApplication.context; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /sample/src/main/java/com/cleveroad/sample/adapter/SampleLinkedTableAdapter.java: -------------------------------------------------------------------------------- 1 | package com.cleveroad.sample.adapter; 2 | 3 | import android.content.Context; 4 | import android.content.res.Resources; 5 | import android.graphics.drawable.Drawable; 6 | import android.graphics.drawable.GradientDrawable; 7 | import android.support.annotation.NonNull; 8 | import android.support.annotation.Nullable; 9 | import android.support.v4.graphics.ColorUtils; 10 | import android.text.TextUtils; 11 | import android.view.LayoutInflater; 12 | import android.view.View; 13 | import android.view.ViewGroup; 14 | import android.widget.ImageView; 15 | import android.widget.TextView; 16 | 17 | import com.bumptech.glide.Glide; 18 | import com.bumptech.glide.load.DataSource; 19 | import com.bumptech.glide.load.engine.GlideException; 20 | import com.bumptech.glide.load.resource.bitmap.FitCenter; 21 | import com.bumptech.glide.request.RequestListener; 22 | import com.bumptech.glide.request.RequestOptions; 23 | import com.bumptech.glide.request.target.Target; 24 | import com.cleveroad.adaptivetablelayout.LinkedAdaptiveTableAdapter; 25 | import com.cleveroad.adaptivetablelayout.ViewHolderImpl; 26 | import com.cleveroad.sample.R; 27 | import com.cleveroad.sample.datasource.TableDataSource; 28 | 29 | public class SampleLinkedTableAdapter extends LinkedAdaptiveTableAdapter { 30 | private static final int[] COLORS = new int[]{ 31 | 0xffe62a10, 0xffe91e63, 0xff9c27b0, 0xff673ab7, 0xff3f51b5, 32 | 0xff5677fc, 0xff03a9f4, 0xff00bcd4, 0xff009688, 0xff259b24, 33 | 0xff8bc34a, 0xffcddc39, 0xffffeb3b, 0xffffc107, 0xffff9800, 0xffff5722}; 34 | 35 | private final LayoutInflater mLayoutInflater; 36 | private final TableDataSource mTableDataSource; 37 | private final int mColumnWidth; 38 | private final int mRowHeight; 39 | private final int mHeaderHeight; 40 | private final int mHeaderWidth; 41 | 42 | public SampleLinkedTableAdapter(Context context, TableDataSource tableDataSource) { 43 | mLayoutInflater = LayoutInflater.from(context); 44 | mTableDataSource = tableDataSource; 45 | Resources res = context.getResources(); 46 | mColumnWidth = res.getDimensionPixelSize(R.dimen.column_width); 47 | mRowHeight = res.getDimensionPixelSize(R.dimen.row_height); 48 | mHeaderHeight = res.getDimensionPixelSize(R.dimen.column_header_height); 49 | mHeaderWidth = res.getDimensionPixelSize(R.dimen.row_header_width); 50 | } 51 | 52 | @Override 53 | public int getRowCount() { 54 | return mTableDataSource.getRowsCount(); 55 | } 56 | 57 | @Override 58 | public int getColumnCount() { 59 | return mTableDataSource.getColumnsCount(); 60 | } 61 | 62 | @NonNull 63 | @Override 64 | public ViewHolderImpl onCreateItemViewHolder(@NonNull ViewGroup parent) { 65 | return new TestViewHolder(mLayoutInflater.inflate(R.layout.item_card, parent, false)); 66 | } 67 | 68 | @NonNull 69 | @Override 70 | public ViewHolderImpl onCreateColumnHeaderViewHolder(@NonNull ViewGroup parent) { 71 | return new TestHeaderColumnViewHolder(mLayoutInflater.inflate(R.layout.item_header_column, parent, false)); 72 | } 73 | 74 | @NonNull 75 | @Override 76 | public ViewHolderImpl onCreateRowHeaderViewHolder(@NonNull ViewGroup parent) { 77 | return new TestHeaderRowViewHolder(mLayoutInflater.inflate(R.layout.item_header_row, parent, false)); 78 | } 79 | 80 | @NonNull 81 | @Override 82 | public ViewHolderImpl onCreateLeftTopHeaderViewHolder(@NonNull ViewGroup parent) { 83 | return new TestHeaderLeftTopViewHolder(mLayoutInflater.inflate(R.layout.item_header_left_top, parent, false)); 84 | } 85 | 86 | @Override 87 | public void onBindViewHolder(@NonNull ViewHolderImpl viewHolder, int row, int column) { 88 | final TestViewHolder vh = (TestViewHolder) viewHolder; 89 | String itemData = mTableDataSource.getItemData(row, column); // skip headers 90 | 91 | if (TextUtils.isEmpty(itemData)) { 92 | itemData = ""; 93 | } 94 | 95 | itemData = itemData.trim(); 96 | vh.tvText.setVisibility(View.VISIBLE); 97 | vh.ivImage.setVisibility(View.VISIBLE); 98 | vh.tvText.setText(itemData); 99 | 100 | Glide.with(vh.ivImage.getContext()) 101 | .load(itemData) 102 | .apply(new RequestOptions().transform(new FitCenter())) 103 | .listener(new RequestListener() { 104 | @Override 105 | public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, boolean isFirstResource) { 106 | vh.ivImage.setVisibility(View.INVISIBLE); 107 | vh.tvText.setVisibility(View.VISIBLE); 108 | return false; 109 | } 110 | 111 | @Override 112 | public boolean onResourceReady(Drawable resource, Object model, Target target, DataSource dataSource, boolean isFirstResource) { 113 | vh.ivImage.setVisibility(View.VISIBLE); 114 | vh.tvText.setVisibility(View.INVISIBLE); 115 | return false; 116 | } 117 | }) 118 | .into(vh.ivImage); 119 | } 120 | 121 | @Override 122 | public void onBindHeaderColumnViewHolder(@NonNull ViewHolderImpl viewHolder, int column) { 123 | int color = COLORS[column % COLORS.length]; 124 | TestHeaderColumnViewHolder vh = (TestHeaderColumnViewHolder) viewHolder; 125 | vh.tvText.setText(mTableDataSource.getColumnHeaderData(column)); // skip left top header 126 | GradientDrawable gd = new GradientDrawable( 127 | mIsRtl ? GradientDrawable.Orientation.RIGHT_LEFT : GradientDrawable.Orientation.LEFT_RIGHT, 128 | new int[]{ColorUtils.setAlphaComponent(color, 50), 0x00000000}); 129 | gd.setCornerRadius(0f); 130 | vh.vGradient.setBackground(gd); 131 | vh.vLine.setBackgroundColor(color); 132 | } 133 | 134 | @Override 135 | public void onBindHeaderRowViewHolder(@NonNull ViewHolderImpl viewHolder, int row) { 136 | TestHeaderRowViewHolder vh = (TestHeaderRowViewHolder) viewHolder; 137 | vh.tvText.setText(mTableDataSource.getItemData(row, 0)); 138 | } 139 | 140 | @Override 141 | public void onBindLeftTopHeaderViewHolder(@NonNull ViewHolderImpl viewHolder) { 142 | TestHeaderLeftTopViewHolder vh = (TestHeaderLeftTopViewHolder) viewHolder; 143 | vh.tvText.setText(mTableDataSource.getFirstHeaderData()); 144 | } 145 | 146 | @Override 147 | public int getColumnWidth(int column) { 148 | return mColumnWidth; 149 | } 150 | 151 | @Override 152 | public int getHeaderColumnHeight() { 153 | return mHeaderHeight; 154 | } 155 | 156 | @Override 157 | public int getRowHeight(int row) { 158 | return mRowHeight; 159 | } 160 | 161 | @Override 162 | public int getHeaderRowWidth() { 163 | return mHeaderWidth; 164 | } 165 | 166 | //------------------------------------- view holders ------------------------------------------ 167 | 168 | private static class TestViewHolder extends ViewHolderImpl { 169 | TextView tvText; 170 | ImageView ivImage; 171 | 172 | private TestViewHolder(@NonNull View itemView) { 173 | super(itemView); 174 | tvText = itemView.findViewById(R.id.tvText); 175 | ivImage = itemView.findViewById(R.id.ivImage); 176 | } 177 | } 178 | 179 | private static class TestHeaderColumnViewHolder extends ViewHolderImpl { 180 | TextView tvText; 181 | View vGradient; 182 | View vLine; 183 | 184 | private TestHeaderColumnViewHolder(@NonNull View itemView) { 185 | super(itemView); 186 | tvText = itemView.findViewById(R.id.tvText); 187 | vGradient = itemView.findViewById(R.id.vGradient); 188 | vLine = itemView.findViewById(R.id.vLine); 189 | } 190 | } 191 | 192 | private static class TestHeaderRowViewHolder extends ViewHolderImpl { 193 | TextView tvText; 194 | 195 | TestHeaderRowViewHolder(@NonNull View itemView) { 196 | super(itemView); 197 | tvText = itemView.findViewById(R.id.tvText); 198 | } 199 | } 200 | 201 | private static class TestHeaderLeftTopViewHolder extends ViewHolderImpl { 202 | TextView tvText; 203 | 204 | private TestHeaderLeftTopViewHolder(@NonNull View itemView) { 205 | super(itemView); 206 | tvText = itemView.findViewById(R.id.tvText); 207 | } 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /sample/src/main/java/com/cleveroad/sample/datasource/Constants.java: -------------------------------------------------------------------------------- 1 | package com.cleveroad.sample.datasource; 2 | 3 | public class Constants { 4 | 5 | public static final String EXTRA_TITLE = "EXTRA_TITLE"; 6 | public static final String EXTRA_VALUE = "EXTRA_VALUE"; 7 | public static final String EXTRA_COLUMN_NUMBER = "EXTRA_COLUMN_NUMBER"; 8 | public static final String EXTRA_ROW_NUMBER = "EXTRA_ROW_NUMBER"; 9 | public static final String EXTRA_BEFORE_OR_AFTER = "EXTRA_BEFORE_OR_AFTER"; 10 | 11 | private static int requestCode = 1; 12 | private static int actionChangeData = 1; 13 | 14 | public static final int REQUEST_CODE_EDIT_SONG = generateRequestCode(); 15 | public static final int REQUEST_CODE_DELETE_ROW = generateRequestCode(); 16 | public static final int REQUEST_CODE_ADD_ROW = generateRequestCode(); 17 | public static final int REQUEST_CODE_ADD_ROW_CONFIRMED = generateRequestCode(); 18 | public static final int REQUEST_CODE_ADD_COLUMN_CONFIRMED = generateRequestCode(); 19 | public static final int REQUEST_CODE_DELETE_COLUMN = generateRequestCode(); 20 | public static final int REQUEST_CODE_ADD_COLUMN = generateRequestCode(); 21 | public static final int REQUEST_CODE_DELETE_ROW_CONFIRMED = generateRequestCode(); 22 | public static final int REQUEST_CODE_DELETE_COLUMN_CONFIRMED = generateRequestCode(); 23 | public static final int REQUEST_CODE_SETTINGS = generateRequestCode(); 24 | 25 | public static final int ADD_ROW = generateActionChangeData(); 26 | public static final int DELETE_ROW = generateActionChangeData(); 27 | public static final int ADD_COLUMN = generateActionChangeData(); 28 | public static final int DELETE_COLUMN = generateActionChangeData(); 29 | 30 | private static int generateActionChangeData(){ 31 | return ++actionChangeData; 32 | } 33 | 34 | private static int generateRequestCode(){ 35 | return ++requestCode; 36 | } 37 | } -------------------------------------------------------------------------------- /sample/src/main/java/com/cleveroad/sample/datasource/TableDataSource.java: -------------------------------------------------------------------------------- 1 | package com.cleveroad.sample.datasource; 2 | 3 | public interface TableDataSource { 4 | 5 | int getRowsCount(); 6 | 7 | int getColumnsCount(); 8 | 9 | TFirstHeaderDataType getFirstHeaderData(); 10 | 11 | TRowHeaderDataType getRowHeaderData(int index); 12 | 13 | TColumnHeaderDataType getColumnHeaderData(int index); 14 | 15 | TItemDataType getItemData(int rowIndex, int columnIndex); 16 | } 17 | -------------------------------------------------------------------------------- /sample/src/main/java/com/cleveroad/sample/datasource/UpdateFileCallback.java: -------------------------------------------------------------------------------- 1 | package com.cleveroad.sample.datasource; 2 | 3 | public interface UpdateFileCallback { 4 | 5 | void onFileUpdated(String fileName, boolean isSuccess); 6 | 7 | void onFileUpdated(String fileName); 8 | } 9 | -------------------------------------------------------------------------------- /sample/src/main/java/com/cleveroad/sample/provider/DocumentsProvider.java: -------------------------------------------------------------------------------- 1 | package com.cleveroad.sample.provider; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.content.ContentProvider; 5 | import android.content.ContentValues; 6 | import android.content.Context; 7 | import android.database.Cursor; 8 | import android.net.Uri; 9 | import android.os.Environment; 10 | import android.os.StrictMode; 11 | import android.provider.DocumentsContract; 12 | import android.provider.MediaStore; 13 | import android.support.annotation.NonNull; 14 | import android.support.annotation.Nullable; 15 | 16 | import com.cleveroad.sample.SampleApplication; 17 | 18 | import java.io.File; 19 | 20 | public class DocumentsProvider extends ContentProvider { 21 | 22 | @Override 23 | public boolean onCreate() { 24 | return true; 25 | } 26 | 27 | @Nullable 28 | @Override 29 | public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) { 30 | return null; 31 | } 32 | 33 | @SuppressLint("NewApi") 34 | @Override 35 | public String getType(@NonNull Uri uri) { 36 | if (DocumentsContract.isDocumentUri(SampleApplication.getAppContext(), uri)) { 37 | if (isExternalStorageDocument(uri)) {// ExternalStorageProvider 38 | final String docId = DocumentsContract.getDocumentId(uri); 39 | final String[] split = docId.split(":"); 40 | final String type = split[0]; 41 | String storageDefinition; 42 | 43 | if ("primary".equalsIgnoreCase(type)) { 44 | return Environment.getExternalStorageDirectory() + "/" + split[1]; 45 | } else { 46 | if (Environment.isExternalStorageRemovable()) { 47 | storageDefinition = "EXTERNAL_STORAGE"; 48 | } else { 49 | storageDefinition = "SECONDARY_STORAGE"; 50 | } 51 | String path = System.getenv(storageDefinition) + "/" + split[1]; 52 | 53 | File file = new File(path); 54 | if (!file.exists()) { 55 | path = "storage/" + type + "/" + split[1]; 56 | file = new File(path.trim()); 57 | if (!file.exists() || file.isDirectory()) { 58 | throw new NullPointerException("Couldn't find file path -> " + path); 59 | } 60 | } 61 | return path; 62 | } 63 | } else if (isDownloadsDocument(uri)) { 64 | final String id = DocumentsContract.getDocumentId(uri); 65 | if (id.contains("raw:")) { 66 | final String[] idArray = id.split("raw:"); 67 | return idArray[1]; 68 | } else { 69 | return null; 70 | } 71 | 72 | } else if (isMediaDocument(uri)) {// MediaProvider 73 | final String docId = DocumentsContract.getDocumentId(uri); 74 | final String[] split = docId.split(":"); 75 | final String type = split[0]; 76 | 77 | Uri contentUri = null; 78 | if ("image".equals(type)) { 79 | contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; 80 | } else if ("video".equals(type)) { 81 | contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; 82 | } else if ("audio".equals(type)) { 83 | contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; 84 | } 85 | 86 | final String selection = "_id=?"; 87 | final String[] selectionArgs = new String[]{ 88 | split[1] 89 | }; 90 | return getDataColumn(SampleApplication.getAppContext(), contentUri, selection, selectionArgs); 91 | } 92 | } else if ("content".equalsIgnoreCase(uri.getScheme())) {// MediaStore (and general) 93 | // Return the remote address 94 | if (isGooglePhotosUri(uri)) { 95 | return uri.getLastPathSegment(); 96 | } 97 | return getDataColumn(SampleApplication.getAppContext(), uri, null, null); 98 | } else if ("file".equalsIgnoreCase(uri.getScheme())) {// File 99 | return uri.getPath(); 100 | } 101 | return null; 102 | } 103 | 104 | @Nullable 105 | @Override 106 | public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) { 107 | return null; 108 | } 109 | 110 | @Override 111 | public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) { 112 | return 0; 113 | } 114 | 115 | @Override 116 | public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) { 117 | return 0; 118 | } 119 | 120 | 121 | public static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) { 122 | Cursor cursor = null; 123 | final String column = "_data"; 124 | final String[] projection = { 125 | column 126 | }; 127 | 128 | try { 129 | cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null); 130 | if (cursor != null && cursor.moveToFirst()) { 131 | StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder(); 132 | StrictMode.setVmPolicy(builder.build()); 133 | final int columnIndex = cursor.getColumnIndexOrThrow(column); 134 | return cursor.getString(columnIndex); 135 | } 136 | } finally { 137 | if (cursor != null) { 138 | cursor.close(); 139 | } 140 | } 141 | return null; 142 | } 143 | 144 | public static boolean isExternalStorageDocument(Uri uri) { 145 | return "com.android.externalstorage.documents".equals(uri.getAuthority()); 146 | } 147 | 148 | 149 | public static boolean isDownloadsDocument(Uri uri) { 150 | return "com.android.providers.downloads.documents".equals(uri.getAuthority()); 151 | } 152 | 153 | public static boolean isMediaDocument(Uri uri) { 154 | return "com.android.providers.media.documents".equals(uri.getAuthority()); 155 | } 156 | 157 | public static boolean isGooglePhotosUri(Uri uri) { 158 | return "com.google.android.apps.photos.content".equals(uri.getAuthority()); 159 | } 160 | } 161 | 162 | -------------------------------------------------------------------------------- /sample/src/main/java/com/cleveroad/sample/ui/CsvPickerFragment.java: -------------------------------------------------------------------------------- 1 | package com.cleveroad.sample.ui; 2 | 3 | import android.Manifest; 4 | import android.annotation.SuppressLint; 5 | import android.app.Activity; 6 | import android.content.Intent; 7 | import android.content.pm.PackageManager; 8 | import android.graphics.Color; 9 | import android.os.Build; 10 | import android.os.Bundle; 11 | import android.os.Environment; 12 | import android.support.annotation.NonNull; 13 | import android.support.annotation.Nullable; 14 | import android.support.v4.app.Fragment; 15 | import android.text.SpannableString; 16 | import android.text.Spanned; 17 | import android.text.TextPaint; 18 | import android.text.TextUtils; 19 | import android.text.method.LinkMovementMethod; 20 | import android.text.style.ClickableSpan; 21 | import android.view.LayoutInflater; 22 | import android.view.View; 23 | import android.view.ViewGroup; 24 | import android.widget.TextView; 25 | 26 | import com.cleveroad.sample.R; 27 | import com.cleveroad.sample.provider.DocumentsProvider; 28 | import com.cleveroad.sample.utils.FileUtils; 29 | import com.cleveroad.sample.utils.PermissionHelper; 30 | 31 | import java.io.File; 32 | import java.io.IOException; 33 | import java.io.InputStream; 34 | import java.util.Objects; 35 | 36 | import static android.content.Intent.EXTRA_MIME_TYPES; 37 | 38 | public class CsvPickerFragment extends Fragment implements View.OnClickListener { 39 | private static final int REQUEST_CODE_PERMISSION_READ_EXTERNAL_STORAGE = 1; 40 | private static final int REQUEST_CODE_PERMISSION_READ_EXTERNAL_STORAGE_DEMO = 3; 41 | private static final int REQUEST_CODE_PICK_CSV = 2; 42 | private static final int START_CHARACTER = 33; 43 | private static final int END_CHARACTER = 37; 44 | private TextView tvPickFile; 45 | 46 | public static CsvPickerFragment newInstance() { 47 | Bundle args = new Bundle(); 48 | CsvPickerFragment fragment = new CsvPickerFragment(); 49 | fragment.setArguments(args); 50 | return fragment; 51 | } 52 | 53 | @Override 54 | public void onCreate(@Nullable Bundle savedInstanceState) { 55 | super.onCreate(savedInstanceState); 56 | setRetainInstance(true); 57 | } 58 | 59 | @Nullable 60 | @Override 61 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 62 | View view = inflater.inflate(R.layout.fragment_csv_picker, container, false); 63 | view.findViewById(R.id.bPickFile).setOnClickListener(this); 64 | tvPickFile = view.findViewById(R.id.tvPickFile); 65 | return view; 66 | } 67 | 68 | @Override 69 | public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { 70 | super.onViewCreated(view, savedInstanceState); 71 | SpannableString ss = new SpannableString(getString(R.string.pick_csv_or_demo_file)); 72 | ClickableSpan clickableSpan = new ClickableSpan() { 73 | @Override 74 | public void onClick(View textView) { 75 | if (PermissionHelper.checkOrRequest(CsvPickerFragment.this, REQUEST_CODE_PERMISSION_READ_EXTERNAL_STORAGE_DEMO, 76 | Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { 77 | createDemoFile(); 78 | } 79 | } 80 | 81 | @Override 82 | public void updateDrawState(TextPaint ds) { 83 | super.updateDrawState(ds); 84 | ds.setUnderlineText(false); 85 | } 86 | }; 87 | ss.setSpan(clickableSpan, START_CHARACTER, END_CHARACTER, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 88 | 89 | tvPickFile.setText(ss); 90 | tvPickFile.setMovementMethod(LinkMovementMethod.getInstance()); 91 | tvPickFile.setHighlightColor(Color.TRANSPARENT); 92 | } 93 | 94 | @Override 95 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { 96 | super.onRequestPermissionsResult(requestCode, permissions, grantResults); 97 | if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { 98 | switch (requestCode) { 99 | case REQUEST_CODE_PERMISSION_READ_EXTERNAL_STORAGE: 100 | pickCsvFile(); 101 | break; 102 | case REQUEST_CODE_PERMISSION_READ_EXTERNAL_STORAGE_DEMO: 103 | createDemoFile(); 104 | break; 105 | } 106 | } 107 | } 108 | 109 | @SuppressLint("NewApi") 110 | @Override 111 | public void onActivityResult(int requestCode, int resultCode, Intent data) { 112 | super.onActivityResult(requestCode, resultCode, data); 113 | DocumentsProvider documentsProvider = new DocumentsProvider(); 114 | if (requestCode == REQUEST_CODE_PICK_CSV && data != null && resultCode == Activity.RESULT_OK) { 115 | Activity activity = getActivity(); 116 | if (activity instanceof OnCsvFileSelectedListener) { 117 | ((OnCsvFileSelectedListener) activity).onCsvFileSelected(documentsProvider.getType(data.getData())); 118 | } 119 | } 120 | } 121 | 122 | @Override 123 | public void onClick(View v) { 124 | if (PermissionHelper.checkOrRequest(this, REQUEST_CODE_PERMISSION_READ_EXTERNAL_STORAGE, 125 | Manifest.permission.WRITE_EXTERNAL_STORAGE)) { 126 | pickCsvFile(); 127 | } 128 | } 129 | 130 | private void pickCsvFile() { 131 | String[] mimeTypes = {"text/comma-separated-values", "text/csv"}; 132 | Intent intent; 133 | 134 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { 135 | intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); 136 | intent.putExtra(EXTRA_MIME_TYPES, mimeTypes); 137 | intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION); 138 | } else { 139 | intent = new Intent(Intent.ACTION_GET_CONTENT); 140 | } 141 | 142 | intent.setType("*/*"); 143 | intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 144 | startActivityForResult(Intent.createChooser(intent, getString(R.string.pick_file)), REQUEST_CODE_PICK_CSV); 145 | } 146 | 147 | private void createDemoFile() { 148 | File file = createDemoTempFile(); 149 | try { 150 | if (!file.exists() && file.createNewFile()) { 151 | InputStream inputStream = null; 152 | if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) { 153 | inputStream = Objects.requireNonNull(getContext()).getAssets().open("fifa100.csv"); 154 | } 155 | FileUtils.copy(inputStream, file); 156 | } 157 | } catch (IOException e) { 158 | e.printStackTrace(); 159 | file = null; 160 | } 161 | 162 | String path = file == null ? "" : file.getPath(); 163 | 164 | if (!TextUtils.isEmpty(path)) { 165 | Activity activity = getActivity(); 166 | if (activity instanceof OnCsvFileSelectedListener) { 167 | ((OnCsvFileSelectedListener) activity).onCsvFileSelected(path); 168 | } 169 | } 170 | } 171 | 172 | public File createDemoTempFile() { 173 | String tempFileName = "DEMO_table_layout_application.csv"; 174 | return new File(Environment.getExternalStorageDirectory(), tempFileName); 175 | } 176 | 177 | interface OnCsvFileSelectedListener { 178 | void onCsvFileSelected(String file); 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /sample/src/main/java/com/cleveroad/sample/ui/SampleActivity.java: -------------------------------------------------------------------------------- 1 | package com.cleveroad.sample.ui; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.app.AppCompatActivity; 5 | import android.widget.Toast; 6 | 7 | import com.cleveroad.sample.R; 8 | 9 | import java.io.File; 10 | 11 | public class SampleActivity extends AppCompatActivity implements 12 | CsvPickerFragment.OnCsvFileSelectedListener { 13 | @Override 14 | protected void onCreate(Bundle savedInstanceState) { 15 | super.onCreate(savedInstanceState); 16 | setContentView(R.layout.activity_sample); 17 | if (savedInstanceState == null) { 18 | getSupportFragmentManager() 19 | .beginTransaction() 20 | .add(R.id.container, CsvPickerFragment.newInstance(), CsvPickerFragment.class.getSimpleName()) 21 | .commit(); 22 | } 23 | } 24 | 25 | @Override 26 | public void onCsvFileSelected(String fileName) { 27 | if (fileName != null && !fileName.isEmpty()) { 28 | File file = new File(fileName); 29 | if (file.exists() && fileName.endsWith(".csv")) { 30 | getSupportFragmentManager() 31 | .beginTransaction() 32 | .replace(R.id.container, TableLayoutFragment.newInstance(fileName), CsvPickerFragment.class.getSimpleName()) 33 | .addToBackStack(CsvPickerFragment.class.getSimpleName()) 34 | .commit(); 35 | } else { 36 | Toast.makeText(this, R.string.not_csv_file_error, Toast.LENGTH_SHORT).show(); 37 | } 38 | } else { 39 | Toast.makeText(this, R.string.no_such_file_error, Toast.LENGTH_SHORT).show(); 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /sample/src/main/java/com/cleveroad/sample/ui/dialogs/AddColumnDialog.java: -------------------------------------------------------------------------------- 1 | package com.cleveroad.sample.ui.dialogs; 2 | 3 | import android.app.Activity; 4 | import android.content.Intent; 5 | import android.graphics.drawable.ColorDrawable; 6 | import android.os.Bundle; 7 | import android.support.annotation.NonNull; 8 | import android.support.annotation.Nullable; 9 | import android.support.v4.app.DialogFragment; 10 | import android.support.v4.app.Fragment; 11 | import android.view.LayoutInflater; 12 | import android.view.View; 13 | import android.view.ViewGroup; 14 | 15 | import com.cleveroad.sample.R; 16 | 17 | import static com.cleveroad.sample.datasource.Constants.EXTRA_BEFORE_OR_AFTER; 18 | import static com.cleveroad.sample.datasource.Constants.EXTRA_COLUMN_NUMBER; 19 | import static com.cleveroad.sample.datasource.Constants.REQUEST_CODE_ADD_COLUMN_CONFIRMED; 20 | 21 | public class AddColumnDialog extends DialogFragment implements View.OnClickListener { 22 | private int mColumn; 23 | 24 | public static AddColumnDialog newInstance(int column) { 25 | AddColumnDialog fragment = new AddColumnDialog(); 26 | Bundle args = new Bundle(); 27 | args.putInt(EXTRA_COLUMN_NUMBER, column); 28 | fragment.setArguments(args); 29 | return fragment; 30 | } 31 | 32 | @Override 33 | public void onCreate(@Nullable Bundle savedInstanceState) { 34 | super.onCreate(savedInstanceState); 35 | Bundle args = getArguments(); 36 | if (args != null) { 37 | mColumn = args.getInt(EXTRA_COLUMN_NUMBER); 38 | } 39 | } 40 | 41 | @Override 42 | public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 43 | //noinspection ConstantConditions 44 | getDialog().getWindow().setBackgroundDrawable(new ColorDrawable(android.graphics.Color.TRANSPARENT)); 45 | return inflater.inflate(R.layout.dialog_add_column, container, false); 46 | } 47 | 48 | @Override 49 | public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { 50 | super.onViewCreated(view, savedInstanceState); 51 | view.findViewById(R.id.bColumnRight).setOnClickListener(this); 52 | view.findViewById(R.id.bColumnLeft).setOnClickListener(this); 53 | view.findViewById(R.id.bNegative).setOnClickListener(this); 54 | } 55 | 56 | @Override 57 | public void onClick(View v) { 58 | switch (v.getId()) { 59 | case R.id.bColumnRight: 60 | sendResult(mColumn, false); 61 | break; 62 | case R.id.bColumnLeft: 63 | sendResult(mColumn, true); 64 | break; 65 | case R.id.bNegative: 66 | // negative 67 | Fragment fragment = getParentFragment(); 68 | if (fragment != null) { 69 | fragment.onActivityResult(REQUEST_CODE_ADD_COLUMN_CONFIRMED, Activity.RESULT_CANCELED, null); 70 | } 71 | break; 72 | default: 73 | //do nothing 74 | } 75 | dismiss(); 76 | } 77 | 78 | private void sendResult(int column, boolean beforeORAfter) { 79 | Fragment fragment = getParentFragment(); 80 | if (fragment != null) { 81 | Intent intent = new Intent(); 82 | intent.putExtra(EXTRA_COLUMN_NUMBER, column); 83 | intent.putExtra(EXTRA_BEFORE_OR_AFTER, beforeORAfter); 84 | fragment.onActivityResult(REQUEST_CODE_ADD_COLUMN_CONFIRMED, Activity.RESULT_OK, intent); 85 | } 86 | } 87 | } -------------------------------------------------------------------------------- /sample/src/main/java/com/cleveroad/sample/ui/dialogs/AddRowDialog.java: -------------------------------------------------------------------------------- 1 | package com.cleveroad.sample.ui.dialogs; 2 | 3 | import android.app.Activity; 4 | import android.content.Intent; 5 | import android.graphics.drawable.ColorDrawable; 6 | import android.os.Bundle; 7 | import android.support.annotation.Nullable; 8 | import android.support.v4.app.DialogFragment; 9 | import android.support.v4.app.Fragment; 10 | import android.view.LayoutInflater; 11 | import android.view.View; 12 | import android.view.ViewGroup; 13 | 14 | import com.cleveroad.sample.R; 15 | 16 | import static com.cleveroad.sample.datasource.Constants.EXTRA_BEFORE_OR_AFTER; 17 | import static com.cleveroad.sample.datasource.Constants.EXTRA_ROW_NUMBER; 18 | import static com.cleveroad.sample.datasource.Constants.REQUEST_CODE_ADD_ROW_CONFIRMED; 19 | 20 | public class AddRowDialog extends DialogFragment implements View.OnClickListener { 21 | 22 | private int mRow; 23 | 24 | public static AddRowDialog newInstance(int row) { 25 | AddRowDialog fragment = new AddRowDialog(); 26 | Bundle args = new Bundle(); 27 | args.putInt(EXTRA_ROW_NUMBER, row); 28 | fragment.setArguments(args); 29 | return fragment; 30 | } 31 | 32 | @Override 33 | public void onCreate(@Nullable Bundle savedInstanceState) { 34 | super.onCreate(savedInstanceState); 35 | Bundle args = getArguments(); 36 | if (args != null) { 37 | mRow = args.getInt(EXTRA_ROW_NUMBER); 38 | } 39 | } 40 | 41 | @Override 42 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 43 | //noinspection ConstantConditions 44 | getDialog().getWindow().setBackgroundDrawable(new ColorDrawable(android.graphics.Color.TRANSPARENT)); 45 | return inflater.inflate(R.layout.dialog_add_row, container, false); 46 | } 47 | 48 | @Override 49 | public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { 50 | super.onViewCreated(view, savedInstanceState); 51 | 52 | view.findViewById(R.id.bAbove).setOnClickListener(this); 53 | view.findViewById(R.id.bBelow).setOnClickListener(this); 54 | view.findViewById(R.id.bNegative).setOnClickListener(this); 55 | } 56 | 57 | @Override 58 | public void onClick(View v) { 59 | switch (v.getId()) { 60 | case R.id.bAbove: 61 | sendResult(mRow, true); 62 | break; 63 | case R.id.bBelow: 64 | sendResult(mRow, false); 65 | break; 66 | case R.id.bNegative: 67 | // negative 68 | Fragment fragment = getParentFragment(); 69 | if (fragment != null) { 70 | fragment.onActivityResult(REQUEST_CODE_ADD_ROW_CONFIRMED, Activity.RESULT_CANCELED, null); 71 | } 72 | break; 73 | default: 74 | //do nothing 75 | } 76 | 77 | dismiss(); 78 | } 79 | 80 | private void sendResult(int row, boolean beforeORAfter) { 81 | Fragment fragment = getParentFragment(); 82 | if (fragment != null) { 83 | Intent intent = new Intent(); 84 | intent.putExtra(EXTRA_ROW_NUMBER, row); 85 | intent.putExtra(EXTRA_BEFORE_OR_AFTER, beforeORAfter); 86 | fragment.onActivityResult(REQUEST_CODE_ADD_ROW_CONFIRMED, Activity.RESULT_OK, intent); 87 | } 88 | } 89 | } -------------------------------------------------------------------------------- /sample/src/main/java/com/cleveroad/sample/ui/dialogs/DeleteDialog.java: -------------------------------------------------------------------------------- 1 | package com.cleveroad.sample.ui.dialogs; 2 | 3 | import android.app.Activity; 4 | import android.content.Intent; 5 | import android.graphics.drawable.ColorDrawable; 6 | import android.os.Bundle; 7 | import android.support.annotation.Nullable; 8 | import android.support.v4.app.DialogFragment; 9 | import android.support.v4.app.Fragment; 10 | import android.view.LayoutInflater; 11 | import android.view.View; 12 | import android.view.ViewGroup; 13 | import android.widget.TextView; 14 | 15 | import com.cleveroad.sample.R; 16 | 17 | import static com.cleveroad.sample.datasource.Constants.EXTRA_COLUMN_NUMBER; 18 | import static com.cleveroad.sample.datasource.Constants.EXTRA_ROW_NUMBER; 19 | import static com.cleveroad.sample.datasource.Constants.REQUEST_CODE_DELETE_COLUMN_CONFIRMED; 20 | import static com.cleveroad.sample.datasource.Constants.REQUEST_CODE_DELETE_ROW_CONFIRMED; 21 | 22 | public class DeleteDialog extends DialogFragment implements View.OnClickListener { 23 | private static final int ZERO_COLUMN_OR_ROW = 0; 24 | private TextView tvTitle; 25 | private int mRow; 26 | private int mColumn; 27 | 28 | public static DeleteDialog newInstance(int row, int column) { 29 | DeleteDialog fragment = new DeleteDialog(); 30 | Bundle args = new Bundle(); 31 | args.putInt(EXTRA_ROW_NUMBER, row); 32 | args.putInt(EXTRA_COLUMN_NUMBER, column); 33 | fragment.setArguments(args); 34 | return fragment; 35 | } 36 | 37 | @Override 38 | public void onCreate(@Nullable Bundle savedInstanceState) { 39 | super.onCreate(savedInstanceState); 40 | Bundle args = getArguments(); 41 | if (args != null) { 42 | mRow = args.getInt(EXTRA_ROW_NUMBER); 43 | mColumn = args.getInt(EXTRA_COLUMN_NUMBER); 44 | } 45 | } 46 | 47 | @Override 48 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 49 | //noinspection ConstantConditions 50 | getDialog().getWindow().setBackgroundDrawable(new ColorDrawable(android.graphics.Color.TRANSPARENT)); 51 | return inflater.inflate(R.layout.dialog_delete, container, false); 52 | } 53 | 54 | @Override 55 | public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { 56 | super.onViewCreated(view, savedInstanceState); 57 | 58 | view.findViewById(R.id.bPositive).setOnClickListener(this); 59 | view.findViewById(R.id.bNegative).setOnClickListener(this); 60 | tvTitle = (TextView) view.findViewById(R.id.tvTitle); 61 | updateUi(); 62 | } 63 | 64 | @Override 65 | public void onClick(View v) { 66 | switch (v.getId()) { 67 | case R.id.bPositive: 68 | // positive 69 | delete(); 70 | break; 71 | case R.id.bNegative: 72 | // negative 73 | Fragment fragment = getParentFragment(); 74 | if (fragment != null) { 75 | fragment.onActivityResult(REQUEST_CODE_DELETE_ROW_CONFIRMED, Activity.RESULT_CANCELED, null); 76 | } 77 | break; 78 | default: 79 | //do nothing 80 | } 81 | 82 | dismiss(); 83 | } 84 | 85 | private void updateUi() { 86 | 87 | if (mColumn == ZERO_COLUMN_OR_ROW) { 88 | tvTitle.setText(getString(R.string.delete_row)); 89 | } else if (mRow == ZERO_COLUMN_OR_ROW) { 90 | tvTitle.setText(getString(R.string.delete_column)); 91 | } 92 | } 93 | 94 | private void delete(){ 95 | if (mColumn == ZERO_COLUMN_OR_ROW) { 96 | sendResult(EXTRA_ROW_NUMBER, mRow, REQUEST_CODE_DELETE_ROW_CONFIRMED); 97 | } else if (mRow == ZERO_COLUMN_OR_ROW) { 98 | sendResult(EXTRA_COLUMN_NUMBER, mColumn, REQUEST_CODE_DELETE_COLUMN_CONFIRMED); 99 | } 100 | } 101 | 102 | private void sendResult(String extra, int data, int requestCode) { 103 | Fragment fragment = getParentFragment(); 104 | if (fragment != null) { 105 | Intent intent = new Intent(); 106 | intent.putExtra(extra, data); 107 | fragment.onActivityResult(requestCode, Activity.RESULT_OK, intent); 108 | } 109 | } 110 | } -------------------------------------------------------------------------------- /sample/src/main/java/com/cleveroad/sample/ui/dialogs/EditItemDialog.java: -------------------------------------------------------------------------------- 1 | package com.cleveroad.sample.ui.dialogs; 2 | 3 | import android.app.Activity; 4 | import android.content.Intent; 5 | import android.graphics.drawable.ColorDrawable; 6 | import android.os.Build; 7 | import android.os.Bundle; 8 | import android.support.annotation.NonNull; 9 | import android.support.annotation.Nullable; 10 | import android.support.design.widget.TextInputEditText; 11 | import android.support.design.widget.TextInputLayout; 12 | import android.support.v4.app.DialogFragment; 13 | import android.support.v4.app.Fragment; 14 | import android.util.DisplayMetrics; 15 | import android.view.LayoutInflater; 16 | import android.view.View; 17 | import android.view.ViewGroup; 18 | import android.view.Window; 19 | import android.widget.TextView; 20 | 21 | import com.cleveroad.sample.R; 22 | 23 | import java.util.Objects; 24 | 25 | import static com.cleveroad.sample.datasource.Constants.EXTRA_COLUMN_NUMBER; 26 | import static com.cleveroad.sample.datasource.Constants.EXTRA_ROW_NUMBER; 27 | import static com.cleveroad.sample.datasource.Constants.EXTRA_TITLE; 28 | import static com.cleveroad.sample.datasource.Constants.EXTRA_VALUE; 29 | import static com.cleveroad.sample.datasource.Constants.REQUEST_CODE_ADD_COLUMN; 30 | import static com.cleveroad.sample.datasource.Constants.REQUEST_CODE_ADD_ROW; 31 | import static com.cleveroad.sample.datasource.Constants.REQUEST_CODE_DELETE_COLUMN; 32 | import static com.cleveroad.sample.datasource.Constants.REQUEST_CODE_DELETE_ROW; 33 | import static com.cleveroad.sample.datasource.Constants.REQUEST_CODE_EDIT_SONG; 34 | 35 | public class EditItemDialog extends DialogFragment implements View.OnClickListener { 36 | private static final int ZERO_COLUMN_OR_ROW = 0; 37 | private String mTitle; 38 | private String mValue; 39 | private int mColumn; 40 | private int mRow; 41 | 42 | private TextInputLayout mTilValue; 43 | private TextInputEditText mEtValue; 44 | private TextView mTvDelete; 45 | private TextView mTvAdd; 46 | 47 | public static EditItemDialog newInstance(int row, int column, String title, String value) { 48 | EditItemDialog dialog = new EditItemDialog(); 49 | Bundle args = new Bundle(); 50 | args.putInt(EXTRA_COLUMN_NUMBER, column); 51 | args.putInt(EXTRA_ROW_NUMBER, row); 52 | args.putString(EXTRA_TITLE, title); 53 | args.putString(EXTRA_VALUE, value); 54 | dialog.setArguments(args); 55 | return dialog; 56 | } 57 | 58 | @Override 59 | public void onCreate(@Nullable Bundle savedInstanceState) { 60 | super.onCreate(savedInstanceState); 61 | Bundle args = getArguments(); 62 | if (args != null) { 63 | mColumn = args.getInt(EXTRA_COLUMN_NUMBER); 64 | mRow = args.getInt(EXTRA_ROW_NUMBER); 65 | mTitle = args.getString(EXTRA_TITLE); 66 | mValue = args.getString(EXTRA_VALUE); 67 | } 68 | } 69 | 70 | @Nullable 71 | @Override 72 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, 73 | @Nullable Bundle savedInstanceState) { 74 | //noinspection ConstantConditions 75 | getDialog().getWindow().setBackgroundDrawable(new ColorDrawable(android.graphics.Color.TRANSPARENT)); 76 | View view = inflater.inflate(R.layout.dialog_edit_item, container, false); 77 | 78 | mTilValue = view.findViewById(R.id.tilValue); 79 | mEtValue = view.findViewById(R.id.etValue); 80 | mTvDelete = view.findViewById(R.id.tvDelete); 81 | mTvAdd = view.findViewById(R.id.tvAdd); 82 | 83 | mTvDelete.setOnClickListener(this); 84 | mTvAdd.setOnClickListener(this); 85 | 86 | view.findViewById(R.id.bPositive).setOnClickListener(this); 87 | view.findViewById(R.id.bNegative).setOnClickListener(this); 88 | 89 | return view; 90 | } 91 | 92 | @Override 93 | public void onResume() { 94 | super.onResume(); 95 | Window window = getDialog().getWindow(); 96 | if (window != null) { 97 | DisplayMetrics dm = new DisplayMetrics(); 98 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { 99 | Objects.requireNonNull(getActivity()).getWindowManager().getDefaultDisplay().getMetrics(dm); 100 | } 101 | int height = dm.heightPixels; 102 | int width = dm.widthPixels; 103 | window.setLayout(width, height); 104 | window.setLayout((int) (width * 0.8), (int) (height * 0.9)); 105 | } 106 | } 107 | 108 | @Override 109 | public void onStart() { 110 | super.onStart(); 111 | updateUiAccordingToModel(); 112 | } 113 | 114 | @Override 115 | public void onClick(View v) { 116 | switch (v.getId()) { 117 | case R.id.bPositive: 118 | sendResult(); 119 | break; 120 | case R.id.bNegative: 121 | Fragment fragment = getParentFragment(); 122 | if (fragment != null) { 123 | fragment.onActivityResult(REQUEST_CODE_EDIT_SONG, Activity.RESULT_CANCELED, null); 124 | } 125 | break; 126 | case R.id.tvDelete: 127 | delete(); 128 | break; 129 | case R.id.tvAdd: 130 | add(); 131 | break; 132 | default: 133 | //do nothing 134 | } 135 | 136 | dismiss(); 137 | } 138 | 139 | private void updateUiAccordingToModel() { 140 | mTilValue.setHint(mTitle); 141 | mEtValue.setText(mValue); 142 | 143 | if (mColumn == ZERO_COLUMN_OR_ROW) { 144 | mTvDelete.setText(getString(R.string.delete_row)); 145 | mTvAdd.setText(getString(R.string.add_row)); 146 | } else if (mRow == ZERO_COLUMN_OR_ROW) { 147 | mTvDelete.setText(getString(R.string.delete_column)); 148 | mTvAdd.setText(getString(R.string.add_column)); 149 | } else { 150 | mTvDelete.setVisibility(View.GONE); 151 | mTvAdd.setVisibility(View.GONE); 152 | } 153 | } 154 | 155 | private void delete() { 156 | if (mColumn == ZERO_COLUMN_OR_ROW) { 157 | sendResult(EXTRA_ROW_NUMBER, mRow, REQUEST_CODE_DELETE_ROW); 158 | } else if (mRow == ZERO_COLUMN_OR_ROW) { 159 | sendResult(EXTRA_COLUMN_NUMBER, mColumn, REQUEST_CODE_DELETE_COLUMN); 160 | } 161 | } 162 | 163 | private void add() { 164 | if (mColumn == ZERO_COLUMN_OR_ROW) { 165 | sendResult(EXTRA_ROW_NUMBER, mRow, REQUEST_CODE_ADD_ROW); 166 | } else if (mRow == ZERO_COLUMN_OR_ROW) { 167 | sendResult(EXTRA_COLUMN_NUMBER, mColumn, REQUEST_CODE_ADD_COLUMN); 168 | } 169 | } 170 | 171 | private void sendResult() { 172 | Fragment fragment = getParentFragment(); 173 | if (fragment != null) { 174 | Intent intent = new Intent(); 175 | String str = mEtValue.getText().toString().trim(); 176 | intent.putExtra(EXTRA_VALUE, str.isEmpty() ? " " : str); 177 | intent.putExtra(EXTRA_COLUMN_NUMBER, mColumn); 178 | intent.putExtra(EXTRA_ROW_NUMBER, mRow); 179 | fragment.onActivityResult(REQUEST_CODE_EDIT_SONG, Activity.RESULT_OK, intent); 180 | } 181 | } 182 | 183 | private void sendResult(String extra, int data, int requestCode) { 184 | Fragment fragment = getParentFragment(); 185 | if (fragment != null) { 186 | Intent intent = new Intent(); 187 | intent.putExtra(extra, data); 188 | fragment.onActivityResult(requestCode, Activity.RESULT_OK, intent); 189 | } 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /sample/src/main/java/com/cleveroad/sample/ui/dialogs/SettingsDialog.java: -------------------------------------------------------------------------------- 1 | package com.cleveroad.sample.ui.dialogs; 2 | 3 | import android.app.Activity; 4 | import android.content.Intent; 5 | import android.graphics.drawable.ColorDrawable; 6 | import android.os.Build; 7 | import android.os.Bundle; 8 | import android.support.annotation.NonNull; 9 | import android.support.annotation.Nullable; 10 | import android.support.v4.app.DialogFragment; 11 | import android.support.v4.app.Fragment; 12 | import android.support.v7.widget.SwitchCompat; 13 | import android.util.DisplayMetrics; 14 | import android.view.LayoutInflater; 15 | import android.view.View; 16 | import android.view.ViewGroup; 17 | import android.view.Window; 18 | import android.widget.CompoundButton; 19 | 20 | import com.cleveroad.sample.R; 21 | 22 | import java.util.Objects; 23 | 24 | import static com.cleveroad.sample.datasource.Constants.REQUEST_CODE_SETTINGS; 25 | 26 | public class SettingsDialog extends DialogFragment implements View.OnClickListener { 27 | 28 | public static final String EXTRA_VALUE_SOLID_HEADER = "EXTRA_VALUE_SOLID_HEADER"; 29 | public static final String EXTRA_VALUE_HEADER_FIXED = "EXTRA_VALUE_HEADER_FIXED"; 30 | public static final String EXTRA_VALUE_RTL_DIRECTION = "EXTRA_VALUE_RTL_DIRECTION"; 31 | public static final String EXTRA_VALUE_DRAG_AND_DROP_ENABLED = "EXTRA_VALUE_DRAG_AND_DROP_ENABLED"; 32 | 33 | /** 34 | * if true - value of row header fixed to the row. Fixed to the data 35 | * if false - fixed to the number of row. Fixed to the row' number from 0 to n. 36 | */ 37 | private boolean mSolidRowHeader; 38 | 39 | private boolean mIsHeaderFixed; 40 | 41 | private boolean mIsRtlDirection; 42 | 43 | private boolean mIsDragAndDropEnabled; 44 | 45 | private SwitchCompat swSolidRow; 46 | 47 | private SwitchCompat swFixedHeaders; 48 | 49 | private SwitchCompat swRtlDirection; 50 | 51 | private SwitchCompat swDragAndDropEnabled; 52 | 53 | 54 | public static SettingsDialog newInstance(boolean isHeaderFixed, boolean solidRowHeader, 55 | boolean isRtlDirection, boolean isDragAndDropEnabled) { 56 | SettingsDialog dialog = new SettingsDialog(); 57 | Bundle args = new Bundle(); 58 | args.putBoolean(EXTRA_VALUE_HEADER_FIXED, isHeaderFixed); 59 | args.putBoolean(EXTRA_VALUE_SOLID_HEADER, solidRowHeader); 60 | args.putBoolean(EXTRA_VALUE_RTL_DIRECTION, isRtlDirection); 61 | args.putBoolean(EXTRA_VALUE_DRAG_AND_DROP_ENABLED, isDragAndDropEnabled); 62 | dialog.setArguments(args); 63 | return dialog; 64 | } 65 | 66 | public static SettingsDialog newInstance(boolean isHeaderFixed, boolean solidRowHeader, boolean isDragAndDropEnabled) { 67 | SettingsDialog dialog = new SettingsDialog(); 68 | Bundle args = new Bundle(); 69 | args.putBoolean(EXTRA_VALUE_HEADER_FIXED, isHeaderFixed); 70 | args.putBoolean(EXTRA_VALUE_SOLID_HEADER, solidRowHeader); 71 | args.putBoolean(EXTRA_VALUE_DRAG_AND_DROP_ENABLED, isDragAndDropEnabled); 72 | dialog.setArguments(args); 73 | return dialog; 74 | } 75 | 76 | @Override 77 | public void onCreate(@Nullable Bundle savedInstanceState) { 78 | super.onCreate(savedInstanceState); 79 | Bundle args = getArguments(); 80 | if (args != null) { 81 | mSolidRowHeader = args.getBoolean(EXTRA_VALUE_SOLID_HEADER); 82 | mIsHeaderFixed = args.getBoolean(EXTRA_VALUE_HEADER_FIXED); 83 | mIsRtlDirection = args.getBoolean(EXTRA_VALUE_RTL_DIRECTION); 84 | mIsDragAndDropEnabled = args.getBoolean(EXTRA_VALUE_DRAG_AND_DROP_ENABLED); 85 | } 86 | } 87 | 88 | @Nullable 89 | @Override 90 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, 91 | @Nullable Bundle savedInstanceState) { 92 | //noinspection ConstantConditions 93 | getDialog().getWindow().setBackgroundDrawable(new ColorDrawable(android.graphics.Color.TRANSPARENT)); 94 | View view = inflater.inflate(R.layout.dialog_settings, container, false); 95 | swSolidRow = view.findViewById(R.id.swSolidRow); 96 | swFixedHeaders = view.findViewById(R.id.swFixedHeaders); 97 | swRtlDirection = view.findViewById(R.id.swRtlDirection); 98 | swDragAndDropEnabled = view.findViewById(R.id.swDragAndDropEnabled); 99 | 100 | return view; 101 | } 102 | 103 | @Override 104 | public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { 105 | super.onViewCreated(view, savedInstanceState); 106 | 107 | view.findViewById(R.id.bPositive).setOnClickListener(this); 108 | view.findViewById(R.id.bNegative).setOnClickListener(this); 109 | 110 | swFixedHeaders.setChecked(mIsHeaderFixed); 111 | swSolidRow.setChecked(mSolidRowHeader); 112 | swRtlDirection.setChecked(mIsRtlDirection); 113 | swDragAndDropEnabled.setChecked(mIsDragAndDropEnabled); 114 | 115 | swFixedHeaders.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { 116 | @Override 117 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 118 | mIsHeaderFixed = isChecked; 119 | } 120 | }); 121 | swSolidRow.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { 122 | @Override 123 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 124 | mSolidRowHeader = isChecked; 125 | } 126 | }); 127 | swRtlDirection.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { 128 | @Override 129 | public void onCheckedChanged(CompoundButton compoundButton, boolean isChecked) { 130 | mIsRtlDirection = isChecked; 131 | } 132 | }); 133 | swDragAndDropEnabled.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { 134 | @Override 135 | public void onCheckedChanged(CompoundButton compoundButton, boolean isChecked) { 136 | mIsDragAndDropEnabled = isChecked; 137 | } 138 | }); 139 | 140 | } 141 | 142 | @Override 143 | public void onResume() { 144 | super.onResume(); 145 | Window window = getDialog().getWindow(); 146 | if (window != null) { 147 | DisplayMetrics dm = new DisplayMetrics(); 148 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { 149 | Objects.requireNonNull(getActivity()).getWindowManager().getDefaultDisplay().getMetrics(dm); 150 | } 151 | int height = dm.heightPixels; 152 | int width = dm.widthPixels; 153 | window.setLayout(width, height); 154 | window.setLayout((int) (width * 0.8), (int) (height * 0.9)); 155 | } 156 | } 157 | 158 | @Override 159 | public void onClick(View v) { 160 | switch (v.getId()) { 161 | case R.id.bPositive: 162 | sendResult(); 163 | break; 164 | case R.id.bNegative: 165 | Fragment fragment = getParentFragment(); 166 | if (fragment != null) { 167 | fragment.onActivityResult(REQUEST_CODE_SETTINGS, Activity.RESULT_CANCELED, null); 168 | } 169 | break; 170 | default: 171 | //do nothing 172 | } 173 | dismiss(); 174 | } 175 | 176 | private void sendResult() { 177 | Fragment fragment = getParentFragment(); 178 | if (fragment != null) { 179 | Intent intent = new Intent(); 180 | intent.putExtra(EXTRA_VALUE_SOLID_HEADER, mSolidRowHeader); 181 | intent.putExtra(EXTRA_VALUE_HEADER_FIXED, mIsHeaderFixed); 182 | intent.putExtra(EXTRA_VALUE_RTL_DIRECTION, mIsRtlDirection); 183 | intent.putExtra(EXTRA_VALUE_DRAG_AND_DROP_ENABLED, mIsDragAndDropEnabled); 184 | fragment.onActivityResult(REQUEST_CODE_SETTINGS, Activity.RESULT_OK, intent); 185 | } 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /sample/src/main/java/com/cleveroad/sample/utils/ClosableUtil.java: -------------------------------------------------------------------------------- 1 | package com.cleveroad.sample.utils; 2 | 3 | import android.util.Log; 4 | 5 | import java.io.Closeable; 6 | 7 | public class ClosableUtil { 8 | private static final String TAG = ClosableUtil.class.getSimpleName(); 9 | 10 | private ClosableUtil() { 11 | 12 | } 13 | 14 | public static void closeWithoutException(Closeable closeable) { 15 | try { 16 | if (closeable != null) { 17 | closeable.close(); 18 | } 19 | } catch (Exception e) { 20 | Log.e(TAG, e.getMessage()); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /sample/src/main/java/com/cleveroad/sample/utils/CsvUtils.java: -------------------------------------------------------------------------------- 1 | package com.cleveroad.sample.utils; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | // http://www.mkyong.com/java/how-to-read-and-parse-csv-file-in-java/ 7 | public class CsvUtils { 8 | private static final char DEFAULT_SEPARATOR = ','; 9 | private static final char DEFAULT_QUOTE = '"'; 10 | private CsvUtils() { 11 | 12 | } 13 | 14 | public static List parseLine(String cvsLine) { 15 | return parseLine(cvsLine, DEFAULT_SEPARATOR, DEFAULT_QUOTE); 16 | } 17 | 18 | public static List parseLine(String cvsLine, char separators) { 19 | return parseLine(cvsLine, separators, DEFAULT_QUOTE); 20 | } 21 | 22 | public static List parseLine(String cvsLine, char separators, char customQuote) { 23 | 24 | List result = new ArrayList<>(); 25 | 26 | //if empty, return! 27 | if (cvsLine == null || cvsLine.isEmpty()) { 28 | return result; 29 | } 30 | 31 | if (customQuote == ' ') { 32 | customQuote = DEFAULT_QUOTE; 33 | } 34 | 35 | if (separators == ' ') { 36 | separators = DEFAULT_SEPARATOR; 37 | } 38 | 39 | StringBuffer curVal = new StringBuffer(); 40 | boolean inQuotes = false; 41 | boolean startCollectChar = false; 42 | boolean doubleQuotesInColumn = false; 43 | 44 | char[] chars = cvsLine.toCharArray(); 45 | 46 | for (char ch : chars) { 47 | 48 | if (inQuotes) { 49 | startCollectChar = true; 50 | if (ch == customQuote) { 51 | inQuotes = false; 52 | doubleQuotesInColumn = false; 53 | } else { 54 | 55 | //Fixed : allow "" in custom quote enclosed 56 | if (ch == '\"') { 57 | if (!doubleQuotesInColumn) { 58 | curVal.append(ch); 59 | doubleQuotesInColumn = true; 60 | } 61 | } else { 62 | curVal.append(ch); 63 | } 64 | 65 | } 66 | } else { 67 | if (ch == customQuote) { 68 | 69 | inQuotes = true; 70 | 71 | //Fixed : allow "" in empty quote enclosed 72 | if (chars[0] != '"' && customQuote == '\"') { 73 | // curVal.append('"'); 74 | } 75 | 76 | //double quotes in column will hit this! 77 | if (startCollectChar) { 78 | curVal.append('"'); 79 | } 80 | 81 | } else if (ch == separators) { 82 | 83 | result.add(curVal.toString()); 84 | 85 | curVal = new StringBuffer(); 86 | startCollectChar = false; 87 | 88 | } else if (ch == '\r') { 89 | //ignore LF characters 90 | continue; 91 | } else if (ch == '\n') { 92 | //the end, break! 93 | break; 94 | } else { 95 | curVal.append(ch); 96 | } 97 | } 98 | 99 | } 100 | 101 | result.add(curVal.toString()); 102 | 103 | return result; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /sample/src/main/java/com/cleveroad/sample/utils/FileUtils.java: -------------------------------------------------------------------------------- 1 | package com.cleveroad.sample.utils; 2 | 3 | import android.content.Context; 4 | 5 | import java.io.File; 6 | import java.io.FileInputStream; 7 | import java.io.FileOutputStream; 8 | import java.io.IOException; 9 | import java.io.InputStream; 10 | import java.io.OutputStream; 11 | import java.text.SimpleDateFormat; 12 | import java.util.Date; 13 | import java.util.Locale; 14 | 15 | public class FileUtils { 16 | private static final String PREFIX = "TEMP_"; 17 | private static final String EXTENSION = ".csv"; 18 | 19 | public static void copy(File src, File dst) throws IOException { 20 | InputStream in = new FileInputStream(src); 21 | OutputStream out = new FileOutputStream(dst); 22 | 23 | // Transfer bytes from in to out 24 | byte[] buf = new byte[1024]; 25 | int len; 26 | while ((len = in.read(buf)) > 0) { 27 | out.write(buf, 0, len); 28 | } 29 | in.close(); 30 | out.close(); 31 | } 32 | 33 | public static void copy(InputStream in, File dst) throws IOException { 34 | OutputStream out = new FileOutputStream(dst); 35 | 36 | // Transfer bytes from in to out 37 | byte[] buf = new byte[1024]; 38 | int len; 39 | while ((len = in.read(buf)) > 0) { 40 | out.write(buf, 0, len); 41 | } 42 | out.close(); 43 | } 44 | 45 | 46 | /** 47 | * Create temp file file. 48 | * 49 | * @param context the context 50 | * @return the file 51 | */ 52 | @SuppressWarnings("ResultOfMethodCallIgnored, unused") 53 | public static File createTempFile(Context context) { 54 | String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmssSSS", Locale.US).format(new Date()); 55 | String tempFileName = PREFIX + timeStamp + "_"; 56 | File cacheDir = context.getCacheDir(); 57 | if (!cacheDir.exists()) { 58 | cacheDir.mkdir(); 59 | } 60 | return new File(cacheDir, tempFileName + EXTENSION); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /sample/src/main/java/com/cleveroad/sample/utils/PermissionHelper.java: -------------------------------------------------------------------------------- 1 | package com.cleveroad.sample.utils; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.content.pm.PackageManager; 6 | import android.support.annotation.NonNull; 7 | import android.support.v4.app.ActivityCompat; 8 | import android.support.v4.app.Fragment; 9 | import android.support.v4.content.ContextCompat; 10 | 11 | public class PermissionHelper { 12 | private PermissionHelper() { 13 | 14 | } 15 | 16 | /** 17 | * @return true if all permissions is granted 18 | */ 19 | public static boolean check(@NonNull Context context, String... permissions) { 20 | boolean result = true; 21 | for (String permission : permissions) { 22 | result = ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED; 23 | if (!result) { 24 | break; 25 | } 26 | } 27 | return result; 28 | } 29 | 30 | /** 31 | * @return true if all permissions is granted 32 | */ 33 | public static boolean checkOrRequest(@NonNull Activity activity, int requestCode, String... permissions) { 34 | if (!check(activity, permissions)) { 35 | ActivityCompat.requestPermissions(activity, permissions, requestCode); 36 | return false; 37 | } else return true; 38 | } 39 | 40 | /** 41 | * @return true if all permissions is granted 42 | */ 43 | public static boolean checkOrRequest(@NonNull Fragment fragment, int requestCode, String... permissions) { 44 | if (!check(fragment.getContext(), permissions)) { 45 | fragment.requestPermissions(permissions, requestCode); 46 | return false; 47 | } else return true; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /sample/src/main/java/com/cleveroad/sample/utils/StringUtils.java: -------------------------------------------------------------------------------- 1 | package com.cleveroad.sample.utils; 2 | 3 | public class StringUtils { 4 | private StringUtils() { 5 | 6 | } 7 | 8 | public static String toString(Iterable iterable, String separator) { 9 | StringBuilder stringBuilder = new StringBuilder(); 10 | 11 | for (Object item : iterable) { 12 | stringBuilder.append(String.valueOf(item)); 13 | stringBuilder.append(separator); 14 | } 15 | 16 | if (stringBuilder.length() > 2) { 17 | return stringBuilder.substring(0, stringBuilder.length() - separator.length()); 18 | } else return stringBuilder.toString(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /sample/src/main/java/com/cleveroad/sample/utils/ValueInterpolator.java: -------------------------------------------------------------------------------- 1 | package com.cleveroad.sample.utils; 2 | 3 | public class ValueInterpolator { 4 | private float mRangeMapFromMin; 5 | private float mRangeMapToMin; 6 | 7 | private float mScaleFactor; 8 | 9 | public ValueInterpolator(float[] rangeMapFrom, float[] rangeMapTo) { 10 | this(rangeMapFrom[0], rangeMapFrom[1], rangeMapTo[0], rangeMapTo[1]); 11 | } 12 | 13 | public ValueInterpolator(float rangeMapFromMin, float rangeMapFromMax, float rangeMapToMin, float rangeMapToMax) { 14 | mRangeMapFromMin = rangeMapFromMin; 15 | mRangeMapToMin = rangeMapToMin; 16 | 17 | float rangeMapFromSpan = rangeMapFromMax - rangeMapFromMin; 18 | float rangeMapToSpan = rangeMapToMax - rangeMapToMin; 19 | 20 | mScaleFactor = rangeMapToSpan / rangeMapFromSpan; 21 | } 22 | 23 | public float map(float value) { 24 | return mRangeMapToMin + ((value - mRangeMapFromMin) * mScaleFactor); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable-hdpi/bg_csv_file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cleveroad/AdaptiveTableLayout/188213b35e05e635497b03fe741799782dde7208/sample/src/main/res/drawable-hdpi/bg_csv_file.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-hdpi/ic_csv_file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cleveroad/AdaptiveTableLayout/188213b35e05e635497b03fe741799782dde7208/sample/src/main/res/drawable-hdpi/ic_csv_file.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-hdpi/ic_csv_new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cleveroad/AdaptiveTableLayout/188213b35e05e635497b03fe741799782dde7208/sample/src/main/res/drawable-hdpi/ic_csv_new.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cleveroad/AdaptiveTableLayout/188213b35e05e635497b03fe741799782dde7208/sample/src/main/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-hdpi/icon_csv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cleveroad/AdaptiveTableLayout/188213b35e05e635497b03fe741799782dde7208/sample/src/main/res/drawable-hdpi/icon_csv.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-ldpi/icon_csv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cleveroad/AdaptiveTableLayout/188213b35e05e635497b03fe741799782dde7208/sample/src/main/res/drawable-ldpi/icon_csv.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-mdpi/bg_csv_file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cleveroad/AdaptiveTableLayout/188213b35e05e635497b03fe741799782dde7208/sample/src/main/res/drawable-mdpi/bg_csv_file.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-mdpi/ic_csv_file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cleveroad/AdaptiveTableLayout/188213b35e05e635497b03fe741799782dde7208/sample/src/main/res/drawable-mdpi/ic_csv_file.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-mdpi/ic_csv_new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cleveroad/AdaptiveTableLayout/188213b35e05e635497b03fe741799782dde7208/sample/src/main/res/drawable-mdpi/ic_csv_new.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cleveroad/AdaptiveTableLayout/188213b35e05e635497b03fe741799782dde7208/sample/src/main/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-mdpi/icon_csv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cleveroad/AdaptiveTableLayout/188213b35e05e635497b03fe741799782dde7208/sample/src/main/res/drawable-mdpi/icon_csv.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xhdpi/bg_csv_file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cleveroad/AdaptiveTableLayout/188213b35e05e635497b03fe741799782dde7208/sample/src/main/res/drawable-xhdpi/bg_csv_file.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xhdpi/ic_csv_file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cleveroad/AdaptiveTableLayout/188213b35e05e635497b03fe741799782dde7208/sample/src/main/res/drawable-xhdpi/ic_csv_file.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xhdpi/ic_csv_new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cleveroad/AdaptiveTableLayout/188213b35e05e635497b03fe741799782dde7208/sample/src/main/res/drawable-xhdpi/ic_csv_new.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cleveroad/AdaptiveTableLayout/188213b35e05e635497b03fe741799782dde7208/sample/src/main/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xhdpi/icon_csv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cleveroad/AdaptiveTableLayout/188213b35e05e635497b03fe741799782dde7208/sample/src/main/res/drawable-xhdpi/icon_csv.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxhdpi/bg_csv_file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cleveroad/AdaptiveTableLayout/188213b35e05e635497b03fe741799782dde7208/sample/src/main/res/drawable-xxhdpi/bg_csv_file.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxhdpi/ic_csv_file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cleveroad/AdaptiveTableLayout/188213b35e05e635497b03fe741799782dde7208/sample/src/main/res/drawable-xxhdpi/ic_csv_file.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxhdpi/ic_csv_new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cleveroad/AdaptiveTableLayout/188213b35e05e635497b03fe741799782dde7208/sample/src/main/res/drawable-xxhdpi/ic_csv_new.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxhdpi/icon_csv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cleveroad/AdaptiveTableLayout/188213b35e05e635497b03fe741799782dde7208/sample/src/main/res/drawable-xxhdpi/icon_csv.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxxhdpi/bg_csv_file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cleveroad/AdaptiveTableLayout/188213b35e05e635497b03fe741799782dde7208/sample/src/main/res/drawable-xxxhdpi/bg_csv_file.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxxhdpi/ic_csv_file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cleveroad/AdaptiveTableLayout/188213b35e05e635497b03fe741799782dde7208/sample/src/main/res/drawable-xxxhdpi/ic_csv_file.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxxhdpi/ic_csv_new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cleveroad/AdaptiveTableLayout/188213b35e05e635497b03fe741799782dde7208/sample/src/main/res/drawable-xxxhdpi/ic_csv_new.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxxhdpi/icon_csv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cleveroad/AdaptiveTableLayout/188213b35e05e635497b03fe741799782dde7208/sample/src/main/res/drawable-xxxhdpi/icon_csv.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable/ic_arrow_back_white_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable/ic_down_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable/ic_up_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable/separator_table_first.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable/separator_table_header.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable/separator_table_header_first.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable/shadow.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/activity_sample.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/dialog_add_column.xml: -------------------------------------------------------------------------------- 1 | 2 | 15 | 16 | 19 | 20 | 28 | 29 | 38 | 39 | 47 | 48 | 55 | 56 | 63 | 64 | 65 | 75 | 76 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/dialog_add_row.xml: -------------------------------------------------------------------------------- 1 | 2 | 15 | 16 | 19 | 20 | 28 | 29 | 38 | 39 | 47 | 48 | 55 | 56 | 63 | 64 | 65 | 75 | 76 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/dialog_delete.xml: -------------------------------------------------------------------------------- 1 | 2 | 15 | 16 | 19 | 20 | 27 | 28 | 38 | 39 | 47 | 48 | 55 | 56 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/dialog_edit_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 15 | 16 | 20 | 21 | 29 | 30 | 37 | 38 | 41 | 42 | 47 | 48 | 54 | 55 | 56 | 57 | 66 | 67 | 77 | 78 | 86 | 87 | 94 | 95 | 96 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/dialog_edit_row.xml: -------------------------------------------------------------------------------- 1 | 2 | 15 | 16 | 19 | 20 | 28 | 29 | 35 | 36 | 41 | 42 | 43 | 44 | 50 | 51 | 58 | 59 | 60 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/dialog_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 15 | 16 | 20 | 21 | 29 | 30 | 37 | 38 | 39 | 42 | 43 | 51 | 52 | 61 | 62 | 71 | 72 | 81 | 82 | 90 | 91 | 98 | 99 | 100 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/fragment_csv_picker.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 15 | 16 | 17 | 24 | 25 | 31 | 32 | 44 | 45 | 53 | 54 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/fragment_tab_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 15 | 16 | 25 | 26 | 32 | 33 | 40 | 41 | 47 | 48 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/item_body.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 15 | 16 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/item_card.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 21 | 22 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/item_header_column.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 14 | 15 | 20 | 21 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/item_header_left_top.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/item_header_row.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 17 | 18 | -------------------------------------------------------------------------------- /sample/src/main/res/menu/table_layout.xml: -------------------------------------------------------------------------------- 1 | 2 |

4 | 5 | 9 | 10 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cleveroad/AdaptiveTableLayout/188213b35e05e635497b03fe741799782dde7208/sample/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cleveroad/AdaptiveTableLayout/188213b35e05e635497b03fe741799782dde7208/sample/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cleveroad/AdaptiveTableLayout/188213b35e05e635497b03fe741799782dde7208/sample/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cleveroad/AdaptiveTableLayout/188213b35e05e635497b03fe741799782dde7208/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cleveroad/AdaptiveTableLayout/188213b35e05e635497b03fe741799782dde7208/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FF394249 4 | #FF273238 5 | #FF394249 6 | #ffffffff 7 | #ffffffff 8 | 9 | 10 | 11 | #FF394249 12 | #FF273238 13 | 14 | 15 | -------------------------------------------------------------------------------- /sample/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 120dp 5 | 120dp 6 | 60dp 7 | 80dp 8 | 9 | 10 | -------------------------------------------------------------------------------- /sample/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | CSV Editor 4 | 5 | Add CSV table from your phone memory and you can read and edit it. 6 | Open any CSV or you can use this DEMO file 7 | Pick CSV-file 8 | Demo 9 | 10 | Edit song 11 | Settings 12 | Fixed headers 13 | Rtl direction 14 | Drag And Drop enabled 15 | Solid row 16 | Position 17 | Photo url 18 | Artist name 19 | Album name 20 | Song 21 | Genres 22 | Time 23 | WLS on chart 24 | Votes count 25 | 26 | Rock 27 | R&B 28 | Pop 29 | Jazz 30 | Hip hop 31 | Electronic 32 | Blues 33 | Country 34 | 35 | 36 | OK 37 | Cancel 38 | 39 | Artists 40 | Save 41 | 42 | All changes have been saved 43 | Unexpected error 44 | No such csv file 45 | Not csv file 46 | Delete row 47 | Add row 48 | Add row above 49 | Add row below 50 | 51 | This may take some time. Do you want to delete? 52 | Add column 53 | Delete column 54 | Add a column to the left 55 | Add a column to the right 56 | This may take some time. 57 | 58 | -------------------------------------------------------------------------------- /sample/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 14 | 15 | 19 | 20 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':sample', ':library' 2 | --------------------------------------------------------------------------------