├── .gitignore ├── .travis.yml ├── README.md ├── build.gradle ├── drawable-bak ├── chips_edittext_gb.xml ├── default_bubble_background.xml ├── default_bubble_background_pressed.xml ├── greentext_background.xml ├── greentext_background_active.xml ├── greentext_background_pressed.xml ├── greybubble_background.xml ├── greybubble_background_edit.xml ├── greybubble_background_pressed.xml ├── greybubble_background_states.xml ├── greybubble_edit.xml ├── greytext_background.xml ├── greytext_background_active.xml ├── greytext_background_pressed.xml ├── lilatext_background.xml ├── lilatext_background_active.xml ├── lilatext_background_active_transparent.xml ├── lilatext_background_pressed.xml ├── lilatext_background_transparent.xml ├── pinktext_background.xml ├── pinktext_background_active.xml └── pinktext_background_pressed.xml ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── library ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ ├── asolutions │ │ └── widget │ │ │ └── RowLayout.java │ │ └── eyeem │ │ └── chips │ │ ├── AwesomeBubble.java │ │ ├── BubbleSpan.java │ │ ├── BubbleSpanImpl.java │ │ ├── BubbleStyle.java │ │ ├── ChipsEditText.java │ │ ├── ChipsTextView.java │ │ ├── CursorDrawable.java │ │ ├── DefaultBubbles.java │ │ ├── FontCache.java │ │ ├── ILayoutCallback.java │ │ ├── LayoutBuild.java │ │ ├── Linkify.java │ │ ├── MultilineEditText.java │ │ ├── Regex.java │ │ ├── TapableSpan.java │ │ └── Utils.java │ └── res │ └── values │ ├── attrs.xml │ ├── colors.xml │ ├── dimens.xml │ └── strings.xml ├── notes ├── build.gradle ├── settings.gradle └── src │ └── main │ ├── AndroidManifest.xml │ ├── assets │ ├── notes.json │ ├── sailec_bold.ttf │ └── sailec_light.otf │ ├── java │ └── com │ │ └── eyeem │ │ └── notes │ │ ├── App.java │ │ ├── MainActivity.java │ │ ├── adapter │ │ ├── NoteHolder.java │ │ ├── NotesAdapter.java │ │ └── RecyclerAdapter.java │ │ ├── core │ │ ├── AppDep.java │ │ ├── AppModule.java │ │ ├── BootstrapNotesModule.java │ │ ├── NoteStorage.java │ │ ├── PausableExecutorModule.java │ │ └── StorageDep.java │ │ ├── event │ │ ├── BubbleClickedEvent.java │ │ ├── NewNoteEvent.java │ │ ├── NoteClickedEvent.java │ │ ├── PreviewRefresh.java │ │ └── TextSnapshotCaptured.java │ │ ├── experimental │ │ ├── CacheOnScroll.java │ │ └── PausableThreadPoolExecutor.java │ │ ├── model │ │ └── Note.java │ │ ├── mortarflow │ │ ├── Ass.java │ │ ├── BaseComponent.java │ │ ├── DynamicModules.java │ │ ├── FlowBundler.java │ │ ├── FlowDep.java │ │ ├── FramePathContainerView.java │ │ ├── GsonParceler.java │ │ ├── HandlesBack.java │ │ ├── HandlesUp.java │ │ ├── HasParent.java │ │ ├── Layout.java │ │ ├── MortarContextFactory.java │ │ ├── ScopeSingleton.java │ │ ├── ScreenScoper.java │ │ ├── SimplePathContainer.java │ │ ├── UpAndBack.java │ │ ├── Utils.java │ │ └── WithComponent.java │ │ ├── screen │ │ ├── ActionBarOwner.java │ │ ├── Edit.java │ │ ├── Note.java │ │ ├── Notes.java │ │ ├── Preview.java │ │ └── Start.java │ │ ├── utils │ │ ├── Assets.java │ │ ├── KeyboardDetector.java │ │ ├── LinkifyDeserialiser.java │ │ └── RxBus.java │ │ ├── view │ │ ├── EditView.java │ │ ├── NoteView.java │ │ ├── NotesView.java │ │ ├── PreviewView.java │ │ └── StartView.java │ │ └── widget │ │ ├── AutocompleteHelper.java │ │ ├── AutocompletePopover.java │ │ └── ScreenPagerAdapter.java │ └── res │ ├── anim │ └── fab_anim.xml │ ├── drawable-hdpi │ └── ic_launcher.png │ ├── drawable-mdpi │ ├── australia.png │ ├── canada.png │ ├── france.png │ ├── germany.png │ ├── ic_launcher.png │ ├── india.png │ ├── italy.png │ ├── japan.png │ ├── malaysia.png │ ├── newzealand.png │ ├── philippines.png │ ├── russia.png │ ├── singapore.png │ ├── sweden.png │ ├── unitedkingdom.png │ └── unitedstates.png │ ├── drawable-v21 │ └── button_round.xml │ ├── drawable-xhdpi │ └── ic_launcher.png │ ├── drawable-xxhdpi │ └── ic_launcher.png │ ├── drawable │ ├── button_round.xml │ ├── chips_edittext_gb.xml │ ├── default_bubble_background.xml │ ├── default_bubble_background_pressed.xml │ ├── greentext_background.xml │ ├── greentext_background_active.xml │ ├── greentext_background_pressed.xml │ ├── greybubble_background.xml │ ├── greybubble_background_edit.xml │ ├── greybubble_background_pressed.xml │ ├── greybubble_background_states.xml │ ├── greybubble_edit.xml │ ├── greytext_background.xml │ ├── greytext_background_active.xml │ ├── greytext_background_pressed.xml │ ├── lilatext_background.xml │ ├── lilatext_background_active.xml │ ├── lilatext_background_active_transparent.xml │ ├── lilatext_background_pressed.xml │ ├── lilatext_background_transparent.xml │ ├── pinktext_background.xml │ ├── pinktext_background_active.xml │ ├── pinktext_background_pressed.xml │ ├── round.xml │ ├── round_touch.xml │ └── xml_pressed_state.xml │ ├── layout │ ├── autocomplete_popover.xml │ ├── autocomplete_row.xml │ ├── edit.xml │ ├── note.xml │ ├── note_row.xml │ ├── notes.xml │ ├── preview.xml │ ├── root_layout.xml │ ├── selectlistvenue.xml │ └── start.xml │ ├── menu │ └── main.xml │ ├── values-v19 │ ├── dimens.xml │ └── styles.xml │ ├── values-v21 │ └── styles.xml │ └── values │ ├── bubble_styles.xml │ ├── colors.xml │ ├── dimens.xml │ ├── ids.xml │ ├── strings.xml │ └── styles.xml └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | #Android generated 2 | bin 3 | gen 4 | lint.xml 5 | 6 | #Eclipse 7 | .project 8 | .classpath 9 | .settings 10 | .checkstyle 11 | project.properties 12 | 13 | #IntelliJ IDEA 14 | .idea 15 | *.iml 16 | *.ipr 17 | *.iws 18 | classes 19 | gen-external-apklibs 20 | 21 | #Maven 22 | target 23 | release.properties 24 | pom.xml.* 25 | 26 | #Ant 27 | ant.properties 28 | local.properties 29 | proguard.cfg 30 | proguard-project.txt 31 | 32 | #Other 33 | .DS_Store 34 | tmp 35 | 36 | # Gradle stuff 37 | .gradle 38 | build/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: android 3 | android: 4 | components: 5 | - platform-tools 6 | - android-23 7 | - build-tools-23.0.3 8 | - extra 9 | 10 | jdk: oraclejdk8 11 | 12 | notifications: 13 | email: false 14 | 15 | before_install: 16 | - sudo apt-get update -qq 17 | - if [ `uname -m` = x86_64 ]; then sudo apt-get install -qq --force-yes libgd2-xpm 18 | ia32-libs ia32-libs-multiarch > /dev/null; fi 19 | 20 | - mkdir -p ~/.gradle 21 | - echo "sonatypeRepo=https://oss.sonatype.org/content/repositories/snapshots/" > ~/.gradle/gradle.properties 22 | - echo "sonatypeSnapshotRepo=https://oss.sonatype.org/content/repositories/snapshots/" >> ~/.gradle/gradle.properties 23 | - echo "sonatypeUsername=xxx" >> ~/.gradle/gradle.properties 24 | - echo "sonatypePassword=xxx" >> ~/.gradle/gradle.properties 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/eyeem/chips-android.png)](https://travis-ci.org/eyeem/chips-android) 2 | 3 | Chips library from EyeEm 4 | ================= 5 | 6 | EyeEm style bubbles (a.k.a. chips). 7 | 8 | [![](http://cdn.eyeem.com/thumb/h/400/f88f4ba735e60e5b6faa24b252d1c1b62e375f72-1384269072)] [![](http://cdn.eyeem.com/thumb/h/400/8c660660033aac40d3d099fbc220e993c57ed7eb-1384269111)] 9 | 10 | Usage 11 | ============ 12 | There are two main widgets which you can use: 13 | 14 | - `ChipsEditText` for editable text and bubbles with an optional `AutocompletePopover`. 15 | - `ChipsTextView` if you plan only on displaying non-editable text with bubbles and optionally wish to provide some feedback on bubble press. 16 | 17 | Check out included `example` app to see how to play and what can be done with this library. 18 | 19 | Including in your project 20 | ========================= 21 | 22 | You can either check out the repo manually or grab a snapshot `aar` which is hosted on sonatype repo. To do so, include this in your build.gradle file: 23 | 24 | ``` 25 | dependencies { 26 | 27 | repositories { 28 | maven { 29 | url 'https://oss.sonatype.org/content/repositories/snapshots/' 30 | } 31 | mavenCentral() 32 | mavenLocal() 33 | } 34 | 35 | compile 'com.eyeem.chips:library:0.9.0-SNAPSHOT@aar' 36 | 37 | // ...other dependencies 38 | } 39 | ``` 40 | 41 | Developed By 42 | ============ 43 | 44 | * Lukasz Wisniewski [@vishna](https://twitter.com/vishna) 45 | 46 | Whorthwhile mentions 47 | ============ 48 | - [chips-edittext-library](https://github.com/kpbird/chips-edittext-library) 49 | - [chips from Google](https://android.googlesource.com/platform/frameworks/ex/+/refs/heads/master/chips) 50 | 51 | License 52 | ======= 53 | 54 | Copyright 2013 EyeEm Mobile GmbH 55 | 56 | Licensed under the Apache License, Version 2.0 (the "License"); 57 | you may not use this file except in compliance with the License. 58 | You may obtain a copy of the License at 59 | 60 | http://www.apache.org/licenses/LICENSE-2.0 61 | 62 | Unless required by applicable law or agreed to in writing, software 63 | distributed under the License is distributed on an "AS IS" BASIS, 64 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 65 | See the License for the specific language governing permissions and 66 | limitations under the License. -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | task wrapper(type: Wrapper) { 2 | gradleVersion = '1.10' 3 | } 4 | 5 | buildscript { 6 | repositories { 7 | maven { 8 | url 'https://oss.sonatype.org/content/repositories/snapshots/' 9 | } 10 | mavenCentral() 11 | mavenLocal() 12 | } 13 | dependencies { 14 | classpath 'com.android.tools.build:gradle:2.1.0' 15 | classpath 'com.neenbedankt.gradle.plugins:android-apt:1.4' 16 | } 17 | } -------------------------------------------------------------------------------- /drawable-bak/chips_edittext_gb.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 10 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /drawable-bak/default_bubble_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /drawable-bak/default_bubble_background_pressed.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /drawable-bak/greentext_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 8 | 9 | -------------------------------------------------------------------------------- /drawable-bak/greentext_background_active.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /drawable-bak/greentext_background_pressed.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /drawable-bak/greybubble_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /drawable-bak/greybubble_background_edit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /drawable-bak/greybubble_background_pressed.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /drawable-bak/greybubble_background_states.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 8 | 9 | -------------------------------------------------------------------------------- /drawable-bak/greybubble_edit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /drawable-bak/greytext_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 8 | 9 | -------------------------------------------------------------------------------- /drawable-bak/greytext_background_active.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /drawable-bak/greytext_background_pressed.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /drawable-bak/lilatext_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 8 | 9 | -------------------------------------------------------------------------------- /drawable-bak/lilatext_background_active.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /drawable-bak/lilatext_background_active_transparent.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | -------------------------------------------------------------------------------- /drawable-bak/lilatext_background_pressed.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /drawable-bak/lilatext_background_transparent.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 8 | 9 | -------------------------------------------------------------------------------- /drawable-bak/pinktext_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 8 | 9 | -------------------------------------------------------------------------------- /drawable-bak/pinktext_background_active.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | -------------------------------------------------------------------------------- /drawable-bak/pinktext_background_pressed.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eyeem/chips-android/cd9ad3fdac6c621095570713ca92a33b1bdfa22a/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Aug 10 14:01:15 CEST 2016 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-2.10-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 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /library/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | maven { 4 | url 'https://oss.sonatype.org/content/repositories/snapshots/' 5 | } 6 | mavenCentral() 7 | mavenLocal() 8 | } 9 | dependencies { 10 | classpath 'com.android.tools.build:gradle:2.1.0' 11 | // classpath 'com.squareup.gradle:gradle-android-test-plugin:0.9.1-SNAPSHOT' 12 | } 13 | } 14 | 15 | apply plugin: 'com.android.library' 16 | 17 | dependencies { 18 | repositories { 19 | maven { 20 | url 'https://oss.sonatype.org/content/repositories/snapshots/' 21 | } 22 | mavenCentral() 23 | mavenLocal() 24 | } 25 | 26 | //compile 'io.reactivex:rxandroid:0.24.0' 27 | //testCompile 'junit:junit:4.10' 28 | //testCompile 'org.robolectric:robolectric:2.1.+' 29 | //testCompile 'com.squareup:fest-android:1.0.+' 30 | } 31 | 32 | android { 33 | compileSdkVersion 23 34 | buildToolsVersion '23.0.3' 35 | 36 | lintOptions { 37 | abortOnError false 38 | } 39 | } 40 | 41 | 42 | // SONATYPE DEPLOYMENT 43 | // in order to deploy, run: gradle uploadArchives 44 | 45 | apply plugin: 'maven' 46 | apply plugin: 'signing' 47 | 48 | version = "0.9.11-SNAPSHOT" 49 | group = "com.eyeem.chips" 50 | 51 | configurations { 52 | archives { 53 | extendsFrom configurations.default 54 | } 55 | } 56 | 57 | signing { 58 | required { has("release") && gradle.taskGraph.hasTask("uploadArchives") } 59 | sign configurations.archives 60 | } 61 | 62 | uploadArchives { 63 | configuration = configurations.archives 64 | repositories.mavenDeployer { 65 | beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) } 66 | 67 | repository(url: sonatypeRepo) { 68 | authentication(userName: sonatypeUsername, password: sonatypePassword) 69 | } 70 | 71 | snapshotRepository(url: sonatypeSnapshotRepo) { 72 | authentication(userName: sonatypeUsername, password: sonatypePassword) 73 | } 74 | 75 | pom.project { 76 | name 'Chips' 77 | packaging 'aar' 78 | description 'EyeEm style text baloons library' 79 | url 'https://github.com/eyeem/chips-android' 80 | 81 | scm { 82 | url 'scm:git@github.com:eyeem/chips-android.git' 83 | connection 'scm:git@github.com:eyeem/chips-android.git' 84 | developerConnection 'scm:git@github.com:eyeem/chips-android.git' 85 | } 86 | 87 | licenses { 88 | license { 89 | name 'The Apache Software License, Version 2.0' 90 | url 'http://www.apache.org/licenses/LICENSE-2.0.txt' 91 | distribution 'repo' 92 | } 93 | } 94 | 95 | developers { 96 | developer { 97 | id 'vishna' 98 | name 'Lukasz Wisniewski' 99 | email 'lukasz@eyeem.com' 100 | } 101 | } 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /library/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /library/src/main/java/com/eyeem/chips/AwesomeBubble.java: -------------------------------------------------------------------------------- 1 | package com.eyeem.chips; 2 | 3 | import android.graphics.*; 4 | import android.text.*; 5 | import android.text.style.StyleSpan; 6 | 7 | public class AwesomeBubble { 8 | String text; 9 | private Rect rect; 10 | StaticLayout textLayout; 11 | boolean isPressed; 12 | BubbleStyle style; 13 | TextPaint text_paint; 14 | int containerWidth = 0; 15 | 16 | public AwesomeBubble (String text, int containerWidth, BubbleStyle style, TextPaint text_paint) { 17 | this.style = style; 18 | this.text_paint = text_paint; 19 | this.text = text; 20 | if (containerWidth > 0) { 21 | resetWidth(containerWidth); 22 | } 23 | } 24 | 25 | public AwesomeBubble resetWidth(int containerWidth) { 26 | containerWidth -= DefaultBubbles.long_bubble_workaround; 27 | if (this.containerWidth == containerWidth) 28 | return this; 29 | this.containerWidth = containerWidth; 30 | text_paint.setTextSize(style.textSize); 31 | if (style.typeface != null) { 32 | text_paint.setTypeface(style.typeface); 33 | } 34 | 35 | int correction = 0; 36 | if (android.os.Build.VERSION.SDK_INT >= 18) { 37 | // so with 4.3, StaticLayout.getDesiredWidth started giving bad results 38 | // adding 1px helps 39 | correction = 1; 40 | } 41 | 42 | int maximum_w = containerWidth - 4 * style.bubblePadding; 43 | int desired_w = (int)StaticLayout.getDesiredWidth(text, text_paint) + correction; 44 | int best_w = Math.max(Math.min(maximum_w, desired_w), 0); 45 | textLayout = new StaticLayout(text, text_paint, best_w, Layout.Alignment.ALIGN_CENTER, 1.0f, 1, false); 46 | if (desired_w > maximum_w) { 47 | makeOneLiner(maximum_w); 48 | } 49 | setPosition(0, 0); 50 | return this; 51 | } 52 | 53 | public AwesomeBubble makeOneLiner(int width) { 54 | text_paint.setTextSize(style.textSize); 55 | int i = text_paint.breakText(textLayout.getText().toString(), true, (float)width, null); 56 | while (i > 1 && textLayout.getLineCount() > 1) { 57 | textLayout = new StaticLayout(textLayout.getText().subSequence(0, i - 1) + "\u2026", 58 | text_paint, width, Layout.Alignment.ALIGN_NORMAL, 1.0f, 1, false); 59 | i--; 60 | } 61 | return this; 62 | } 63 | 64 | public int getWidth() { 65 | return textLayout == null ? 0 : textLayout.getWidth() + 4 * style.bubblePadding; 66 | } 67 | 68 | public int getHeight() { 69 | return textLayout == null ? 0 : textLayout.getHeight() + 2 * style.bubblePadding; 70 | } 71 | 72 | public void setPosition(int x, int y) { 73 | rect = new Rect(x, y, x + getWidth(), y + getHeight()); 74 | } 75 | 76 | public void draw(Canvas canvas) { 77 | if (textLayout == null) 78 | return; 79 | if (isPressed && style.pressed != null) { 80 | style.pressed.setBounds(rect); 81 | style.pressed.draw(canvas); 82 | } else if (!isPressed && style.active != null){ 83 | style.active.setBounds(rect); 84 | style.active.draw(canvas); 85 | } 86 | canvas.translate(rect.left + 2 * style.bubblePadding, rect.top + style.bubblePadding); 87 | text_paint.setTextSize(style.textSize); 88 | text_paint.setColor(isPressed ? style.textPressedColor : style.textColor); 89 | text_paint.setAntiAlias(true); 90 | textLayout.draw(canvas); 91 | canvas.translate(-rect.left - 2 * style.bubblePadding, - rect.top - style.bubblePadding); 92 | } 93 | 94 | public void setPressed(boolean value) { 95 | this.isPressed = value; 96 | } 97 | 98 | public String text() { 99 | return text; 100 | } 101 | 102 | public Rect rect() { 103 | return rect; 104 | } 105 | 106 | /** 107 | * distance between baseline and text bottom 108 | * @return 109 | */ 110 | public int baselineHeight() { 111 | return textLayout == null ? 0 : textLayout.getHeight() - textLayout.getLineBaseline(0); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /library/src/main/java/com/eyeem/chips/BubbleSpan.java: -------------------------------------------------------------------------------- 1 | package com.eyeem.chips; 2 | 3 | import android.graphics.Canvas; 4 | import android.graphics.Rect; 5 | import android.text.Spannable; 6 | 7 | import java.util.ArrayList; 8 | 9 | public interface BubbleSpan { 10 | public void setPressed(boolean value, Spannable s); 11 | public void resetWidth(int width); 12 | public ArrayList rect(ILayoutCallback callback); 13 | public void redraw(Canvas canvas); 14 | public void setData(Object data); 15 | public Object data(); 16 | } -------------------------------------------------------------------------------- /library/src/main/java/com/eyeem/chips/BubbleSpanImpl.java: -------------------------------------------------------------------------------- 1 | package com.eyeem.chips; 2 | 3 | import android.graphics.Canvas; 4 | import android.graphics.Color; 5 | import android.graphics.Paint; 6 | import android.graphics.Point; 7 | import android.graphics.Rect; 8 | import android.text.Layout; 9 | import android.text.Spannable; 10 | import android.text.style.ReplacementSpan; 11 | 12 | import java.lang.ref.WeakReference; 13 | import java.util.ArrayList; 14 | 15 | public class BubbleSpanImpl extends ReplacementSpan implements BubbleSpan { 16 | public Object data; 17 | public AwesomeBubble bubble; 18 | WeakReference _et; 19 | int start; 20 | float baselineDiff; 21 | 22 | public BubbleSpanImpl(AwesomeBubble bubble) { 23 | this.bubble = bubble; 24 | } 25 | 26 | public BubbleSpanImpl(AwesomeBubble bubble, ChipsEditText et) { 27 | this.bubble = bubble; 28 | this._et = new WeakReference<>(et); 29 | } 30 | 31 | @Override 32 | public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, Paint paint) { 33 | this.start = start; 34 | canvas.save(); 35 | 36 | baselineDiff = lineCorrectionLogic(bubble); 37 | float transY = y - baselineDiff; 38 | 39 | canvas.translate(x, transY); 40 | bubble.draw(canvas); 41 | canvas.restore(); 42 | } 43 | 44 | @Override 45 | public void redraw(Canvas canvas) { 46 | ChipsEditText et = _et.get(); 47 | if (et == null) return; 48 | if (et.getScrollY() != 0) 49 | return; 50 | 51 | int pos = et.getText().getSpanStart(this); 52 | if (pos == -1) 53 | return; 54 | Layout layout = et.getLayout(); 55 | int line = layout.getLineForOffset(pos); 56 | float x = layout.getPrimaryHorizontal(pos); 57 | float y = layout.getLineTop(line); 58 | x += et.getPaddingLeft(); 59 | y += et.getPaddingTop(); 60 | 61 | canvas.save(); 62 | canvas.translate(x, y - baselineDiff); 63 | bubble.draw(canvas); 64 | canvas.restore(); 65 | } 66 | 67 | @Override 68 | public int getSize(Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) { 69 | return bubble.getWidth(); 70 | } 71 | 72 | @Override 73 | public void setPressed(boolean value, Spannable s) { 74 | bubble.setPressed(value); 75 | } 76 | 77 | @Override 78 | public void resetWidth(int width) { 79 | bubble.resetWidth(width); 80 | } 81 | 82 | @Override 83 | public ArrayList rect(ILayoutCallback callback) { 84 | ArrayList result = new ArrayList(); 85 | Rect position = new Rect(bubble.rect()); 86 | int spanStart = callback.getSpannable().getSpanStart(this); 87 | Point startPoint = callback.getCursorPosition(spanStart); 88 | if (startPoint == null) return result; 89 | position.offset(startPoint.x, startPoint.y); 90 | result.add(position); 91 | return result; 92 | } 93 | 94 | @Override 95 | public void setData(Object data) { 96 | this.data = data; 97 | } 98 | 99 | @Override 100 | public Object data() { 101 | return data; 102 | } 103 | 104 | public static float lineCorrectionLogic(AwesomeBubble bubble) { 105 | return (bubble.getHeight() - bubble.style.bubblePadding - bubble.baselineHeight()); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /library/src/main/java/com/eyeem/chips/BubbleStyle.java: -------------------------------------------------------------------------------- 1 | package com.eyeem.chips; 2 | 3 | import android.content.Context; 4 | import android.content.res.Resources; 5 | import android.content.res.TypedArray; 6 | import android.graphics.Typeface; 7 | import android.graphics.drawable.Drawable; 8 | import android.view.ContextThemeWrapper; 9 | 10 | public class BubbleStyle { 11 | Drawable active; 12 | Drawable pressed; 13 | int textSize; 14 | int textColor; 15 | int textPressedColor; 16 | int bubblePadding; 17 | boolean nextNeedsSpacing; 18 | Typeface typeface; 19 | 20 | public BubbleStyle(Drawable active, Drawable pressed, int textSize, 21 | int textColor, int textPressedColor, int bubblePadding, Typeface tf) { 22 | this(active, pressed, textSize, textColor, textPressedColor, bubblePadding, true); 23 | this.typeface = tf; 24 | } 25 | 26 | public BubbleStyle(Drawable active, Drawable pressed, int textSize, 27 | int textColor, int textPressedColor, int bubblePadding) { 28 | this(active, pressed, textSize, textColor, textPressedColor, bubblePadding, true); 29 | } 30 | 31 | public BubbleStyle(Drawable active, Drawable pressed, int textSize, 32 | int textColor, int textPressedColor, int bubblePadding, boolean nextNeedsSpacing) { 33 | this.active = active; 34 | this.pressed = pressed; 35 | this.textSize = textSize; 36 | this.textColor = textColor; 37 | this.textPressedColor = textPressedColor; 38 | this.bubblePadding = bubblePadding; 39 | this.nextNeedsSpacing = nextNeedsSpacing; 40 | } 41 | 42 | private static final int[] ATTRS = { 43 | R.attr.bubble_stateActive, 44 | R.attr.bubble_statePressed, 45 | R.attr.bubble_textSize, 46 | R.attr.bubble_textColor, 47 | R.attr.bubble_textColorActive, 48 | R.attr.bubble_textPadding, 49 | R.attr.bubble_customFont 50 | }; 51 | 52 | // public static BubbleStyle buildDefault(Context context) { 53 | // return build(context, R.style.default_bubble_style); 54 | // } 55 | 56 | public static BubbleStyle build(Context context, int styleId) { 57 | 58 | Drawable temp; 59 | 60 | context = new ContextThemeWrapper(context.getApplicationContext(), styleId); 61 | Resources r = context.getResources(); 62 | TypedArray ta = context.obtainStyledAttributes(styleId, ATTRS); 63 | 64 | temp = ta.getDrawable(0); 65 | Drawable active = temp; 66 | temp = ta.getDrawable(1); 67 | Drawable pressed = temp; 68 | float size = ta.getDimension(2, r.getDimension(R.dimen.default_bubble_text_size)); 69 | int color = ta.getColor(3, r.getColor(R.color.default_bubble_text_color)); 70 | int colorActive = ta.getColor(4, r.getColor(R.color.default_bubble_text_color_active)); 71 | float pad = ta.getDimension(5, r.getDimension(R.dimen.default_bubble_text_pad)); 72 | String fontName = ta.getString(6); 73 | Typeface tf = null; 74 | if (fontName != null) { 75 | tf = FontCache.getTypeface(context, fontName); 76 | } 77 | 78 | 79 | BubbleStyle bs = new BubbleStyle( 80 | active, 81 | pressed, 82 | (int) size, 83 | color, 84 | colorActive, 85 | (int) pad, 86 | tf); 87 | 88 | ta.recycle(); 89 | return bs; 90 | } 91 | 92 | public void setTextSize(int textSize) { 93 | this.textSize = textSize; 94 | } 95 | 96 | public Drawable getActive() { 97 | return active; 98 | } 99 | 100 | public void setActive(Drawable active) { 101 | this.active = active; 102 | } 103 | 104 | public Drawable getPressed() { 105 | return pressed; 106 | } 107 | 108 | public void setPressed(Drawable pressed) { 109 | this.pressed = pressed; 110 | } 111 | 112 | public int getTextSize() { 113 | return textSize; 114 | } 115 | 116 | public int getTextColor() { 117 | return textColor; 118 | } 119 | 120 | public void setTextColor(int textColor) { 121 | this.textColor = textColor; 122 | } 123 | 124 | public int getTextPressedColor() { 125 | return textPressedColor; 126 | } 127 | 128 | public void setTextPressedColor(int textPressedColor) { 129 | this.textPressedColor = textPressedColor; 130 | } 131 | 132 | public int getBubblePadding() { 133 | return bubblePadding; 134 | } 135 | 136 | public void setBubblePadding(int bubblePadding) { 137 | this.bubblePadding = bubblePadding; 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /library/src/main/java/com/eyeem/chips/CursorDrawable.java: -------------------------------------------------------------------------------- 1 | package com.eyeem.chips; 2 | 3 | 4 | import android.content.Context; 5 | import android.graphics.*; 6 | import android.text.TextPaint; 7 | 8 | public class CursorDrawable { 9 | 10 | private final Paint paint; 11 | ChipsEditText editText; 12 | float textSize; 13 | float cursorWidth; 14 | AwesomeBubble bubble; 15 | public int color; 16 | 17 | public CursorDrawable(ChipsEditText editText, float textSize, float cursorWidth, Context context) { 18 | this.editText = editText; 19 | this.paint = new Paint(); 20 | 21 | paint.setAntiAlias(true); 22 | paint.setFakeBoldText(true); 23 | paint.setStyle(Paint.Style.FILL); 24 | paint.setTextAlign(Paint.Align.LEFT); 25 | this.textSize = textSize; 26 | this.cursorWidth = cursorWidth; 27 | 28 | BubbleStyle bubbleStyle = editText.getCurrentBubbleStyle(); 29 | bubble = new AwesomeBubble(" ", 100, bubbleStyle, new TextPaint()); 30 | color = editText.getTextColors().getDefaultColor(); 31 | } 32 | 33 | public void draw(Canvas canvas, boolean blink) { 34 | Point p = editText.getCursorPosition(); 35 | canvas.save(); 36 | canvas.translate(p.x, p.y - bubble.getHeight() + bubble.style.bubblePadding + bubble.baselineHeight()); 37 | if (editText._manualModeOn) { 38 | // calculate cursor offset 39 | int x_offset = 0; 40 | int y_offset = bubble.style.bubblePadding; 41 | int y_h = bubble.getHeight() - 2 * bubble.style.bubblePadding; 42 | if (editText.manualStart == editText.getSelectionStart()) { // empty bubble case 43 | // draw bubble behind 44 | bubble.draw(canvas); 45 | x_offset = - bubble.getWidth()/2; 46 | } else { 47 | x_offset = 2 * bubble.style.bubblePadding; 48 | } 49 | 50 | // draw cursor inside 51 | if (blink) { 52 | paint.setColor(bubble.style.textColor); 53 | canvas.drawRect(0 - x_offset, y_offset, cursorWidth - x_offset, y_offset + y_h, paint); 54 | } 55 | } else if (blink) { 56 | paint.setColor(color); 57 | canvas.drawRect(0, 0, cursorWidth, textSize, paint); 58 | } 59 | canvas.restore(); 60 | } 61 | 62 | public Point bubble_offset() { 63 | int x_offset = 0; 64 | int y_offset = 0; 65 | if (editText._manualModeOn) { 66 | if (editText.manualStart == editText.getSelectionStart()) { // empty bubble case 67 | x_offset = -bubble.getWidth() / 2; 68 | } else { 69 | x_offset = 2 * bubble.style.bubblePadding; 70 | } 71 | } 72 | return new Point(x_offset, y_offset); 73 | } 74 | 75 | public void setColor(int color) { 76 | this.color = color; 77 | } 78 | } -------------------------------------------------------------------------------- /library/src/main/java/com/eyeem/chips/DefaultBubbles.java: -------------------------------------------------------------------------------- 1 | package com.eyeem.chips; 2 | 3 | import android.content.Context; 4 | import android.content.res.Resources; 5 | import android.graphics.Color; 6 | import android.graphics.Rect; 7 | import android.text.TextPaint; 8 | import android.util.Log; 9 | import android.util.TypedValue; 10 | 11 | import java.util.HashMap; 12 | 13 | /** 14 | * @author vishna 15 | */ 16 | public class DefaultBubbles { 17 | // public static int LILA = 0; 18 | // public static int GRAY = 1; 19 | // public static int GRAY_WHITE_TEXT = 2; 20 | // public static int GREY_EDIT = 3; 21 | // public static int GREEN = 4; 22 | // public static int CITY_COUNTRY = 5; 23 | 24 | // private static HashMap defaults = new HashMap(); 25 | 26 | // public static int v_spacing; 27 | // public static int h_spacing; 28 | public static int long_bubble_workaround; 29 | 30 | // @Deprecated public static BubbleStyle get(int type, Context context) { 31 | // int textSize = context.getResources().getDimensionPixelSize(R.dimen.bubble_text_size); 32 | // if (defaults.get(textSize) == null) { 33 | // defaults.put(textSize, init(context, textSize)); 34 | // } 35 | // return defaults.get(textSize)[type]; 36 | // } 37 | 38 | // public static BubbleStyle get(int type, Context context, int textSize) { 39 | // if (defaults.get(textSize) == null) { 40 | // defaults.put(textSize, init(context, textSize)); 41 | // } 42 | // return defaults.get(textSize)[type]; 43 | // } 44 | 45 | // public static BubbleStyle[] init(Context context, int textSize) { 46 | // context = context.getApplicationContext(); 47 | // Resources r = context.getResources(); 48 | 49 | // int padding = Math.round((float)textSize * (0.05f)); 50 | // v_spacing = r.getDimensionPixelSize(R.dimen.bubble_v_spacing); 51 | // h_spacing = r.getDimensionPixelSize(R.dimen.bubble_h_spacing); 52 | 53 | // BubbleStyle[] array = new BubbleStyle[] { 54 | // new BubbleStyle( 55 | // context.getResources().getDrawable(R.drawable.lilatext_background_active), 56 | // context.getResources().getDrawable(R.drawable.lilatext_background_pressed), 57 | // textSize, 0xffebf5e0, 0xffebf5e0, padding), // LILA 58 | // new BubbleStyle( 59 | // context.getResources().getDrawable(R.drawable.greybubble_background_edit), 60 | // context.getResources().getDrawable(R.drawable.greybubble_background_edit), 61 | // textSize, Color.WHITE, Color.WHITE, padding), // GRAY 62 | // new BubbleStyle( 63 | // context.getResources().getDrawable(R.drawable.greybubble_background), 64 | // context.getResources().getDrawable(R.drawable.greybubble_background_pressed), 65 | // textSize, Color.WHITE, Color.WHITE, padding), // GRAY_WHITE_TEXT 66 | // new BubbleStyle( 67 | // context.getResources().getDrawable(R.drawable.greybubble_edit), 68 | // context.getResources().getDrawable(R.drawable.greybubble_edit), 69 | // textSize, Color.WHITE, Color.WHITE, padding), // GREY_EDIT 70 | // new BubbleStyle( 71 | // context.getResources().getDrawable(R.drawable.greentext_background_active), 72 | // context.getResources().getDrawable(R.drawable.greentext_background_pressed), 73 | // textSize, 0xffebe0f5, 0xffebe0f5, padding), // GREEN 74 | // new BubbleStyle( 75 | // null, null, 76 | // context.getResources().getDimensionPixelSize(R.dimen.bubble_country_text_size), 77 | // 0xffcccccc, 0xff000000, 0, false) // CITY_COUNTRY 78 | // }; 79 | 80 | // // calculate workaround per device 81 | // TextPaint paint = new TextPaint(); 82 | // float _dp = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 16, r.getDisplayMetrics()); 83 | // paint.setAntiAlias(true); 84 | // paint.setTextSize(_dp); 85 | // paint.setColor(Color.BLACK); 86 | // long_bubble_workaround = (int)paint.measureText(" "); 87 | // Log.i("CHIPS", "long_bubble_workaround = "+long_bubble_workaround); 88 | 89 | // return array; 90 | // } 91 | } -------------------------------------------------------------------------------- /library/src/main/java/com/eyeem/chips/FontCache.java: -------------------------------------------------------------------------------- 1 | package com.eyeem.chips; 2 | 3 | import android.content.Context; 4 | import android.graphics.Typeface; 5 | 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | /** 10 | * Created by Dario on 03/04/2018. 11 | */ 12 | 13 | public class FontCache { 14 | 15 | private static Map cache = new HashMap<>(); 16 | 17 | public static Typeface getTypeface(Context context, String assetPath) { 18 | if (!cache.containsKey(assetPath)) { 19 | Typeface tf = Typeface.createFromAsset(context.getAssets(), assetPath); 20 | cache.put(assetPath, tf); 21 | } 22 | return cache.get(assetPath); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /library/src/main/java/com/eyeem/chips/ILayoutCallback.java: -------------------------------------------------------------------------------- 1 | package com.eyeem.chips; 2 | 3 | import android.graphics.Point; 4 | import android.text.Spannable; 5 | 6 | public interface ILayoutCallback { 7 | public Point getCursorPosition(int pos); 8 | public int getLine(int pos); 9 | public Spannable getSpannable(); 10 | public int getLineEnd(int line); 11 | public int getLineHeight(); 12 | } 13 | -------------------------------------------------------------------------------- /library/src/main/java/com/eyeem/chips/Linkify.java: -------------------------------------------------------------------------------- 1 | package com.eyeem.chips; 2 | 3 | import java.io.Serializable; 4 | import java.util.ArrayList; 5 | import java.util.Collection; 6 | import java.util.regex.Matcher; 7 | import java.util.regex.Pattern; 8 | 9 | /** 10 | * Standarizes url link handling... sort of. 11 | * User: vishna 12 | * Date: 3/11/13 13 | * Time: 5:37 PM 14 | */ 15 | public class Linkify { 16 | public static final Pattern USER_REGEX = Pattern.compile("@([A-Za-z0-9_]+)"); 17 | public static final Pattern VALID_EMAIL_ADDRESS_REGEX = Pattern.compile("[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,6}", Pattern.CASE_INSENSITIVE); 18 | 19 | public static Entities computeEntities(String ss){ 20 | 21 | Entities entities = new Entities(); 22 | Matcher matcher = USER_REGEX.matcher(ss); 23 | while (matcher.find()) { 24 | final String user = matcher.group(1); 25 | int i = matcher.start()-1; 26 | if (i == -1 || (i > 0 && Character.isWhitespace(ss.charAt(i)))) { 27 | entities.add(new Entity(matcher.start(), matcher.end(), user, matcher.group(), Entity.MENTION)); 28 | } 29 | } 30 | 31 | matcher = Regex.VALID_URL.matcher(ss); 32 | while (matcher.find()) { 33 | final String _url = matcher.group(); 34 | int i = matcher.start() - 1 + _url.indexOf(_url.trim()); 35 | if (i == -1 || (i > 0 && Character.isWhitespace(ss.charAt(i)))) { 36 | String url = _url.trim(); 37 | if (url != null && !url.startsWith("http://") && !url.startsWith("https://") && !url.startsWith("ftp://")) { 38 | url = "http://" + url; 39 | } 40 | entities.add(new Entity(matcher.start(), matcher.end(), url, matcher.group(), Entity.URL)); 41 | } 42 | } 43 | 44 | matcher = VALID_EMAIL_ADDRESS_REGEX.matcher(ss); 45 | while (matcher.find()) { 46 | final String email = matcher.group(); 47 | int i = matcher.start()-1; 48 | if (i == -1 || (i > 0 && Character.isWhitespace(ss.charAt(i)))) { 49 | entities.add(new Entity(matcher.start(), matcher.end(), email, matcher.group(), Entity.EMAIL)); 50 | } 51 | } 52 | 53 | return entities; 54 | } 55 | 56 | public static class Entities extends ArrayList { 57 | @Override 58 | public boolean add(Entity a) { 59 | // TODO this should be some smart binary interpolation alogrithm 60 | for (Entity b : this) { 61 | // check if entities intersect 62 | if (a.start >= b.start && a.start < b.end) 63 | return false; 64 | if (a.end > b.start && a.end <= b.end) 65 | return false; 66 | } 67 | return super.add(a); 68 | } 69 | 70 | @Override 71 | public boolean addAll(Collection collection) { 72 | for (Entity e : collection) 73 | add(e); 74 | return true; 75 | } 76 | 77 | /** 78 | * This works on assumptions that mPhoto.entities are grouped and 79 | * ordered e.g. album|album|link|link|mention|person|person|city 80 | * They should be cause this is what Linkify.computeEntites does 81 | * @param type of album that is required 82 | * @return Ordered array of album entites of one type 83 | */ 84 | public ArrayList subEntities (int type) { 85 | ArrayList albums = new ArrayList(); 86 | for (Linkify.Entity entity : this) { 87 | if (entity.type == type) 88 | albums.add(entity); 89 | } 90 | return albums; 91 | } 92 | } 93 | 94 | public static class Entity implements Serializable { 95 | public static final int EMAIL = 0; 96 | public static final int PERSON = 1; 97 | public static final int URL = 2; 98 | public static final int ALBUM = 3; 99 | public static final int VENUE = 4; 100 | public static final int CITY = 5; 101 | public static final int COUNTRY = 6; 102 | public static final int MENTION = 7; 103 | public static final int UNKNOWN = 8; 104 | 105 | public int start; 106 | public int end; 107 | public String id; 108 | public String text; 109 | public int type; 110 | public Serializable data; 111 | 112 | public Entity() {}; 113 | public Entity(int start, int end, String id, String text, int type) { 114 | this.start = start; 115 | this.end = end; 116 | this.id = id; 117 | this.text = text; 118 | this.type = type; 119 | } 120 | } 121 | } 122 | 123 | -------------------------------------------------------------------------------- /library/src/main/java/com/eyeem/chips/MultilineEditText.java: -------------------------------------------------------------------------------- 1 | package com.eyeem.chips; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.view.inputmethod.EditorInfo; 6 | import android.view.inputmethod.InputConnection; 7 | import android.widget.EditText; 8 | 9 | /** 10 | * Created with IntelliJ IDEA. 11 | * User: vishna 12 | * Date: 6/24/13 13 | * Time: 5:46 PM 14 | * To change this template use File | Settings | File Templates. 15 | */ 16 | public class MultilineEditText extends EditText { 17 | public MultilineEditText(Context context) { 18 | super(context); 19 | } 20 | 21 | public MultilineEditText(Context context, AttributeSet attrs) { 22 | super(context, attrs); 23 | } 24 | 25 | public MultilineEditText(Context context, AttributeSet attrs, int defStyle) { 26 | super(context, attrs, defStyle); 27 | } 28 | 29 | @Override 30 | public InputConnection onCreateInputConnection(EditorInfo outAttrs) { 31 | InputConnection connection = super.onCreateInputConnection(outAttrs); 32 | int imeActions = outAttrs.imeOptions&EditorInfo.IME_MASK_ACTION; 33 | if ((imeActions&EditorInfo.IME_ACTION_DONE) != 0) { 34 | // clear the existing action 35 | outAttrs.imeOptions ^= imeActions; 36 | // set the DONE action 37 | outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE; 38 | } 39 | if ((outAttrs.imeOptions&EditorInfo.IME_FLAG_NO_ENTER_ACTION) != 0) { 40 | outAttrs.imeOptions &= ~EditorInfo.IME_FLAG_NO_ENTER_ACTION; 41 | } 42 | return connection; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /library/src/main/java/com/eyeem/chips/TapableSpan.java: -------------------------------------------------------------------------------- 1 | package com.eyeem.chips; 2 | 3 | import android.graphics.Canvas; 4 | import android.graphics.Point; 5 | import android.graphics.Rect; 6 | import android.graphics.Typeface; 7 | import android.text.Spannable; 8 | import android.text.Spanned; 9 | import android.text.style.ForegroundColorSpan; 10 | import android.text.style.StyleSpan; 11 | 12 | import java.util.ArrayList; 13 | 14 | /** 15 | * Created with IntelliJ IDEA. 16 | * User: vishna 17 | * Date: 6/4/13 18 | * Time: 4:15 PM 19 | * To change this template use File | Settings | File Templates. 20 | */ 21 | public class TapableSpan extends StyleSpan implements BubbleSpan { 22 | private Object data; 23 | private ForegroundColorSpan activeSpan; 24 | private ForegroundColorSpan inactiveSpan; 25 | 26 | public TapableSpan(int activeColor, int inactiveColor) { 27 | this(activeColor, inactiveColor, Typeface.NORMAL); 28 | } 29 | 30 | public TapableSpan(int activeColor, int inactiveColor, int style) { 31 | super(style); 32 | activeSpan = new ForegroundColorSpan(activeColor); 33 | inactiveSpan = new ForegroundColorSpan(inactiveColor); 34 | } 35 | 36 | @Override 37 | public void setPressed(boolean value, Spannable s) { 38 | try { 39 | if (value) { 40 | s.removeSpan(inactiveSpan); 41 | s.setSpan(activeSpan, s.getSpanStart(this), s.getSpanEnd(this), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 42 | } else { 43 | s.removeSpan(activeSpan); 44 | s.setSpan(inactiveSpan, s.getSpanStart(this), s.getSpanEnd(this), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 45 | } 46 | } catch (IndexOutOfBoundsException ioobe) { 47 | // don't crash if something was set in an ugly manner 48 | } 49 | } 50 | 51 | @Override 52 | public void resetWidth(int width) { 53 | // noop 54 | } 55 | 56 | @Override 57 | public ArrayList rect(ILayoutCallback callback) { 58 | ArrayList result = new ArrayList(); 59 | int spanStart = callback.getSpannable().getSpanStart(this); 60 | int spanEnd = callback.getSpannable().getSpanEnd(this); 61 | int startLine = callback.getLine(spanStart); 62 | int endLine = callback.getLine(spanEnd); 63 | int currentPos = spanStart; 64 | for (int i = startLine; i <= endLine; i++) { 65 | Point startPoint = callback.getCursorPosition(currentPos); 66 | if (startPoint == null) continue; 67 | currentPos = (i != endLine) ? callback.getLineEnd(i) : spanEnd; 68 | Point endPoint = callback.getCursorPosition(currentPos-1); 69 | if (endPoint == null) continue; 70 | int h = callback.getLineHeight(); 71 | result.add(new Rect(startPoint.x, startPoint.y, endPoint.x, endPoint.y+h)); 72 | } 73 | return result; 74 | } 75 | 76 | @Override 77 | public void redraw(Canvas canvas) {} 78 | 79 | @Override 80 | public void setData(Object data) { 81 | this.data = data; 82 | } 83 | 84 | @Override 85 | public Object data() { 86 | return data; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /library/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | 16sp 11 | #FFF 12 | 1.25 13 | 4 14 | …more 15 | #FFF 16 | #888 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 45 | 46 | 48 | 49 | 16sp 50 | 2sp 51 | #FFF 52 | #888 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /library/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /library/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 14dp 4 | 16dp 5 | 2dp 6 | 2dp 7 | 3dp 8 | 3dp 9 | 240dp 10 | 11 | -------------------------------------------------------------------------------- /library/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | ACTIVITY_ENTRY_NAME 4 | 5 | -------------------------------------------------------------------------------- /notes/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | mavenCentral() 4 | } 5 | 6 | dependencies { 7 | classpath 'com.android.tools.build:gradle:2.1.0' 8 | classpath 'me.tatarka:gradle-retrolambda:3.2.5' 9 | } 10 | } 11 | 12 | apply plugin: 'com.android.application' 13 | apply plugin: 'me.tatarka.retrolambda' // follow instructions here 14 | apply plugin: 'com.neenbedankt.android-apt' 15 | 16 | dependencies { 17 | repositories { 18 | maven { 19 | url 'https://oss.sonatype.org/content/repositories/snapshots/' 20 | } 21 | mavenCentral() 22 | mavenLocal() 23 | } 24 | 25 | compile 'com.android.support:support-v4:23.4.0' 26 | compile 'com.android.support:appcompat-v7:23.4.0' 27 | compile 'com.android.support:recyclerview-v7:23.4.0' 28 | 29 | compile 'com.squareup.okhttp:okhttp:2.0.0' 30 | compile 'com.squareup.okhttp:okhttp-urlconnection:2.0.0' 31 | compile 'com.squareup.okio:okio:1.0.0' 32 | compile 'com.squareup:otto:1.3.6' 33 | compile 'io.reactivex:rxandroid:0.24.0' 34 | 35 | compile 'com.squareup.mortar:mortar:0.17' 36 | 37 | compile 'com.google.dagger:dagger:2.0' 38 | apt 'com.google.dagger:dagger-compiler:2.0' 39 | 40 | compile 'org.glassfish:javax.annotation:10.0-b28' 41 | compile 'com.squareup.flow:flow:0.10' 42 | compile 'com.squareup.flow:flow-path:0.10' 43 | compile 'com.jakewharton:butterknife:5.1.2' 44 | compile 'com.google.code.gson:gson:2.2.4' 45 | 46 | compile 'com.eyeem.potato:library:0.9.2.5-SNAPSHOT@aar' 47 | 48 | compile 'com.mikepenz.iconics:library:0.7.0@aar' 49 | 50 | provided 'org.projectlombok:lombok:1.12.6' 51 | apt 'org.projectlombok:lombok:1.12.6' 52 | 53 | compile project(':library') 54 | } 55 | 56 | android { 57 | buildToolsVersion '23.0.3' 58 | compileSdkVersion 23 59 | 60 | defaultConfig { 61 | targetSdkVersion 23 62 | } 63 | 64 | lintOptions { 65 | // lint keeps complaining about okio not being included 66 | disable 'InvalidPackage' 67 | abortOnError false 68 | } 69 | 70 | compileOptions { 71 | sourceCompatibility JavaVersion.VERSION_1_8 72 | targetCompatibility JavaVersion.VERSION_1_8 73 | } 74 | } -------------------------------------------------------------------------------- /notes/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':library' -------------------------------------------------------------------------------- /notes/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 9 | 10 | 11 | 12 | 18 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /notes/src/main/assets/notes.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "1", 4 | "text": "This is a sample app demonstrating possibilities of the chips library.", 5 | "entities": [ 6 | { 7 | "start": 56, 8 | "end": 61, 9 | "type": "url", 10 | "data": "https://github.com/eyeem/chips-android", 11 | "displayText": "chips" 12 | } 13 | ] 14 | }, 15 | { 16 | "id": "2", 17 | "text": "The text you read right now is stored in assets/notes.json file. It goes over most interesting parts of this app.", 18 | "entities": [] 19 | }, 20 | { 21 | "id": "3", 22 | "text": "You can click on each item on this list and go into edit mode. There you can modify contents of the selected item, preview, save your changes and go back.", 23 | "entities": [] 24 | }, 25 | { 26 | "id": "4", 27 | "text": "ChipsTextView text layout can be rendered in a background thread, thanks to that we can reduce time spent in onBindView from up to 100ms down to 4ms and dramatically improve smoothness of scrolling.", 28 | "entities": [ 29 | { 30 | "start": 0, 31 | "end": 13, 32 | "type": "url", 33 | "data": "https://github.com/eyeem/chips-android/blob/master/library/src/main/java/com/eyeem/chips/ChipsTextView.java", 34 | "displayText": "ChipsTextView" 35 | } 36 | ] 37 | }, 38 | { 39 | "id": "5", 40 | "text": "You can click on each item on this list and go into edit mode. There you can modify contents of the selected item and go back.", 41 | "entities": [] 42 | }, 43 | { 44 | "id": "6", 45 | "text": "Navigation in this app is implemented using the flow library. Mortar manages scopes of each screen and then dependencies are injected using Dagger2.", 46 | "entities": [] 47 | } 48 | ] -------------------------------------------------------------------------------- /notes/src/main/assets/sailec_bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eyeem/chips-android/cd9ad3fdac6c621095570713ca92a33b1bdfa22a/notes/src/main/assets/sailec_bold.ttf -------------------------------------------------------------------------------- /notes/src/main/assets/sailec_light.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eyeem/chips-android/cd9ad3fdac6c621095570713ca92a33b1bdfa22a/notes/src/main/assets/sailec_light.otf -------------------------------------------------------------------------------- /notes/src/main/java/com/eyeem/notes/App.java: -------------------------------------------------------------------------------- 1 | package com.eyeem.notes; 2 | 3 | import android.app.Application; 4 | 5 | import com.eyeem.notes.core.StorageDep; 6 | import com.eyeem.notes.core.AppDep; 7 | import com.eyeem.notes.core.AppModule; 8 | import com.eyeem.notes.core.PausableExecutorModule; 9 | import com.eyeem.notes.mortarflow.FlowDep; 10 | import com.eyeem.notes.mortarflow.ScopeSingleton; 11 | 12 | import mortar.MortarScope; 13 | 14 | import static com.eyeem.notes.mortarflow.Utils.DAGGER_SERVICE; 15 | import static com.eyeem.notes.mortarflow.Utils.createComponent; 16 | 17 | 18 | /** 19 | * Created by vishna on 27/01/15. 20 | */ 21 | public class App extends Application { 22 | private MortarScope rootScope; 23 | 24 | @dagger.Component(modules = {AppModule.class, PausableExecutorModule.class}) 25 | @ScopeSingleton(Component.class) 26 | public interface Component extends FlowDep, AppDep, StorageDep {} 27 | 28 | @Override public void onCreate() { 29 | super.onCreate(); 30 | } 31 | 32 | @Override public Object getSystemService(String name) { 33 | if (rootScope == null) { 34 | rootScope = MortarScope.buildRootScope() 35 | .withService(DAGGER_SERVICE, createComponent(Component.class, new AppModule(this))) 36 | .build(App.class.getName()); 37 | } 38 | 39 | return rootScope.hasService(name) ? rootScope.getService(name) : super.getSystemService(name); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /notes/src/main/java/com/eyeem/notes/adapter/NoteHolder.java: -------------------------------------------------------------------------------- 1 | package com.eyeem.notes.adapter; 2 | 3 | import android.support.v7.widget.RecyclerView; 4 | import android.view.View; 5 | 6 | import com.eyeem.chips.BubbleSpan; 7 | import com.eyeem.chips.ChipsTextView; 8 | import com.eyeem.notes.R; 9 | import com.eyeem.notes.event.BubbleClickedEvent; 10 | import com.eyeem.notes.event.NoteClickedEvent; 11 | import com.eyeem.notes.model.Note; 12 | import com.squareup.otto.Bus; 13 | 14 | import butterknife.ButterKnife; 15 | import butterknife.InjectView; 16 | import butterknife.OnClick; 17 | 18 | /** 19 | * Created by vishna on 18/02/15. 20 | */ 21 | public class NoteHolder extends RecyclerView.ViewHolder implements ChipsTextView.OnBubbleClickedListener { 22 | 23 | @InjectView(R.id.note_text_view) ChipsTextView textView; 24 | final Bus bus; 25 | Note note; 26 | 27 | public NoteHolder(View itemView, Bus bus) { 28 | super(itemView); 29 | ButterKnife.inject(this, itemView); 30 | this.bus = bus; 31 | textView.setOnBubbleClickedListener(this); 32 | } 33 | 34 | public NoteHolder setNote(Note note) { 35 | this.note = note; 36 | return this; 37 | } 38 | 39 | @OnClick(R.id.note_container) void onClick(View view) { 40 | if (note != null) bus.post(new NoteClickedEvent(note)); 41 | } 42 | 43 | @Override public void onBubbleClicked(View view, BubbleSpan bubbleSpan) { 44 | 45 | if (bubbleSpan.data() instanceof ChipsTextView.Truncation) { 46 | textView.expand(true); 47 | return; 48 | } 49 | 50 | bus.post(new BubbleClickedEvent(view, bubbleSpan)); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /notes/src/main/java/com/eyeem/notes/adapter/NotesAdapter.java: -------------------------------------------------------------------------------- 1 | package com.eyeem.notes.adapter; 2 | 3 | import android.content.Context; 4 | import android.content.res.Resources; 5 | import android.support.v7.widget.RecyclerView; 6 | import android.text.Spannable; 7 | import android.text.SpannableString; 8 | import android.text.SpannableStringBuilder; 9 | import android.text.TextPaint; 10 | import android.util.TypedValue; 11 | import android.view.LayoutInflater; 12 | import android.view.View; 13 | import android.view.ViewGroup; 14 | 15 | import com.eyeem.chips.BubbleStyle; 16 | import com.eyeem.chips.ChipsTextView; 17 | import com.eyeem.chips.LayoutBuild; 18 | import com.eyeem.notes.R; 19 | import com.eyeem.notes.event.NoteClickedEvent; 20 | import com.eyeem.notes.experimental.CacheOnScroll; 21 | import com.eyeem.notes.model.Note; 22 | import com.eyeem.storage.Storage; 23 | import com.squareup.otto.Bus; 24 | 25 | import java.util.ArrayList; 26 | import java.util.List; 27 | 28 | import butterknife.ButterKnife; 29 | import butterknife.InjectView; 30 | import butterknife.OnClick; 31 | 32 | /** 33 | * Created by vishna on 03/02/15. 34 | */ 35 | public class NotesAdapter extends RecyclerAdapter { 36 | 37 | BubbleStyle bubbleStyle; 38 | LayoutBuild.Config layoutConfig; 39 | int width = 0; 40 | CacheOnScroll cacheOnScroll; 41 | Bus bus; 42 | 43 | public NotesAdapter(Storage.List list, CacheOnScroll cacheOnScroll, Bus bus) { 44 | super(list); 45 | this.cacheOnScroll = cacheOnScroll; 46 | this.bus = bus; 47 | } 48 | 49 | Context appContext; 50 | 51 | @Override public NoteHolder onCreateViewHolder(ViewGroup parent, int viewType) { 52 | NoteHolder noteHolder = new NoteHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.note_row, parent, false), bus); 53 | if(layoutConfig == null){ 54 | // hacky. Ahead loader calculates with the config of the 1st inflated view 55 | // TODO: maybe a better way to handle that? 56 | layoutConfig = noteHolder.textView.defaultConfig(); 57 | this.cacheOnScroll.setAheadLoader(new LayoutAheadLoader()); 58 | } 59 | 60 | if (width <= 0) { 61 | width = parent.getWidth() - noteHolder.textView.getPaddingLeft() - noteHolder.textView.getPaddingRight(); 62 | } 63 | 64 | if (appContext == null) { 65 | appContext = parent.getContext().getApplicationContext(); 66 | } 67 | 68 | if (bubbleStyle == null) { 69 | bubbleStyle = Note.defaultBubbleStyle(appContext, layoutConfig.textPaint.getTextSize()); 70 | } 71 | 72 | return noteHolder; 73 | } 74 | 75 | @Override public void onBindViewHolder(NoteHolder holder, final int position) { 76 | 77 | Note note = getItem(position); 78 | // holder.setNote(note).textView.setLayoutBuild(cacheOnScroll.get(note.id)); 79 | } 80 | 81 | public CacheOnScroll getCacheOnScroll() { 82 | return cacheOnScroll; 83 | } 84 | 85 | public class LayoutAheadLoader implements CacheOnScroll.AheadLoader { 86 | @Override public LayoutBuild buildFor(String id) { 87 | Note found = null; 88 | 89 | // TODO some smart lambda 90 | for (Note note : items) { // find note 91 | if (note.id.equals(id)) { 92 | found = note; 93 | break; 94 | } 95 | } 96 | 97 | if (found == null) return null; 98 | 99 | Spannable spannable = new SpannableString(found.textSpan(bubbleStyle, null)); 100 | 101 | LayoutBuild layoutBuild = new LayoutBuild(spannable, layoutConfig); 102 | layoutBuild.build(width); 103 | 104 | return layoutBuild; 105 | } 106 | 107 | @Override public List idsAround(int centerIndex, int radius) { 108 | 109 | ArrayList result = new ArrayList<>(); 110 | 111 | if (centerIndex < 0 || centerIndex >= items.size()) return result; 112 | 113 | for (int r = 1; r < radius; r++) { 114 | 115 | int left = centerIndex - r; 116 | int right = centerIndex + r; 117 | 118 | if (left > 0) { 119 | result.add(items.get(left).id); 120 | } 121 | 122 | if (right < items.size()) { 123 | result.add(items.get(right).id); 124 | } 125 | } 126 | 127 | return result; 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /notes/src/main/java/com/eyeem/notes/adapter/RecyclerAdapter.java: -------------------------------------------------------------------------------- 1 | package com.eyeem.notes.adapter; 2 | 3 | import android.support.v7.widget.RecyclerView; 4 | import android.view.View; 5 | 6 | import com.eyeem.storage.Storage; 7 | 8 | /** 9 | * Created by vishna on 15/11/13. 10 | */ 11 | public abstract class RecyclerAdapter 12 | extends RecyclerView.Adapter implements Storage.Subscription { 13 | 14 | protected Storage.List items; 15 | 16 | public RecyclerAdapter(Storage.List items) { 17 | this.items = items; 18 | } 19 | 20 | public void replace(Storage.List items) { 21 | if (this.items != null) { 22 | this.items.unsubscribe(this); 23 | } 24 | this.items = items; 25 | if (this.items != null) { 26 | this.items.subscribe(this); 27 | } 28 | notifyDataSetChanged(); 29 | } 30 | 31 | @Override 32 | public int getItemCount() { 33 | return items == null ? 0 : items.size(); 34 | } 35 | 36 | public T getItem(int position) { 37 | return items.get(position); 38 | } 39 | 40 | @Override 41 | public long getItemId(int position) { 42 | return Math.abs(items.idForPosition(position).hashCode()); 43 | } 44 | 45 | @Override public void onUpdate(Action action) { 46 | notifyDataSetChanged(); 47 | if (emptyView != null) { 48 | emptyView.setVisibility(getItemCount() == 0 ? View.VISIBLE : View.GONE); 49 | } 50 | } 51 | 52 | @Override 53 | public void registerAdapterDataObserver(RecyclerView.AdapterDataObserver observer) { 54 | super.registerAdapterDataObserver(observer); 55 | if (items != null) items.subscribe(this); 56 | } 57 | 58 | @Override 59 | public void unregisterAdapterDataObserver(RecyclerView.AdapterDataObserver observer) { 60 | super.unregisterAdapterDataObserver(observer); 61 | if (items != null) items.unsubscribe(this); 62 | } 63 | 64 | ///// seems empty view support was dropped thus putting this in 65 | private View emptyView; 66 | 67 | public void setEmptyView(View view) { 68 | this.emptyView = view; 69 | } 70 | } -------------------------------------------------------------------------------- /notes/src/main/java/com/eyeem/notes/core/AppDep.java: -------------------------------------------------------------------------------- 1 | package com.eyeem.notes.core; 2 | 3 | import com.eyeem.notes.App; 4 | import com.eyeem.notes.experimental.PausableThreadPoolExecutor; 5 | import com.squareup.otto.Bus; 6 | 7 | /** 8 | * Created by vishna on 17/02/15. 9 | */ 10 | public interface AppDep { 11 | App provideApp(); 12 | PausableThreadPoolExecutor providePausableThreadPool(); 13 | Bus provideBus(); 14 | } 15 | -------------------------------------------------------------------------------- /notes/src/main/java/com/eyeem/notes/core/AppModule.java: -------------------------------------------------------------------------------- 1 | package com.eyeem.notes.core; 2 | 3 | import android.content.Context; 4 | 5 | import com.eyeem.notes.App; 6 | import com.eyeem.notes.mortarflow.FlowBundler; 7 | import com.eyeem.notes.mortarflow.GsonParceler; 8 | import com.eyeem.notes.mortarflow.ScopeSingleton; 9 | import com.eyeem.notes.screen.Notes; 10 | import com.google.gson.Gson; 11 | import com.google.gson.GsonBuilder; 12 | import com.squareup.otto.Bus; 13 | 14 | import dagger.Module; 15 | import dagger.Provides; 16 | import flow.History; 17 | import flow.StateParceler; 18 | 19 | @Module 20 | public class AppModule { 21 | 22 | private final App app; 23 | 24 | public AppModule(App app) { 25 | this.app = app; 26 | } 27 | 28 | @Provides Context context() { return app; } 29 | 30 | @Provides App app() { return app; } 31 | 32 | @Provides @ScopeSingleton(App.Component.class) NoteStorage provideNoteStorage(App app) { 33 | return new NoteStorage(app); 34 | } 35 | 36 | @Provides @ScopeSingleton(App.Component.class) FlowBundler provideFlowBundler(StateParceler parceler, final NoteStorage storage) { 37 | return new FlowBundler(parceler) { 38 | @Override protected History getColdStartHistory(History restoredHistory) { 39 | return restoredHistory == null ? History.single(new Notes(storage.all())) : restoredHistory; 40 | } 41 | }; 42 | } 43 | 44 | @Provides @ScopeSingleton(App.Component.class) Gson provideGson() { 45 | return new GsonBuilder().create(); 46 | } 47 | 48 | @Provides @ScopeSingleton(App.Component.class) StateParceler provideParcer(Gson gson) { 49 | return new GsonParceler(gson); 50 | } 51 | 52 | @Provides @ScopeSingleton(App.Component.class) Bus provideBus() { 53 | return new Bus(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /notes/src/main/java/com/eyeem/notes/core/BootstrapNotesModule.java: -------------------------------------------------------------------------------- 1 | package com.eyeem.notes.core; 2 | 3 | import com.eyeem.chips.Linkify; 4 | import com.eyeem.notes.App; 5 | import com.eyeem.notes.model.Note; 6 | import com.eyeem.notes.utils.Assets; 7 | import com.eyeem.notes.utils.LinkifyDeserialiser; 8 | import com.google.gson.Gson; 9 | import com.google.gson.GsonBuilder; 10 | import com.google.gson.reflect.TypeToken; 11 | 12 | import java.lang.reflect.Type; 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | 16 | import dagger.Module; 17 | import dagger.Provides; 18 | import rx.Observable; 19 | import rx.schedulers.Schedulers; 20 | 21 | /** 22 | * Created by vishna on 17/02/15. 23 | */ 24 | @Module public class BootstrapNotesModule { 25 | 26 | @Provides public Observable> bootstrapNotes(final App app) { 27 | return 28 | Assets.from(app, "notes.json") 29 | .map((String jsonString) -> fromJSONString(jsonString)).subscribeOn(Schedulers.io()); 30 | } 31 | 32 | private static List fromJSONString(String jsonString) { 33 | Gson gson = new GsonBuilder().registerTypeAdapter(Linkify.Entities.class, new LinkifyDeserialiser()).create(); 34 | Type listType = new TypeToken>() {}.getType(); 35 | return gson.fromJson(jsonString, listType); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /notes/src/main/java/com/eyeem/notes/core/NoteStorage.java: -------------------------------------------------------------------------------- 1 | package com.eyeem.notes.core; 2 | 3 | import android.content.Context; 4 | 5 | import com.eyeem.notes.model.Note; 6 | import com.eyeem.storage.Storage; 7 | 8 | /** 9 | * Created by vishna on 16/02/15. 10 | */ 11 | public class NoteStorage extends Storage { 12 | 13 | public NoteStorage(Context context) { 14 | super(context); 15 | init(); 16 | } 17 | 18 | @Override public String id(Note note) { 19 | return note.id; 20 | } 21 | 22 | @Override public Class classname() { 23 | return Note.class; 24 | } 25 | 26 | public List all() { 27 | return obtainList("all"); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /notes/src/main/java/com/eyeem/notes/core/PausableExecutorModule.java: -------------------------------------------------------------------------------- 1 | package com.eyeem.notes.core; 2 | 3 | import com.eyeem.notes.App; 4 | import com.eyeem.notes.experimental.PausableThreadPoolExecutor; 5 | import com.eyeem.notes.mortarflow.ScopeSingleton; 6 | 7 | import java.util.concurrent.LinkedBlockingQueue; 8 | import java.util.concurrent.TimeUnit; 9 | 10 | import dagger.Module; 11 | import dagger.Provides; 12 | 13 | /** 14 | * Created by vishna on 17/02/15. 15 | */ 16 | @Module 17 | public class PausableExecutorModule { 18 | private final static PausableThreadPoolExecutor sPausableExecutor; 19 | // Sets the amount of time an idle thread waits before terminating 20 | private static final int NUMBER_OF_CORES = 2; 21 | // Sets the amount of time an idle thread waits before terminating 22 | private static final int KEEP_ALIVE_TIME = 5; 23 | // Sets the Time Unit to seconds 24 | private static final TimeUnit KEEP_ALIVE_TIME_UNIT = TimeUnit.SECONDS; 25 | 26 | @Provides @ScopeSingleton(App.Component.class) PausableThreadPoolExecutor provideExecutor() { 27 | return sPausableExecutor; 28 | } 29 | 30 | static { 31 | sPausableExecutor = new PausableThreadPoolExecutor( 32 | NUMBER_OF_CORES, // Initial pool size 33 | NUMBER_OF_CORES, // Max pool size 34 | KEEP_ALIVE_TIME, 35 | KEEP_ALIVE_TIME_UNIT, 36 | new LinkedBlockingQueue()); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /notes/src/main/java/com/eyeem/notes/core/StorageDep.java: -------------------------------------------------------------------------------- 1 | package com.eyeem.notes.core; 2 | 3 | /** 4 | * Created by vishna on 16/02/15. 5 | */ 6 | public interface StorageDep { 7 | NoteStorage provideNoteStorage(); 8 | } 9 | -------------------------------------------------------------------------------- /notes/src/main/java/com/eyeem/notes/event/BubbleClickedEvent.java: -------------------------------------------------------------------------------- 1 | package com.eyeem.notes.event; 2 | 3 | import android.view.View; 4 | 5 | import com.eyeem.chips.BubbleSpan; 6 | 7 | /** 8 | * Created by vishna on 18/02/15. 9 | */ 10 | public class BubbleClickedEvent { 11 | public View view; 12 | public BubbleSpan bubbleSpan; 13 | 14 | public BubbleClickedEvent(View view, BubbleSpan bubbleSpan) { 15 | this.view = view; 16 | this.bubbleSpan = bubbleSpan; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /notes/src/main/java/com/eyeem/notes/event/NewNoteEvent.java: -------------------------------------------------------------------------------- 1 | package com.eyeem.notes.event; 2 | 3 | /** 4 | * Created by vishna on 19/02/15. 5 | */ 6 | public class NewNoteEvent { 7 | } 8 | -------------------------------------------------------------------------------- /notes/src/main/java/com/eyeem/notes/event/NoteClickedEvent.java: -------------------------------------------------------------------------------- 1 | package com.eyeem.notes.event; 2 | 3 | import com.eyeem.notes.model.Note; 4 | 5 | /** 6 | * Created by vishna on 18/02/15. 7 | */ 8 | public class NoteClickedEvent { 9 | public Note note; 10 | 11 | public NoteClickedEvent(Note note) { 12 | this.note = note; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /notes/src/main/java/com/eyeem/notes/event/PreviewRefresh.java: -------------------------------------------------------------------------------- 1 | package com.eyeem.notes.event; 2 | 3 | /** 4 | * Created by vishna on 19/02/15. 5 | */ 6 | public class PreviewRefresh {} 7 | -------------------------------------------------------------------------------- /notes/src/main/java/com/eyeem/notes/event/TextSnapshotCaptured.java: -------------------------------------------------------------------------------- 1 | package com.eyeem.notes.event; 2 | 3 | import android.text.SpannableString; 4 | 5 | import lombok.Getter; 6 | 7 | /** 8 | * Created by vishna on 19/02/15. 9 | */ 10 | public class TextSnapshotCaptured { 11 | @Getter SpannableString snapshot; 12 | 13 | public TextSnapshotCaptured(SpannableString snapshot) { 14 | this.snapshot = snapshot; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /notes/src/main/java/com/eyeem/notes/experimental/CacheOnScroll.java: -------------------------------------------------------------------------------- 1 | package com.eyeem.notes.experimental; 2 | 3 | import android.support.v4.util.LruCache; 4 | import android.support.v7.widget.RecyclerView; 5 | import android.view.View; 6 | import android.widget.AbsListView; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | import rx.Observable; 12 | import rx.Scheduler; 13 | import rx.Subscriber; 14 | import rx.Subscription; 15 | import rx.android.schedulers.AndroidSchedulers; 16 | import rx.schedulers.Schedulers; 17 | 18 | /** 19 | * Created by vishna on 06/02/15. 20 | */ 21 | public class CacheOnScroll extends RecyclerView.OnScrollListener { 22 | 23 | LruCache cache; 24 | 25 | /** 26 | * number of items that are not yet visible but should be 27 | * prepared by this class for a likely display 28 | */ 29 | int aheadCount; 30 | 31 | PausableThreadPoolExecutor executor; 32 | Scheduler scheduler; 33 | AheadLoader aheadLoader; 34 | 35 | public CacheOnScroll(PausableThreadPoolExecutor executor, int aheadCount) { 36 | this.executor = executor; 37 | this.aheadCount = aheadCount; 38 | this.cache = new LruCache<>(aheadCount); 39 | } 40 | 41 | public CacheOnScroll setAheadLoader(AheadLoader aheadLoader) { 42 | this.aheadLoader = aheadLoader; 43 | return this; 44 | } 45 | 46 | private int previousScrollState = AbsListView.OnScrollListener.SCROLL_STATE_IDLE; 47 | private boolean scrollingFirstTime = true; 48 | 49 | @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { 50 | 51 | int visibleItemCount = recyclerView.getChildCount(); 52 | int totalItemCount = recyclerView.getLayoutManager().getItemCount(); 53 | if (visibleItemCount == 0) return; 54 | View firstChild = recyclerView.getChildAt(0); 55 | View lastChild = recyclerView.getChildAt(visibleItemCount - 1); 56 | int firstVisibleItemPosition = recyclerView.getChildPosition(firstChild); 57 | int lastVisibleItemPosition = recyclerView.getChildPosition(lastChild); 58 | 59 | int middleVisibleItemPosition = (firstVisibleItemPosition + lastVisibleItemPosition)/2; 60 | 61 | if (scrollingFirstTime) { 62 | executor.resume(); 63 | scrollingFirstTime = false; 64 | checkCacheDisplacement(middleVisibleItemPosition); 65 | } 66 | 67 | if (!isScrolling(newState) && isScrolling(previousScrollState)) { 68 | executor.resume(); 69 | checkCacheDisplacement(middleVisibleItemPosition); 70 | } 71 | 72 | if (isScrolling(newState) && !isScrolling(previousScrollState)) { 73 | executor.pause(); 74 | cancelCacheCheck(); 75 | } 76 | 77 | previousScrollState = newState; 78 | } 79 | 80 | protected boolean isScrolling(int scrollState) { 81 | return scrollState == AbsListView.OnScrollListener.SCROLL_STATE_FLING; 82 | } 83 | 84 | private Scheduler scheduler() { 85 | if (scheduler == null) { 86 | scheduler = Schedulers.from(executor); 87 | } 88 | return scheduler; 89 | } 90 | 91 | public interface AheadLoader { 92 | /** 93 | * Builds a Type instance for a given id 94 | * @param id 95 | * @return 96 | */ 97 | Type buildFor(String id); 98 | 99 | /** 100 | * Provides ids of surrounding items 101 | * @param position 102 | * @param radius 103 | * @return 104 | */ 105 | List idsAround(int position, int radius); 106 | } 107 | 108 | public Observable get(final String id) { 109 | final Type cachedType = cache.get(id); 110 | 111 | // TODO weakreference this clojure 112 | Observable typeObservable = Observable.create((Subscriber subscriber) -> { 113 | 114 | if (!subscriber.isUnsubscribed() && cachedType != null) { 115 | subscriber.onNext(cachedType); 116 | return; 117 | } 118 | 119 | if (subscriber.isUnsubscribed()) return; 120 | 121 | Type uncachedType = aheadLoader != null ? aheadLoader.buildFor(id) : null; 122 | 123 | if (uncachedType != null) cache.put(id, uncachedType); 124 | 125 | if (!subscriber.isUnsubscribed()) { 126 | subscriber.onNext(uncachedType); 127 | } 128 | }) 129 | .subscribeOn(cachedType == null ? scheduler() : AndroidSchedulers.mainThread()); 130 | 131 | return typeObservable; 132 | } 133 | 134 | private Subscription checkCacheDisplacementSubscription; 135 | 136 | private void cancelCacheCheck() { 137 | if (checkCacheDisplacementSubscription != null) { 138 | checkCacheDisplacementSubscription.unsubscribe(); 139 | checkCacheDisplacementSubscription = null; 140 | } 141 | } 142 | 143 | private void checkCacheDisplacement(int middlePosition) { 144 | if (aheadLoader == null) return; 145 | cancelCacheCheck(); 146 | 147 | List ids = aheadLoader.idsAround(middlePosition, aheadCount/2 - 1); 148 | 149 | // check number of items already available in cache 150 | List> observables = new ArrayList<>(); 151 | for (final String id : ids) { 152 | if (cache.get(id) == null) { 153 | observables.add(get(id).subscribeOn(scheduler()).observeOn(scheduler())); 154 | } 155 | } 156 | 157 | checkCacheDisplacementSubscription = Observable.merge(observables).subscribeOn(scheduler()).observeOn(scheduler()).subscribe(); 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /notes/src/main/java/com/eyeem/notes/experimental/PausableThreadPoolExecutor.java: -------------------------------------------------------------------------------- 1 | package com.eyeem.notes.experimental; 2 | 3 | import java.util.concurrent.BlockingQueue; 4 | import java.util.concurrent.RejectedExecutionHandler; 5 | import java.util.concurrent.ThreadFactory; 6 | import java.util.concurrent.ThreadPoolExecutor; 7 | import java.util.concurrent.TimeUnit; 8 | import java.util.concurrent.locks.Condition; 9 | import java.util.concurrent.locks.ReentrantLock; 10 | 11 | /** 12 | * Created by vishna on 06/02/15. 13 | */ 14 | public class PausableThreadPoolExecutor extends ThreadPoolExecutor { 15 | private boolean isPaused; 16 | private ReentrantLock pauseLock = new ReentrantLock(); 17 | private Condition unpaused = pauseLock.newCondition(); 18 | 19 | public PausableThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue) { 20 | super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue); 21 | } 22 | 23 | public PausableThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, ThreadFactory threadFactory) { 24 | super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory); 25 | } 26 | 27 | public PausableThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, RejectedExecutionHandler handler) { 28 | super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler); 29 | } 30 | 31 | public PausableThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) { 32 | super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler); 33 | } 34 | 35 | protected void beforeExecute(Thread t, Runnable r) { 36 | super.beforeExecute(t, r); 37 | pauseLock.lock(); 38 | try { 39 | while (isPaused) unpaused.await(); 40 | } catch (InterruptedException ie) { 41 | t.interrupt(); 42 | } finally { 43 | pauseLock.unlock(); 44 | } 45 | } 46 | 47 | public void pause() { 48 | pauseLock.lock(); 49 | try { 50 | isPaused = true; 51 | } finally { 52 | pauseLock.unlock(); 53 | } 54 | } 55 | 56 | public void resume() { 57 | pauseLock.lock(); 58 | try { 59 | isPaused = false; 60 | unpaused.signalAll(); 61 | } finally { 62 | pauseLock.unlock(); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /notes/src/main/java/com/eyeem/notes/model/Note.java: -------------------------------------------------------------------------------- 1 | package com.eyeem.notes.model; 2 | 3 | import android.content.Context; 4 | import android.text.SpannableString; 5 | import android.text.SpannableStringBuilder; 6 | import android.text.TextUtils; 7 | 8 | import com.eyeem.chips.BubbleSpan; 9 | import com.eyeem.chips.BubbleStyle; 10 | import com.eyeem.chips.ChipsEditText; 11 | import com.eyeem.chips.Linkify; 12 | import com.eyeem.chips.Utils; 13 | import com.eyeem.notes.R; 14 | import com.google.gson.annotations.SerializedName; 15 | 16 | import java.util.HashMap; 17 | 18 | /** 19 | * Created by vishna on 03/02/15. 20 | */ 21 | public class Note { 22 | 23 | @SerializedName("id") 24 | public String id; 25 | 26 | @SerializedName("text") 27 | public String text; 28 | 29 | @SerializedName("entities") 30 | public Linkify.Entities entities; 31 | 32 | public Note() {} 33 | 34 | public Note(String id, String text, Linkify.Entities entities) { 35 | this.id = id; 36 | this.text = text; 37 | this.entities = entities; 38 | } 39 | 40 | public SpannableStringBuilder textSpan(BubbleStyle style, ChipsEditText et) { 41 | boolean isEmpty = TextUtils.isEmpty(text); 42 | SpannableStringBuilder ssb = new SpannableStringBuilder( 43 | isEmpty ? "" : text 44 | ); 45 | 46 | int width = 0; // FIXME not so great 47 | if (entities == null) 48 | return ssb; 49 | for (Linkify.Entity entity : entities) { 50 | switch (entity.type) { 51 | case Linkify.Entity.ALBUM: 52 | case Linkify.Entity.VENUE: 53 | case Linkify.Entity.CITY: 54 | case Linkify.Entity.COUNTRY: 55 | case Linkify.Entity.PERSON: 56 | case Linkify.Entity.EMAIL: 57 | case Linkify.Entity.MENTION: 58 | case Linkify.Entity.URL: 59 | case Linkify.Entity.UNKNOWN: 60 | com.eyeem.chips.Utils.bubblify(ssb, entity.text, entity.start, entity.end, 61 | width, style, et, entity); 62 | break; 63 | default: 64 | // NOOP 65 | } 66 | } 67 | return ssb; 68 | } 69 | 70 | public static Note from(String id, SpannableString ss) { 71 | Note out = new Note(); 72 | out.id = id; 73 | out.entities = new Linkify.Entities(); 74 | 75 | HashMap, Utils.FlatteningFactory> factories = new HashMap<>(); 76 | factories.put(BubbleSpan.class, new Note.Flatten(out.entities)); 77 | out.text = Utils.flatten(ss, factories); 78 | 79 | return out; 80 | } 81 | 82 | public static BubbleStyle defaultBubbleStyle(Context context, float textSize) { 83 | BubbleStyle bubbleStyle = BubbleStyle.build(context, R.style.note_default_style); 84 | bubbleStyle.setTextSize((int) textSize); 85 | return bubbleStyle; 86 | } 87 | 88 | private static class Flatten implements Utils.FlatteningFactory { 89 | 90 | Linkify.Entities entities; 91 | 92 | Flatten(Linkify.Entities entities) { 93 | this.entities = entities; 94 | } 95 | 96 | @Override public String out(String in, Object span) { 97 | return in == null ? null : in.trim(); 98 | } 99 | 100 | @Override public void afterReplaced(int start, int end, String replacementText, Object span) { 101 | Linkify.Entity entity = new Linkify.Entity(); 102 | 103 | if (!(span instanceof BubbleSpan)) return; 104 | 105 | BubbleSpan bubbleSpan = (BubbleSpan) span; 106 | 107 | entity.start = start; 108 | entity.end = end; 109 | if (bubbleSpan.data() instanceof Linkify.Entity) { 110 | Linkify.Entity oldEntity = (Linkify.Entity) bubbleSpan.data(); 111 | entity.text = oldEntity.text; 112 | entity.id = oldEntity.id; 113 | entity.type = oldEntity.type; 114 | } else { 115 | entity.text = replacementText; 116 | entity.id = String.valueOf(System.currentTimeMillis()); 117 | entity.type = Linkify.Entity.UNKNOWN; 118 | } 119 | 120 | entities.add(entity); 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /notes/src/main/java/com/eyeem/notes/mortarflow/Ass.java: -------------------------------------------------------------------------------- 1 | package com.eyeem.notes.mortarflow; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | import android.util.SparseArray; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | 9 | /** 10 | * Created by vishna on 17/04/15. 11 | */ 12 | public class Ass { 13 | 14 | public static Parcelable onSave(ViewGroup view, Parcelable superState) { 15 | SavedState ss = new SavedState(superState); 16 | ss.childrenStates = new SparseArray(); 17 | for (int i = 0; i < view.getChildCount(); i++) { 18 | view.getChildAt(i).saveHierarchyState(ss.childrenStates); 19 | } 20 | return ss; 21 | } 22 | 23 | public static Parcelable onLoad(ViewGroup view, Parcelable state) { 24 | SavedState ss = (SavedState) state; 25 | for (int i = 0; i < view.getChildCount(); i++) { 26 | view.getChildAt(i).restoreHierarchyState(ss.childrenStates); 27 | } 28 | return ss.getSuperState(); 29 | } 30 | 31 | static class SavedState extends View.BaseSavedState { 32 | SparseArray childrenStates; 33 | 34 | SavedState(Parcelable superState) { 35 | super(superState); 36 | } 37 | 38 | private SavedState(Parcel in, ClassLoader classLoader) { 39 | super(in); 40 | childrenStates = in.readSparseArray(classLoader); 41 | } 42 | 43 | @Override 44 | public void writeToParcel(Parcel out, int flags) { 45 | super.writeToParcel(out, flags); 46 | out.writeSparseArray(childrenStates); 47 | } 48 | 49 | public static final ClassLoaderCreator CREATOR = new ClassLoaderCreator() { 50 | @Override 51 | public SavedState createFromParcel(Parcel source, ClassLoader loader) { 52 | return new SavedState(source, loader); 53 | } 54 | 55 | @Override 56 | public SavedState createFromParcel(Parcel source) { 57 | return createFromParcel(null); 58 | } 59 | 60 | public SavedState[] newArray(int size) { 61 | return new SavedState[size]; 62 | } 63 | }; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /notes/src/main/java/com/eyeem/notes/mortarflow/BaseComponent.java: -------------------------------------------------------------------------------- 1 | package com.eyeem.notes.mortarflow; 2 | 3 | import com.eyeem.notes.core.AppDep; 4 | import com.eyeem.notes.core.StorageDep; 5 | 6 | /** 7 | * Created by vishna on 15/04/15. 8 | */ 9 | public interface BaseComponent extends FlowDep, AppDep, StorageDep { 10 | } 11 | -------------------------------------------------------------------------------- /notes/src/main/java/com/eyeem/notes/mortarflow/DynamicModules.java: -------------------------------------------------------------------------------- 1 | package com.eyeem.notes.mortarflow; 2 | 3 | import java.util.List; 4 | 5 | public interface DynamicModules { 6 | List dependencies(); 7 | } 8 | -------------------------------------------------------------------------------- /notes/src/main/java/com/eyeem/notes/mortarflow/FlowBundler.java: -------------------------------------------------------------------------------- 1 | package com.eyeem.notes.mortarflow; 2 | 3 | /* 4 | * Copyright 2014 Square Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | 20 | import android.os.Bundle; 21 | import android.support.annotation.Nullable; 22 | 23 | import flow.Flow; 24 | import flow.History; 25 | import flow.StateParceler; 26 | 27 | 28 | /** 29 | * Handles Bundle persistence of a Flow. 30 | */ 31 | public abstract class FlowBundler { 32 | private static final String FLOW_KEY = "flow_key"; 33 | 34 | private final StateParceler parceler; 35 | 36 | private Flow flow; 37 | 38 | protected FlowBundler(StateParceler parceler) { 39 | this.parceler = parceler; 40 | } 41 | 42 | public Flow onCreate(@Nullable Bundle savedInstanceState) { 43 | if (flow != null) return flow; 44 | 45 | History restoredHistory = null; 46 | if (savedInstanceState != null && savedInstanceState.containsKey(FLOW_KEY)) { 47 | restoredHistory = History.from(savedInstanceState.getParcelable(FLOW_KEY), parceler); 48 | } 49 | flow = new Flow(getColdStartHistory(restoredHistory)); 50 | return flow; 51 | } 52 | 53 | public void onSaveInstanceState(Bundle outState) { 54 | History history = getHistoryToSave(flow.getHistory()); 55 | if (history == null) return; 56 | outState.putParcelable(FLOW_KEY, history.getParcelable(parceler)); 57 | } 58 | 59 | /** 60 | * Returns the history that should be archived by {@link #onSaveInstanceState}. Overriding 61 | * allows subclasses to handle cases where the current configuration is not one that should 62 | * survive process death. The default implementation returns a HistoryToSave that specifies 63 | * that view state should be persisted. 64 | * 65 | * @return the stack to archive, or null to archive nothing 66 | */ 67 | @Nullable protected History getHistoryToSave(History history) { 68 | return history; 69 | } 70 | 71 | /** 72 | * Returns the history to initialize the new flow. 73 | * 74 | * @param restoredHistory the backstack recovered from the bundle passed to {@link #onCreate}, 75 | * or null if there was no bundle or no backstack was found 76 | */ 77 | protected abstract History getColdStartHistory(@Nullable History restoredHistory); 78 | } 79 | -------------------------------------------------------------------------------- /notes/src/main/java/com/eyeem/notes/mortarflow/FlowDep.java: -------------------------------------------------------------------------------- 1 | package com.eyeem.notes.mortarflow; 2 | 3 | /** 4 | * Created by vishna on 30/01/15. 5 | */ 6 | public interface FlowDep { 7 | FlowBundler provideFlowBundler(); 8 | // App provideApp(); 9 | // Picasso providePicasso(); 10 | // PausableThreadPoolExecutor providePausableThreadPool(); 11 | } 12 | -------------------------------------------------------------------------------- /notes/src/main/java/com/eyeem/notes/mortarflow/FramePathContainerView.java: -------------------------------------------------------------------------------- 1 | package com.eyeem.notes.mortarflow; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.view.ViewGroup; 6 | import android.widget.FrameLayout; 7 | 8 | import com.eyeem.notes.R; 9 | 10 | import flow.Flow; 11 | import flow.path.Path; 12 | import flow.path.PathContainer; 13 | import flow.path.PathContainerView; 14 | 15 | /** 16 | * Created by vishna on 29/01/15. 17 | */ 18 | public class FramePathContainerView extends FrameLayout implements HandlesBack, HandlesUp, PathContainerView { 19 | 20 | private final PathContainer container; 21 | private boolean disabled; 22 | 23 | public FramePathContainerView(Context context, AttributeSet attrs) { 24 | super(context, attrs); 25 | container = new SimplePathContainer(R.id.main_content_tag, Path.contextFactory(new MortarContextFactory())); 26 | } 27 | 28 | @Override public ViewGroup getContainerView() { 29 | return this; 30 | } 31 | 32 | @Override public void dispatch(Flow.Traversal traversal, final Flow.TraversalCallback callback) { 33 | disabled = true; 34 | container.executeTraversal(this, traversal, new Flow.TraversalCallback() { 35 | @Override public void onTraversalCompleted() { 36 | callback.onTraversalCompleted(); 37 | disabled = false; 38 | } 39 | }); 40 | } 41 | 42 | @Override public boolean onUpPressed() { 43 | return UpAndBack.onUpPressed(getCurrentChild()); 44 | } 45 | 46 | @Override public boolean onBackPressed() { 47 | return UpAndBack.onBackPressed(getCurrentChild()); 48 | } 49 | 50 | @Override public ViewGroup getCurrentChild() { 51 | return (ViewGroup) getContainerView().getChildAt(0); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /notes/src/main/java/com/eyeem/notes/mortarflow/GsonParceler.java: -------------------------------------------------------------------------------- 1 | package com.eyeem.notes.mortarflow; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | import com.google.gson.Gson; 6 | import com.google.gson.stream.JsonReader; 7 | import com.google.gson.stream.JsonWriter; 8 | import flow.path.Path; 9 | import flow.StateParceler; 10 | 11 | import java.io.IOException; 12 | import java.io.StringReader; 13 | import java.io.StringWriter; 14 | 15 | public class GsonParceler implements StateParceler { 16 | private final Gson gson; 17 | 18 | public GsonParceler(Gson gson) { 19 | this.gson = gson; 20 | } 21 | 22 | @Override public Parcelable wrap(Object instance) { 23 | try { 24 | String json = encode(instance); 25 | return new Wrapper(json); 26 | } catch (IOException e) { 27 | throw new RuntimeException(e); 28 | } 29 | } 30 | 31 | @Override public Path unwrap(Parcelable parcelable) { 32 | Wrapper wrapper = (Wrapper) parcelable; 33 | try { 34 | return decode(wrapper.json); 35 | } catch (IOException e) { 36 | throw new RuntimeException(e); 37 | } 38 | } 39 | 40 | private String encode(Object instance) throws IOException { 41 | StringWriter stringWriter = new StringWriter(); 42 | JsonWriter writer = new JsonWriter(stringWriter); 43 | 44 | try { 45 | Class type = instance.getClass(); 46 | 47 | writer.beginObject(); 48 | writer.name(type.getName()); 49 | gson.toJson(instance, type, writer); 50 | writer.endObject(); 51 | 52 | return stringWriter.toString(); 53 | } finally { 54 | writer.close(); 55 | } 56 | } 57 | 58 | private Path decode(String json) throws IOException { 59 | JsonReader reader = new JsonReader(new StringReader(json)); 60 | 61 | try { 62 | reader.beginObject(); 63 | 64 | Class type = Class.forName(reader.nextName()); 65 | return gson.fromJson(reader, type); 66 | } catch (ClassNotFoundException e) { 67 | throw new RuntimeException(e); 68 | } finally { 69 | reader.close(); 70 | } 71 | } 72 | 73 | private static class Wrapper implements Parcelable { 74 | final String json; 75 | 76 | Wrapper(String json) { 77 | this.json = json; 78 | } 79 | 80 | @Override public int describeContents() { 81 | return 0; 82 | } 83 | 84 | @Override public void writeToParcel(Parcel out, int flags) { 85 | out.writeString(json); 86 | } 87 | 88 | public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { 89 | @Override public Wrapper createFromParcel(Parcel in) { 90 | String json = in.readString(); 91 | return new Wrapper(json); 92 | } 93 | 94 | @Override public Wrapper[] newArray(int size) { 95 | return new Wrapper[size]; 96 | } 97 | }; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /notes/src/main/java/com/eyeem/notes/mortarflow/HandlesBack.java: -------------------------------------------------------------------------------- 1 | package com.eyeem.notes.mortarflow; 2 | 3 | /** 4 | * Implemented by views that want the option to intercept back button taps. If a view has subviews 5 | * that implement this interface their {@link #onBackPressed()} method should be invoked before 6 | * any of this view's own logic. 7 | *

8 | * 9 | * The typical flow of back button handling starts in the {@link android.app.Activity#onBackPressed()} 10 | * calling {@link #onBackPressed()} on its content view. Each view in turn delegates to its 11 | * child views to give them first say. 12 | */ 13 | public interface HandlesBack { 14 | /** 15 | * Returns true if back event was handled, false if someone higher in 16 | * the chain should. 17 | */ 18 | boolean onBackPressed(); 19 | } 20 | -------------------------------------------------------------------------------- /notes/src/main/java/com/eyeem/notes/mortarflow/HandlesUp.java: -------------------------------------------------------------------------------- 1 | package com.eyeem.notes.mortarflow; 2 | 3 | /** Like {@link HandlesBack}, but for the action bar's up button. */ 4 | public interface HandlesUp { 5 | boolean onUpPressed(); 6 | } -------------------------------------------------------------------------------- /notes/src/main/java/com/eyeem/notes/mortarflow/HasParent.java: -------------------------------------------------------------------------------- 1 | package com.eyeem.notes.mortarflow; 2 | 3 | import flow.path.Path; 4 | 5 | /** 6 | * Created by vishna on 20/05/15. 7 | */ 8 | public interface HasParent { 9 | public Path getParent(); 10 | } 11 | -------------------------------------------------------------------------------- /notes/src/main/java/com/eyeem/notes/mortarflow/Layout.java: -------------------------------------------------------------------------------- 1 | package com.eyeem.notes.mortarflow; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.Target; 5 | 6 | import static java.lang.annotation.ElementType.TYPE; 7 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 8 | 9 | // copied from FLOW 10 | 11 | /** 12 | * Marks a class that designates a screen and specifies its layout. A screen is a distinct part of 13 | * an application containing all information that describes this state. 14 | * 15 | *

For example,


16 | * {@literal@}Layout(R.layout.conversation_screen_layout)
17 | * public class ConversationScreen { ... }
18 | * 
19 | */ 20 | @Retention(RUNTIME) @Target(TYPE) 21 | public @interface Layout { 22 | int value(); 23 | } -------------------------------------------------------------------------------- /notes/src/main/java/com/eyeem/notes/mortarflow/MortarContextFactory.java: -------------------------------------------------------------------------------- 1 | package com.eyeem.notes.mortarflow; 2 | 3 | 4 | import android.content.Context; 5 | import android.content.ContextWrapper; 6 | import android.view.LayoutInflater; 7 | 8 | import flow.path.Path; 9 | import flow.path.PathContextFactory; 10 | import mortar.MortarScope; 11 | 12 | import static mortar.MortarScope.getScope; 13 | 14 | public final class MortarContextFactory implements PathContextFactory { 15 | private final ScreenScoper screenScoper = new ScreenScoper(); 16 | 17 | public MortarContextFactory() { 18 | } 19 | 20 | @Override public Context setUpContext(Path path, Context parentContext) { 21 | MortarScope screenScope = 22 | screenScoper.getScreenScope(parentContext, path.getClass().getName(), path); 23 | return new TearDownContext(parentContext, screenScope); 24 | } 25 | 26 | @Override public void tearDownContext(Context context) { 27 | TearDownContext.destroyScope(context); 28 | } 29 | 30 | static class TearDownContext extends ContextWrapper { 31 | private static final String SERVICE = "SNEAKY_MORTAR_PARENT_HOOK"; 32 | private final MortarScope parentScope; 33 | private LayoutInflater inflater; 34 | 35 | static void destroyScope(Context context) { 36 | MortarScope child = getScope(context); 37 | child.destroy(); 38 | } 39 | 40 | public TearDownContext(Context context, MortarScope scope) { 41 | super(scope.createContext(context)); 42 | this.parentScope = getScope(context); 43 | } 44 | 45 | @Override public Object getSystemService(String name) { 46 | if (LAYOUT_INFLATER_SERVICE.equals(name)) { 47 | if (inflater == null) { 48 | inflater = LayoutInflater.from(getBaseContext()).cloneInContext(this); 49 | } 50 | return inflater; 51 | } 52 | 53 | if (SERVICE.equals(name)) { 54 | return parentScope; 55 | } 56 | 57 | return super.getSystemService(name); 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /notes/src/main/java/com/eyeem/notes/mortarflow/ScopeSingleton.java: -------------------------------------------------------------------------------- 1 | package com.eyeem.notes.mortarflow; 2 | 3 | import javax.inject.Scope; 4 | 5 | @Scope 6 | public @interface ScopeSingleton { 7 | Class value(); 8 | } 9 | -------------------------------------------------------------------------------- /notes/src/main/java/com/eyeem/notes/mortarflow/ScreenScoper.java: -------------------------------------------------------------------------------- 1 | package com.eyeem.notes.mortarflow; 2 | 3 | 4 | import android.content.Context; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | import mortar.MortarScope; 10 | 11 | import static com.eyeem.notes.mortarflow.Utils.DAGGER_SERVICE; 12 | import static com.eyeem.notes.mortarflow.Utils.createComponent; 13 | import static mortar.MortarScope.findChild; 14 | import static mortar.MortarScope.getScope; 15 | 16 | /** 17 | * Creates {@link MortarScope}s for screens that may be annotated with {@link WithComponent}, 18 | * {@link WithComponent}. 19 | */ 20 | public class ScreenScoper { 21 | 22 | public MortarScope getScreenScope(Context context, String name, Object screen) { 23 | MortarScope parentScope = getScope(context); 24 | return getScreenScope(context, parentScope, name, screen); 25 | } 26 | 27 | /** 28 | * Finds or creates the scope for the given screen, honoring its optional {@link 29 | * DynamicModules} or {@link WithComponent} annotation. Note that scopes are also created 30 | * for unannotated screens. 31 | */ 32 | public MortarScope getScreenScope(Context context, MortarScope parentScope, final String name, 33 | final Object screen) { 34 | 35 | Object parentComponent = parentScope.getService(DAGGER_SERVICE); 36 | MortarScope childScope = findChild(context, name); 37 | 38 | if (childScope == null) { 39 | WithComponent withComponent = screen.getClass().getAnnotation(WithComponent.class); 40 | List dependencies = new ArrayList<>(); 41 | dependencies.add(parentComponent); 42 | 43 | if (screen instanceof DynamicModules) { 44 | dependencies.addAll(((DynamicModules) screen).dependencies()); 45 | } 46 | 47 | Object[] dependenciesArray = new Object[dependencies.size()]; 48 | dependencies.toArray(dependenciesArray); 49 | 50 | childScope = parentScope.buildChild() 51 | .withService(DAGGER_SERVICE, createComponent(withComponent.value(), dependenciesArray)) 52 | .build(name); 53 | } 54 | return childScope; 55 | } 56 | 57 | 58 | } 59 | -------------------------------------------------------------------------------- /notes/src/main/java/com/eyeem/notes/mortarflow/SimplePathContainer.java: -------------------------------------------------------------------------------- 1 | package com.eyeem.notes.mortarflow; 2 | 3 | /* 4 | * Copyright 2014 Square Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | 20 | import android.animation.Animator; 21 | import android.animation.AnimatorListenerAdapter; 22 | import android.animation.AnimatorSet; 23 | import android.animation.ObjectAnimator; 24 | import android.view.LayoutInflater; 25 | import android.view.View; 26 | import android.view.ViewGroup; 27 | 28 | import flow.Flow; 29 | import flow.path.Path; 30 | import flow.path.PathContainer; 31 | import flow.path.PathContext; 32 | import flow.path.PathContextFactory; 33 | 34 | import static com.eyeem.notes.mortarflow.Utils.getLayout; 35 | import static flow.Flow.Direction.REPLACE; 36 | 37 | /** 38 | * Provides basic right-to-left transitions. Saves and restores view state. 39 | * Uses {@link flow.path.PathContext} to allow customized sub-containers. 40 | */ 41 | public class SimplePathContainer extends PathContainer { 42 | private final PathContextFactory contextFactory; 43 | 44 | public SimplePathContainer(int tagKey, PathContextFactory contextFactory) { 45 | super(tagKey); 46 | this.contextFactory = contextFactory; 47 | } 48 | 49 | @Override protected void performTraversal(final ViewGroup containerView, 50 | final TraversalState traversalState, final Flow.Direction direction, 51 | final Flow.TraversalCallback callback) { 52 | 53 | final PathContext context; 54 | final PathContext oldPath; 55 | if (containerView.getChildCount() > 0) { 56 | oldPath = PathContext.get(containerView.getChildAt(0).getContext()); 57 | } else { 58 | oldPath = PathContext.root(containerView.getContext()); 59 | } 60 | 61 | Path to = traversalState.toPath(); 62 | 63 | ViewGroup newView; 64 | context = PathContext.create(oldPath, to, contextFactory); 65 | int layout = getLayout(to); 66 | newView = (ViewGroup) LayoutInflater.from(context) 67 | .cloneInContext(context) 68 | .inflate(layout, containerView, false); 69 | 70 | View fromView = null; 71 | if (traversalState.fromPath() != null) { 72 | fromView = containerView.getChildAt(0); 73 | traversalState.saveViewState(fromView); 74 | } 75 | traversalState.restoreViewState(newView); 76 | 77 | if (fromView == null || direction == REPLACE) { 78 | containerView.removeAllViews(); 79 | containerView.addView(newView); 80 | oldPath.destroyNotIn(context, contextFactory); 81 | callback.onTraversalCompleted(); 82 | } else { 83 | containerView.addView(newView); 84 | final View finalFromView = fromView; 85 | Utils.waitForMeasure(newView, new Utils.OnMeasuredCallback() { 86 | @Override public void onMeasured(View view, int width, int height) { 87 | runAnimation(containerView, finalFromView, view, direction, new Flow.TraversalCallback() { 88 | @Override public void onTraversalCompleted() { 89 | containerView.removeView(finalFromView); 90 | oldPath.destroyNotIn(context, contextFactory); 91 | callback.onTraversalCompleted(); 92 | } 93 | }); 94 | } 95 | }); 96 | } 97 | } 98 | 99 | private void runAnimation(final ViewGroup container, final View from, final View to, 100 | Flow.Direction direction, final Flow.TraversalCallback callback) { 101 | Animator animator = createSegue(from, to, direction); 102 | animator.addListener(new AnimatorListenerAdapter() { 103 | @Override public void onAnimationEnd(Animator animation) { 104 | container.removeView(from); 105 | callback.onTraversalCompleted(); 106 | } 107 | }); 108 | animator.start(); 109 | } 110 | 111 | private Animator createSegue(View from, View to, Flow.Direction direction) { 112 | boolean backward = direction == Flow.Direction.BACKWARD; 113 | int fromTranslation = backward ? from.getWidth() : -from.getWidth(); 114 | int toTranslation = backward ? -to.getWidth() : to.getWidth(); 115 | 116 | AnimatorSet set = new AnimatorSet(); 117 | 118 | set.play(ObjectAnimator.ofFloat(from, View.TRANSLATION_X, fromTranslation)); 119 | set.play(ObjectAnimator.ofFloat(to, View.TRANSLATION_X, toTranslation, 0)); 120 | 121 | return set; 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /notes/src/main/java/com/eyeem/notes/mortarflow/UpAndBack.java: -------------------------------------------------------------------------------- 1 | package com.eyeem.notes.mortarflow; 2 | 3 | import android.view.View; 4 | import flow.Flow; 5 | 6 | /** 7 | * Support for {@link HandlesUp} and {@link HandlesBack}. 8 | */ 9 | public class UpAndBack { 10 | public static boolean onUpPressed(View childView) { 11 | if (childView instanceof HandlesUp) { 12 | if (((HandlesUp) childView).onUpPressed()) { 13 | return true; 14 | } 15 | } 16 | // Try to go up. If up isn't supported, go back. 17 | return /*Flow.get(childView).goUp() || */onBackPressed(childView); 18 | } 19 | 20 | public static boolean onBackPressed(View childView) { 21 | if (childView instanceof HandlesBack) { 22 | if (((HandlesBack) childView).onBackPressed()) { 23 | return true; 24 | } 25 | } 26 | return Flow.get(childView).goBack(); 27 | } 28 | 29 | private UpAndBack() { 30 | } 31 | } -------------------------------------------------------------------------------- /notes/src/main/java/com/eyeem/notes/mortarflow/Utils.java: -------------------------------------------------------------------------------- 1 | package com.eyeem.notes.mortarflow; 2 | 3 | import android.content.Context; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewTreeObserver; 7 | 8 | import java.lang.reflect.Method; 9 | 10 | import flow.path.Path; 11 | 12 | /** 13 | * Created by vishna on 29/01/15. 14 | */ 15 | public class Utils { 16 | 17 | public final static String DAGGER_SERVICE = "DaggerService"; 18 | 19 | public interface OnMeasuredCallback { 20 | void onMeasured(View view, int width, int height); 21 | } 22 | 23 | public static void waitForMeasure(final View view, final OnMeasuredCallback callback) { 24 | int width = view.getWidth(); 25 | int height = view.getHeight(); 26 | 27 | if (width > 0 && height > 0) { 28 | callback.onMeasured(view, width, height); 29 | return; 30 | } 31 | 32 | view.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { 33 | @Override public boolean onPreDraw() { 34 | final ViewTreeObserver observer = view.getViewTreeObserver(); 35 | if (observer.isAlive()) { 36 | observer.removeOnPreDrawListener(this); 37 | } 38 | 39 | callback.onMeasured(view, view.getWidth(), view.getHeight()); 40 | 41 | return true; 42 | } 43 | }); 44 | } 45 | 46 | /** 47 | * Magic method that creates a component with its dependencies set, by reflection. Relies on 48 | * Dagger2 naming conventions. 49 | */ 50 | public static T createComponent(Class componentClass, Object... dependencies) { 51 | String fqn = componentClass.getName(); 52 | 53 | String packageName = componentClass.getPackage().getName(); 54 | // Accounts for inner classes, ie MyApplication$Component 55 | String simpleName = fqn.substring(packageName.length() + 1); 56 | String generatedName = (packageName + ".Dagger" + simpleName).replace('$', '_'); 57 | 58 | try { 59 | Class generatedClass = Class.forName(generatedName); 60 | Object builder = generatedClass.getMethod("builder").invoke(null); 61 | 62 | for (Method method : builder.getClass().getMethods()) { 63 | Class[] params = method.getParameterTypes(); 64 | if (params.length == 1) { 65 | Class dependencyClass = params[0]; 66 | for (Object dependency : dependencies) { 67 | if (dependencyClass.isAssignableFrom(dependency.getClass())) { 68 | method.invoke(builder, dependency); 69 | break; 70 | } 71 | } 72 | } 73 | } 74 | //noinspection unchecked 75 | return (T) builder.getClass().getMethod("build").invoke(builder); 76 | } catch (Exception e) { 77 | throw new RuntimeException(e); 78 | } 79 | } 80 | 81 | /** 82 | * Caller is required to know the type of the component for this context. 83 | */ 84 | @SuppressWarnings("unchecked") // 85 | public static T getDaggerComponent(Context context) { 86 | return (T) context.getSystemService(DAGGER_SERVICE); 87 | } 88 | 89 | public static int getLayout(Path path) { 90 | Layout layout = path.getClass().getAnnotation(Layout.class); 91 | return layout.value(); 92 | } 93 | 94 | public final static class Layouts { 95 | 96 | /** 97 | * Create an instance of the view specified in a {@link Layout} annotation. 98 | */ 99 | public static android.view.View createView(Context context, Object screen) { 100 | return createView(context, screen.getClass()); 101 | } 102 | 103 | /** 104 | * Create an instance of the view specified in a {@link Layout} annotation. 105 | */ 106 | public static android.view.View createView(Context context, Class screenType) { 107 | Layout screen = screenType.getAnnotation(Layout.class); 108 | if (screen == null) { 109 | throw new IllegalArgumentException( 110 | String.format("@%s annotation not found on class %s", Layout.class.getSimpleName(), 111 | screenType.getName())); 112 | } 113 | 114 | int layout = screen.value(); 115 | return inflateLayout(context, layout); 116 | } 117 | 118 | private static android.view.View inflateLayout(Context context, int layoutId) { 119 | return LayoutInflater.from(context).inflate(layoutId, null); 120 | } 121 | 122 | private Layouts() {} 123 | } 124 | 125 | private Utils() {} 126 | } 127 | -------------------------------------------------------------------------------- /notes/src/main/java/com/eyeem/notes/mortarflow/WithComponent.java: -------------------------------------------------------------------------------- 1 | package com.eyeem.notes.mortarflow; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) 9 | public @interface WithComponent { 10 | Class value(); 11 | } 12 | -------------------------------------------------------------------------------- /notes/src/main/java/com/eyeem/notes/screen/ActionBarOwner.java: -------------------------------------------------------------------------------- 1 | package com.eyeem.notes.screen; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | 6 | import com.eyeem.notes.MainActivity; 7 | import com.eyeem.notes.mortarflow.ScopeSingleton; 8 | 9 | import java.util.List; 10 | 11 | import javax.inject.Inject; 12 | 13 | import mortar.bundler.BundleService; 14 | 15 | import static mortar.bundler.BundleService.getBundleService; 16 | 17 | /** Allows shared configuration of the Android ActionBar. */ 18 | public class ActionBarOwner { 19 | 20 | public interface Activity { 21 | void setShowHomeEnabled(boolean enabled); 22 | 23 | void setUpButtonEnabled(boolean enabled); 24 | 25 | void setTitle(CharSequence title); 26 | 27 | void setMenu(List actions); 28 | 29 | Context getContext(); 30 | } 31 | 32 | public static class Config { 33 | public final boolean showHomeEnabled; 34 | public final boolean upButtonEnabled; 35 | public final CharSequence title; 36 | public final List actions; 37 | 38 | public Config(boolean showHomeEnabled, boolean upButtonEnabled, CharSequence title, 39 | List actions) { 40 | this.showHomeEnabled = showHomeEnabled; 41 | this.upButtonEnabled = upButtonEnabled; 42 | this.title = title; 43 | this.actions = actions; 44 | } 45 | 46 | public Config withAction(List actions) { 47 | return new Config(showHomeEnabled, upButtonEnabled, title, actions); 48 | } 49 | } 50 | 51 | public static class MenuAction { 52 | public final int stringId; 53 | 54 | public MenuAction(int stringId) { 55 | this.stringId = stringId; 56 | } 57 | } 58 | 59 | @ScopeSingleton(MainActivity.Component.class) 60 | public static class Presenter extends mortar.Presenter { 61 | 62 | @Inject Presenter() {} 63 | 64 | @Override protected BundleService extractBundleService(Activity activity) { 65 | return getBundleService(activity.getContext()); 66 | } 67 | 68 | private Config config; 69 | 70 | public void setConfig(Config config) { 71 | this.config = config; 72 | update(); 73 | } 74 | 75 | public Config getConfig() { 76 | return config; 77 | } 78 | 79 | @Override protected void onLoad(Bundle savedInstanceState) { 80 | if (config != null) update(); 81 | } 82 | 83 | private void update() { 84 | if (!hasView()) return; 85 | Activity activity = getView(); 86 | 87 | activity.setShowHomeEnabled(config.showHomeEnabled); 88 | activity.setUpButtonEnabled(config.upButtonEnabled); 89 | activity.setTitle(config.title); 90 | activity.setMenu(config.actions); 91 | } 92 | } 93 | 94 | public interface MenuActions { 95 | List menuActions(); 96 | } 97 | } -------------------------------------------------------------------------------- /notes/src/main/java/com/eyeem/notes/screen/Edit.java: -------------------------------------------------------------------------------- 1 | package com.eyeem.notes.screen; 2 | 3 | import android.os.Bundle; 4 | 5 | import com.eyeem.notes.R; 6 | import com.eyeem.notes.event.PreviewRefresh; 7 | import com.eyeem.notes.event.TextSnapshotCaptured; 8 | import com.eyeem.notes.mortarflow.HasParent; 9 | import com.eyeem.notes.mortarflow.Layout; 10 | import com.eyeem.notes.mortarflow.ScopeSingleton; 11 | import com.eyeem.notes.mortarflow.WithComponent; 12 | import com.eyeem.notes.view.EditView; 13 | import com.squareup.otto.Bus; 14 | import com.squareup.otto.Subscribe; 15 | 16 | import java.util.ArrayList; 17 | import java.util.List; 18 | 19 | import javax.inject.Inject; 20 | import javax.inject.Named; 21 | 22 | import dagger.Provides; 23 | import flow.path.Path; 24 | import mortar.MortarScope; 25 | import mortar.ViewPresenter; 26 | 27 | /** 28 | * Created by vishna on 27/01/15. 29 | */ 30 | @Layout(R.layout.edit) @WithComponent(Edit.Component.class) 31 | public class Edit extends Path implements HasParent { 32 | 33 | public final static String KEY_INSTANCE_STATE = Edit.class.getCanonicalName() + "." + "KEY_INSTANCE_STATE"; 34 | 35 | @Override public Path getParent() { 36 | return new Start(); 37 | } 38 | 39 | @dagger.Component(modules = Module.class, dependencies = Note.Component.class) 40 | @ScopeSingleton(Component.class) 41 | public interface Component { 42 | void inject(EditView t); 43 | } 44 | 45 | @dagger.Module 46 | public static class Module { 47 | @Provides List provideSuggestions() { 48 | final ArrayList availableItems = new ArrayList(); 49 | availableItems.add("Changing the world"); 50 | availableItems.add("Best startup ever"); 51 | availableItems.add("Lars but not least"); 52 | availableItems.add("My Ramzi"); 53 | availableItems.add("Walking around"); 54 | availableItems.add("Today's hot look"); 55 | availableItems.add("In other news"); 56 | availableItems.add("Screw that stuff"); 57 | availableItems.add("x"); 58 | availableItems.add("NADA NADA NADA"); 59 | availableItems.add("..."); 60 | availableItems.add("IPA"); 61 | availableItems.add("#flowers#nature#hangingout#takingphotos#colors#hello world#flora#fauna"); 62 | return availableItems; 63 | } 64 | } 65 | 66 | @ScopeSingleton(Component.class) 67 | public static class Presenter extends ViewPresenter { 68 | 69 | @Inject @Named("noteBus") Bus noteBus; 70 | 71 | @Inject Presenter() {} 72 | 73 | @Override protected void onLoad(Bundle savedInstanceState) { 74 | super.onLoad(savedInstanceState); 75 | if (savedInstanceState != null) getView().onRestoreInstanceState(savedInstanceState.getParcelable(KEY_INSTANCE_STATE)); 76 | } 77 | 78 | @Override protected void onSave(Bundle outState) { 79 | super.onSave(outState); 80 | outState.putParcelable(KEY_INSTANCE_STATE, getView().onSaveInstanceState()); 81 | } 82 | 83 | @Override protected void onEnterScope(MortarScope scope) { 84 | super.onEnterScope(scope); 85 | noteBus.register(this); 86 | } 87 | 88 | @Override protected void onExitScope() { 89 | super.onExitScope(); 90 | noteBus.unregister(this); 91 | } 92 | 93 | @Subscribe public void onPreviewSelected(PreviewRefresh previewSelected) { 94 | if (!hasView()) return; 95 | getView().getEt().hideKeyboard(); 96 | noteBus.post(new TextSnapshotCaptured(getView().getEt().snapshot())); 97 | } 98 | 99 | @Subscribe public void menuAction(ActionBarOwner.MenuAction menuAction) { 100 | if (menuAction.stringId == R.string.clear_note) { 101 | getView().getEt().getText().clear(); 102 | noteBus.post(new PreviewRefresh()); 103 | } 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /notes/src/main/java/com/eyeem/notes/screen/Note.java: -------------------------------------------------------------------------------- 1 | package com.eyeem.notes.screen; 2 | 3 | import android.text.TextUtils; 4 | 5 | import com.eyeem.notes.MainActivity; 6 | import com.eyeem.notes.R; 7 | import com.eyeem.notes.core.AppDep; 8 | import com.eyeem.notes.core.NoteStorage; 9 | import com.eyeem.notes.event.PreviewRefresh; 10 | import com.eyeem.notes.mortarflow.BaseComponent; 11 | import com.eyeem.notes.mortarflow.DynamicModules; 12 | import com.eyeem.notes.mortarflow.FlowDep; 13 | import com.eyeem.notes.mortarflow.HasParent; 14 | import com.eyeem.notes.mortarflow.Layout; 15 | import com.eyeem.notes.mortarflow.ScopeSingleton; 16 | import com.eyeem.notes.mortarflow.WithComponent; 17 | import com.eyeem.notes.view.NoteView; 18 | import com.squareup.otto.Bus; 19 | import com.squareup.otto.Subscribe; 20 | 21 | import java.util.Arrays; 22 | import java.util.List; 23 | 24 | import javax.inject.Inject; 25 | import javax.inject.Named; 26 | 27 | import dagger.Provides; 28 | import flow.path.Path; 29 | import mortar.MortarScope; 30 | import mortar.ViewPresenter; 31 | 32 | /** 33 | * Created by vishna on 16/02/15. 34 | */ 35 | @Layout(R.layout.note) @WithComponent(Note.Component.class) 36 | public class Note extends Path implements HasParent, DynamicModules, ActionBarOwner.MenuActions { 37 | 38 | Module module; 39 | 40 | public Note(NoteStorage.List list, String noteId) { 41 | this.module = new Module(list, noteId); 42 | } 43 | 44 | @Override public Path getParent() { 45 | return new Notes(module.list); 46 | } 47 | 48 | @Override public List dependencies() { 49 | return Arrays.asList(module); 50 | } 51 | 52 | @Override public List menuActions() { 53 | return Arrays.asList( 54 | new ActionBarOwner.MenuAction(R.string.clear_note), 55 | new ActionBarOwner.MenuAction(R.string.save_note), 56 | new ActionBarOwner.MenuAction(R.string.tag_setup) 57 | ); 58 | } 59 | 60 | @dagger.Component(modules = Module.class, dependencies = BaseComponent.class) 61 | @ScopeSingleton(Component.class) 62 | public interface Component extends FlowDep, AppDep { 63 | void inject(NoteView t); 64 | com.eyeem.notes.model.Note provideNote(); 65 | @Named("noteBus") Bus provideNoteBus(); 66 | } 67 | 68 | @dagger.Module public static class Module { 69 | 70 | NoteStorage.List list; 71 | String noteId; 72 | com.eyeem.notes.model.Note localNote; 73 | Bus noteBus; 74 | NoteStorage storage; 75 | 76 | public Module(NoteStorage.List list, String noteId) { 77 | this.list = list; 78 | this.noteId = noteId; 79 | this.noteBus = new Bus("noteBus"); 80 | } 81 | 82 | @Provides NoteStorage.List provideNoteList() { 83 | return list; 84 | } 85 | 86 | @Provides com.eyeem.notes.model.Note provideNote(NoteStorage storage) { 87 | if (localNote == null) { 88 | if (!TextUtils.isEmpty(noteId)) { 89 | localNote = storage.get(noteId); 90 | } else { 91 | localNote = new com.eyeem.notes.model.Note(); 92 | localNote.id = String.valueOf(System.currentTimeMillis()); 93 | } 94 | } 95 | return localNote; 96 | } 97 | 98 | @Provides @Named("noteBus") Bus provideNoteBus() { 99 | return noteBus; 100 | } 101 | } 102 | 103 | @ScopeSingleton(Component.class) public static class Presenter extends ViewPresenter { 104 | 105 | @Inject Bus bus; 106 | @Inject @Named("noteBus") Bus noteBus; 107 | @Inject Presenter() {} 108 | 109 | @Override protected void onEnterScope(MortarScope scope) { 110 | super.onEnterScope(scope); 111 | bus.register(this); 112 | } 113 | 114 | @Override protected void onExitScope() { 115 | super.onExitScope(); 116 | bus.unregister(this); 117 | } 118 | 119 | @Subscribe public void menuAction(ActionBarOwner.MenuAction menuAction) { 120 | if (!hasView()) return; 121 | 122 | if (menuAction.stringId == R.string.tag_setup && getView().getPager().getCurrentItem() != 1) { 123 | // force generate the preview if we are on a wrong page 124 | noteBus.post(new PreviewRefresh()); 125 | } 126 | 127 | noteBus.post(menuAction); 128 | } 129 | 130 | public void onPageSelected(int page) { 131 | if (page == 1) noteBus.post(new PreviewRefresh()); 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /notes/src/main/java/com/eyeem/notes/screen/Notes.java: -------------------------------------------------------------------------------- 1 | package com.eyeem.notes.screen; 2 | 3 | import android.os.Bundle; 4 | import android.view.MenuItem; 5 | import android.widget.Toast; 6 | 7 | import com.eyeem.chips.BubbleSpan; 8 | import com.eyeem.chips.LayoutBuild; 9 | import com.eyeem.notes.MainActivity; 10 | import com.eyeem.notes.R; 11 | import com.eyeem.notes.adapter.NotesAdapter; 12 | import com.eyeem.notes.core.BootstrapNotesModule; 13 | import com.eyeem.notes.core.NoteStorage; 14 | import com.eyeem.notes.event.BubbleClickedEvent; 15 | import com.eyeem.notes.event.NewNoteEvent; 16 | import com.eyeem.notes.event.NoteClickedEvent; 17 | import com.eyeem.notes.experimental.CacheOnScroll; 18 | import com.eyeem.notes.experimental.PausableThreadPoolExecutor; 19 | import com.eyeem.notes.model.Note; 20 | import com.eyeem.notes.mortarflow.DynamicModules; 21 | import com.eyeem.notes.mortarflow.Layout; 22 | import com.eyeem.notes.mortarflow.ScopeSingleton; 23 | import com.eyeem.notes.mortarflow.WithComponent; 24 | import com.eyeem.notes.view.NotesView; 25 | import com.squareup.otto.Bus; 26 | import com.squareup.otto.Subscribe; 27 | 28 | import java.util.Arrays; 29 | import java.util.List; 30 | 31 | import javax.inject.Inject; 32 | 33 | import dagger.Provides; 34 | import flow.Flow; 35 | import flow.path.Path; 36 | import mortar.MortarScope; 37 | import mortar.ViewPresenter; 38 | import rx.Observable; 39 | import rx.android.schedulers.AndroidSchedulers; 40 | import rx.functions.Action1; 41 | 42 | /** 43 | * Created by vishna on 03/02/15. 44 | */ 45 | @Layout(R.layout.notes) @WithComponent(Notes.Component.class) 46 | public class Notes extends Path implements DynamicModules, ActionBarOwner.MenuActions { 47 | 48 | NoteStorage.List list; 49 | 50 | public Notes(NoteStorage.List list) { 51 | this.list = list; 52 | } 53 | 54 | @Override public List dependencies() { 55 | return Arrays.asList(new Module(list)); 56 | } 57 | 58 | @Override public List menuActions() { 59 | return Arrays.asList( 60 | new ActionBarOwner.MenuAction(R.string.clear_all_notes) 61 | ); 62 | } 63 | 64 | @dagger.Component(modules = {Module.class, BootstrapNotesModule.class}, dependencies = MainActivity.Component.class) 65 | @ScopeSingleton(Component.class) 66 | public interface Component { 67 | void inject(NotesView t); 68 | } 69 | 70 | @dagger.Module 71 | public static class Module { 72 | 73 | NoteStorage.List list; 74 | 75 | public Module(NoteStorage.List list) { 76 | this.list = list; 77 | } 78 | 79 | @Provides NotesAdapter provideAdapter(CacheOnScroll cacheOnScroll, Bus bus) { 80 | return new NotesAdapter(list, cacheOnScroll, bus); 81 | } 82 | 83 | @Provides CacheOnScroll provideCacheOnScroll(PausableThreadPoolExecutor pausableThreadPoolExecutor) { 84 | return new CacheOnScroll<>(pausableThreadPoolExecutor, 100); 85 | } 86 | 87 | @Provides NoteStorage.List provideNoteStorageList() { 88 | return list; 89 | } 90 | } 91 | 92 | @ScopeSingleton(Component.class) 93 | public static class Presenter extends ViewPresenter { 94 | 95 | @Inject Observable> noteSource; 96 | @Inject NoteStorage.List noteList; 97 | @Inject Bus bus; 98 | 99 | @Inject Presenter() {} 100 | 101 | @Override protected void onLoad(Bundle savedInstanceState) { 102 | super.onLoad(savedInstanceState); 103 | if (!hasView()) return; 104 | 105 | noteSource 106 | .observeOn(AndroidSchedulers.mainThread()) 107 | .subscribe(new Action1>() { 108 | @Override public void call(List notes) { 109 | if (!hasView() || noteList.size() > 0) return; 110 | noteList.addAll(notes); 111 | } 112 | }); 113 | } 114 | 115 | @Override protected void onEnterScope(MortarScope scope) { 116 | super.onEnterScope(scope); 117 | bus.register(this); 118 | } 119 | 120 | @Override protected void onExitScope() { 121 | super.onExitScope(); 122 | bus.unregister(this); 123 | } 124 | 125 | @Subscribe public void noteClicked(NoteClickedEvent noteClickedEvent) { 126 | Note note = noteClickedEvent.note; 127 | if (hasView()) Flow.get(getView()).set(new com.eyeem.notes.screen.Note(noteList, note.id)); 128 | } 129 | 130 | @Subscribe public void bubbleClicked(BubbleClickedEvent bubbleClickedEvent) { 131 | BubbleSpan span = bubbleClickedEvent.bubbleSpan; 132 | // TODO some event handling links, emails and such 133 | Toast.makeText(getView().getContext(), span.data().toString(), Toast.LENGTH_SHORT).show(); 134 | } 135 | 136 | @Subscribe public void newNoteClicked(NewNoteEvent newNoteEvent) { 137 | Flow.get(getView().getContext()).set(new com.eyeem.notes.screen.Note(noteList, null)); 138 | } 139 | 140 | @Subscribe public void menuAction(ActionBarOwner.MenuAction action) { 141 | if (action.stringId == R.string.clear_all_notes) { 142 | noteList.clear(); 143 | } 144 | } 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /notes/src/main/java/com/eyeem/notes/screen/Preview.java: -------------------------------------------------------------------------------- 1 | package com.eyeem.notes.screen; 2 | 3 | import android.os.Bundle; 4 | import android.text.SpannableString; 5 | import android.widget.Toast; 6 | 7 | import com.eyeem.chips.BubbleStyle; 8 | import com.eyeem.chips.Utils; 9 | import com.eyeem.notes.R; 10 | import com.eyeem.notes.event.TextSnapshotCaptured; 11 | import com.eyeem.notes.mortarflow.HasParent; 12 | import com.eyeem.notes.mortarflow.Layout; 13 | import com.eyeem.notes.mortarflow.ScopeSingleton; 14 | import com.eyeem.notes.mortarflow.WithComponent; 15 | import com.eyeem.notes.view.PreviewView; 16 | import com.squareup.otto.Bus; 17 | import com.squareup.otto.Subscribe; 18 | 19 | import javax.inject.Inject; 20 | import javax.inject.Named; 21 | 22 | import flow.path.Path; 23 | import lombok.Getter; 24 | import mortar.MortarScope; 25 | import mortar.ViewPresenter; 26 | 27 | import static com.eyeem.notes.model.Note.from; 28 | 29 | /** 30 | * Created by vishna on 18/02/15. 31 | */ 32 | @Layout(R.layout.preview) @WithComponent(Preview.Component.class) 33 | public class Preview extends Path implements HasParent { 34 | 35 | public final static String KEY_INSTANCE_STATE = Preview.class.getCanonicalName() + "." + "KEY_INSTANCE_STATE"; 36 | 37 | @Override public Path getParent() { 38 | return new Start(); 39 | } 40 | 41 | @dagger.Component(modules = Module.class, dependencies = Note.Component.class) 42 | @ScopeSingleton(Component.class) 43 | public interface Component { 44 | void inject(PreviewView t); 45 | } 46 | 47 | @dagger.Module 48 | public static class Module { 49 | } 50 | 51 | @ScopeSingleton(Component.class) 52 | public static class Presenter extends ViewPresenter { 53 | 54 | @Inject @Named("noteBus") Bus noteBus; 55 | @Inject com.eyeem.notes.model.Note sourceNote; 56 | @Getter com.eyeem.notes.model.Note previewNote; 57 | 58 | @Inject Presenter() {} 59 | 60 | @Override protected void onLoad(Bundle savedInstanceState) { 61 | super.onLoad(savedInstanceState); 62 | if (savedInstanceState != null) getView().onRestoreInstanceState(savedInstanceState.getParcelable(KEY_INSTANCE_STATE)); 63 | } 64 | 65 | @Override protected void onSave(Bundle outState) { 66 | super.onSave(outState); 67 | outState.putParcelable(KEY_INSTANCE_STATE, getView().onSaveInstanceState()); 68 | } 69 | 70 | @Override protected void onEnterScope(MortarScope scope) { 71 | super.onEnterScope(scope); 72 | noteBus.register(this); 73 | } 74 | 75 | @Override protected void onExitScope() { 76 | super.onExitScope(); 77 | noteBus.unregister(this); 78 | } 79 | 80 | @Subscribe public void textSnapshotCaptured(TextSnapshotCaptured textSnapshotCaptured) { 81 | previewNote = from(sourceNote.id, textSnapshotCaptured.getSnapshot()); 82 | populateNote(); 83 | } 84 | 85 | @Subscribe public void menuAction(ActionBarOwner.MenuAction menuAction) { 86 | if (menuAction.stringId == R.string.tag_setup && hasView()) { 87 | Toast.makeText(getView().getContext(), Utils.tag_setup(new SpannableString(getView().getTv().getLayoutBuild().getSpannable())), Toast.LENGTH_LONG).show(); 88 | } 89 | } 90 | 91 | public void populateNote() { 92 | if (!hasView()) return; 93 | com.eyeem.notes.model.Note note = getPreviewNote(); 94 | if (note == null) { 95 | note = sourceNote; 96 | } 97 | if (note == null) return; 98 | BubbleStyle style = com.eyeem.notes.model.Note.defaultBubbleStyle(getView().getContext(), getView().getTv().getTextSize()); 99 | getView().getTv().setText(note.textSpan(style, null)); 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /notes/src/main/java/com/eyeem/notes/screen/Start.java: -------------------------------------------------------------------------------- 1 | package com.eyeem.notes.screen; 2 | 3 | import com.eyeem.notes.MainActivity; 4 | import com.eyeem.notes.R; 5 | import com.eyeem.notes.mortarflow.Layout; 6 | import com.eyeem.notes.mortarflow.ScopeSingleton; 7 | import com.eyeem.notes.mortarflow.WithComponent; 8 | import com.eyeem.notes.view.StartView; 9 | 10 | import javax.inject.Inject; 11 | 12 | import flow.path.Path; 13 | import mortar.ViewPresenter; 14 | 15 | @Layout(R.layout.start) @WithComponent(Start.Component.class) 16 | public class Start extends Path { 17 | 18 | @dagger.Component(dependencies = MainActivity.Component.class) 19 | @ScopeSingleton(Component.class) 20 | public interface Component { 21 | void inject(StartView view); 22 | } 23 | 24 | @ScopeSingleton(Component.class) 25 | public static class Presenter extends ViewPresenter { 26 | @Inject Presenter() {} 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /notes/src/main/java/com/eyeem/notes/utils/Assets.java: -------------------------------------------------------------------------------- 1 | package com.eyeem.notes.utils; 2 | 3 | import android.content.Context; 4 | 5 | import java.io.BufferedReader; 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | import java.io.InputStreamReader; 9 | 10 | import rx.Observable; 11 | import rx.functions.Func0; 12 | 13 | /** 14 | * Created by vishna on 17/02/15. 15 | */ 16 | public class Assets { 17 | 18 | public static Observable from(final Context context, final String filename) { 19 | return Observable.defer(() -> Observable.just(_from(context, filename))); 20 | } 21 | 22 | private static String _from(Context context, String filename) { 23 | 24 | BufferedReader in = null; 25 | 26 | try { 27 | StringBuilder buf = new StringBuilder(); 28 | InputStream json = context.getAssets().open(filename); 29 | in = new BufferedReader(new InputStreamReader(json, "UTF-8")); 30 | String str; 31 | 32 | while ((str = in.readLine()) != null) { 33 | buf.append(str); 34 | } 35 | 36 | in.close(); 37 | in = null; 38 | return buf.toString(); 39 | } catch (Exception e) { 40 | return null; 41 | } finally { 42 | if (in != null) try { 43 | in.close(); 44 | } catch (IOException e) {} 45 | } 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /notes/src/main/java/com/eyeem/notes/utils/KeyboardDetector.java: -------------------------------------------------------------------------------- 1 | package com.eyeem.notes.utils; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.content.ContextWrapper; 6 | import android.graphics.Rect; 7 | import android.os.Build; 8 | import android.util.Log; 9 | import android.util.TypedValue; 10 | import android.view.View; 11 | import android.view.ViewTreeObserver; 12 | 13 | /** 14 | * Created by vishna on 10/08/16. 15 | */ 16 | public class KeyboardDetector implements ViewTreeObserver.OnGlobalLayoutListener, ViewTreeObserver.OnPreDrawListener { 17 | 18 | public final static String TAG = KeyboardDetector.class.getSimpleName(); 19 | 20 | private static final int DP_KEYBOARD_THRESHOLD = 60; 21 | private int keyboardThreshold; 22 | 23 | private int currentHeight; 24 | private View view; 25 | private boolean isKeyboardShown = false; 26 | private KeyboardListener nonFinalListener; 27 | 28 | public void attachToView(View view, KeyboardListener listener) { 29 | nonFinalListener = listener; 30 | if (nonFinalListener == null) return; 31 | 32 | if (Build.VERSION.SDK_INT >= 19) { 33 | Activity activity = findInContext(view.getContext()); 34 | if (activity == null) { 35 | return; 36 | } 37 | view = activity.getWindow().getDecorView(); 38 | } 39 | 40 | keyboardThreshold = (int) TypedValue.applyDimension( 41 | TypedValue.COMPLEX_UNIT_DIP, DP_KEYBOARD_THRESHOLD, view.getResources().getDisplayMetrics()); 42 | 43 | this.view = view; 44 | currentHeight = view.getHeight(); 45 | view.getViewTreeObserver().addOnGlobalLayoutListener(this); 46 | 47 | if (currentHeight <= 0) { 48 | view.getViewTreeObserver().addOnPreDrawListener(this); 49 | } 50 | } 51 | 52 | public void detachFromView() { 53 | if (view != null) { 54 | view.getViewTreeObserver().removeGlobalOnLayoutListener(this); 55 | view.getViewTreeObserver().removeOnPreDrawListener(this); 56 | view = null; 57 | } 58 | nonFinalListener = null; 59 | } 60 | 61 | @Override 62 | public void onGlobalLayout() { 63 | int newHeight = view.getHeight(); 64 | 65 | if (Build.VERSION.SDK_INT >= 19) { 66 | Rect r = new Rect(); 67 | //r will be populated with the coordinates of your view that area still visible. 68 | view.getWindowVisibleDisplayFrame(r); 69 | newHeight = r.height(); 70 | } 71 | 72 | if (currentHeight > 0) { 73 | int diff = newHeight - currentHeight; 74 | if (diff < -keyboardThreshold) { 75 | Log.d(TAG, "onGlobalLayout. keyboard is show. height diff = " + -diff); 76 | // keyboard is show 77 | isKeyboardShown = true; 78 | if (nonFinalListener != null) { 79 | nonFinalListener.onKeyboardShow(-diff); 80 | } 81 | 82 | } else if (diff > keyboardThreshold) { 83 | Log.d(TAG, "onGlobalLayout.keyboard is hide. height diff = " + diff); 84 | // keyboard is hide 85 | isKeyboardShown = false; 86 | if (nonFinalListener != null) { 87 | nonFinalListener.onKeyboardHide(diff); 88 | } 89 | } else { 90 | Log.v(TAG, "onGlobalLayout. height diff = " + diff); 91 | } 92 | } 93 | currentHeight = newHeight; 94 | } 95 | 96 | public boolean isKeyboardShown() { 97 | return isKeyboardShown; 98 | } 99 | 100 | @Override 101 | public boolean onPreDraw() { 102 | currentHeight = view.getHeight(); 103 | view.getViewTreeObserver().removeOnPreDrawListener(this); 104 | return true; 105 | } 106 | 107 | public interface KeyboardListener { 108 | public void onKeyboardShow(int height); 109 | 110 | public void onKeyboardHide(int height); 111 | } 112 | 113 | public static Activity findInContext(Context context) { 114 | if (context instanceof Activity) return (Activity) context; 115 | if (context instanceof ContextWrapper) return findInContext(((ContextWrapper)context).getBaseContext()); 116 | return null; 117 | } 118 | } -------------------------------------------------------------------------------- /notes/src/main/java/com/eyeem/notes/utils/LinkifyDeserialiser.java: -------------------------------------------------------------------------------- 1 | package com.eyeem.notes.utils; 2 | 3 | import android.util.Log; 4 | 5 | import com.eyeem.chips.Linkify; 6 | import com.google.gson.JsonArray; 7 | import com.google.gson.JsonDeserializationContext; 8 | import com.google.gson.JsonDeserializer; 9 | import com.google.gson.JsonElement; 10 | import com.google.gson.JsonObject; 11 | import com.google.gson.JsonParseException; 12 | 13 | import java.lang.reflect.Type; 14 | import java.util.HashMap; 15 | 16 | /** 17 | * Created by vishna on 17/02/15. 18 | */ 19 | public class LinkifyDeserialiser implements JsonDeserializer { 20 | @Override public Linkify.Entities deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { 21 | 22 | Linkify.Entities entities = new Linkify.Entities(); 23 | 24 | JsonArray array = (JsonArray) json; 25 | 26 | for (JsonElement element : array) { 27 | try { 28 | JsonObject o = (JsonObject) element; 29 | 30 | Linkify.Entity entity = new Linkify.Entity(); 31 | entity.start = o.get("start").getAsInt(); 32 | entity.end = o.get("end").getAsInt(); 33 | entity.id = o.get("data").getAsString(); 34 | entity.text = o.get("displayText").getAsString(); 35 | entity.type = sMap.get(o.get("type").getAsString()); 36 | entities.add(entity); 37 | } catch (Exception e) { 38 | Log.w(LinkifyDeserialiser.class.getSimpleName(), "deserialize", e); 39 | continue; 40 | } 41 | } 42 | return entities; 43 | } 44 | 45 | private final static HashMap sMap; 46 | 47 | static { 48 | sMap = new HashMap<>(); 49 | sMap.put("url", Linkify.Entity.URL); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /notes/src/main/java/com/eyeem/notes/utils/RxBus.java: -------------------------------------------------------------------------------- 1 | package com.eyeem.notes.utils; 2 | 3 | import rx.Observable; 4 | import rx.subjects.PublishSubject; 5 | import rx.subjects.SerializedSubject; 6 | import rx.subjects.Subject; 7 | 8 | /** 9 | * Created by vishna on 18/02/15. 10 | */ 11 | public class RxBus { 12 | 13 | private final Subject _bus = new SerializedSubject<>(PublishSubject.create()); 14 | 15 | public void send(Object o) { 16 | _bus.onNext(o); 17 | } 18 | 19 | public Observable toObserverable() { 20 | return _bus; 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /notes/src/main/java/com/eyeem/notes/view/EditView.java: -------------------------------------------------------------------------------- 1 | package com.eyeem.notes.view; 2 | 3 | import android.annotation.TargetApi; 4 | import android.content.Context; 5 | import android.os.Build; 6 | import android.os.Parcelable; 7 | import android.text.TextUtils; 8 | import android.util.AttributeSet; 9 | import android.util.SparseArray; 10 | import android.view.View; 11 | import android.widget.Button; 12 | import android.widget.RelativeLayout; 13 | 14 | import com.eyeem.chips.BubbleStyle; 15 | import com.eyeem.chips.ChipsEditText; 16 | import com.eyeem.notes.R; 17 | import com.eyeem.notes.model.Note; 18 | import com.eyeem.notes.mortarflow.Ass; 19 | import com.eyeem.notes.mortarflow.HandlesBack; 20 | import com.eyeem.notes.screen.Edit; 21 | import com.eyeem.notes.utils.KeyboardDetector; 22 | import com.eyeem.notes.widget.AutocompletePopover; 23 | 24 | import java.util.ArrayList; 25 | import java.util.List; 26 | 27 | import javax.inject.Inject; 28 | 29 | import butterknife.ButterKnife; 30 | import butterknife.InjectView; 31 | import butterknife.OnClick; 32 | import lombok.Getter; 33 | 34 | import static mortar.MortarScope.getScope; 35 | import static com.eyeem.notes.mortarflow.Utils.DAGGER_SERVICE; 36 | 37 | /** 38 | * Created by vishna on 27/01/15. 39 | */ 40 | public class EditView extends RelativeLayout implements HandlesBack { 41 | 42 | @Inject Edit.Presenter presenter; 43 | @Inject List suggestions; 44 | @Inject Note note; 45 | 46 | @InjectView(R.id.chipsMultiAutoCompleteTextview1) @Getter ChipsEditText et; 47 | @InjectView(R.id.popover) AutocompletePopover popover; 48 | @InjectView(R.id.edit) Button edit; 49 | 50 | KeyboardDetector keyboardDetector; 51 | 52 | public EditView(Context context) { 53 | super(context); 54 | init(); 55 | } 56 | 57 | public EditView(Context context, AttributeSet attrs) { 58 | super(context, attrs); 59 | init(); 60 | } 61 | 62 | public EditView(Context context, AttributeSet attrs, int defStyleAttr) { 63 | super(context, attrs, defStyleAttr); 64 | init(); 65 | } 66 | 67 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) public EditView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 68 | super(context, attrs, defStyleAttr, defStyleRes); 69 | init(); 70 | } 71 | 72 | private void init() { 73 | getScope(getContext()).getService(DAGGER_SERVICE).inject(this); 74 | setSaveEnabled(true); 75 | } 76 | 77 | @Override protected void onFinishInflate() { 78 | super.onFinishInflate(); 79 | ButterKnife.inject(this, this); 80 | setup(); 81 | } 82 | 83 | @Override protected void onAttachedToWindow() { 84 | super.onAttachedToWindow(); 85 | presenter.takeView(this); 86 | keyboardDetector.attachToView(this, new KeyboardDetector.KeyboardListener() { 87 | @Override public void onKeyboardShow(int height) { 88 | 89 | } 90 | 91 | @Override public void onKeyboardHide(int height) { 92 | // popover.hide(); 93 | } 94 | }); 95 | } 96 | 97 | @Override protected void onDetachedFromWindow() { 98 | super.onDetachedFromWindow(); 99 | presenter.dropView(this); 100 | keyboardDetector.detachFromView(); 101 | } 102 | 103 | public void setup() { 104 | et.setMaxBubbleCount(25); 105 | et.setLineSpacing(1.0f, 1.25f); 106 | popover.setChipsEditText(et); 107 | 108 | final ArrayList availableItems = new ArrayList(suggestions); 109 | popover.setResolver(new AutocompletePopover.Resolver() { 110 | @Override 111 | public ArrayList getSuggestions(String query) throws Exception { 112 | return new ArrayList(); 113 | } 114 | 115 | @Override 116 | public ArrayList getDefaultSuggestions() { 117 | return availableItems; 118 | } 119 | }); 120 | 121 | BubbleStyle bubbleStyle = Note.defaultBubbleStyle(getContext(), et.getTextSize()); 122 | et.setText(note.textSpan(bubbleStyle, et)); 123 | et.setCurrentBubbleStyle(bubbleStyle); 124 | 125 | popover.setOnVisibilityChanged(visible -> edit.setVisibility(visible ? GONE : VISIBLE)); 126 | keyboardDetector = new KeyboardDetector(); 127 | } 128 | 129 | @OnClick(R.id.edit) public void toggleEdit(View view) { 130 | et.resetAutocompleList(); 131 | et.startManualMode(); 132 | popover.show(); 133 | et.postDelayed(() -> et.showKeyboard(), 100); 134 | } 135 | 136 | ///// boiler plate code that makes saving state work http://trickyandroid.com/saving-android-view-state-correctly/ 137 | @Override 138 | public Parcelable onSaveInstanceState() { 139 | return Ass.onSave(this, super.onSaveInstanceState()); 140 | } 141 | 142 | @Override 143 | public void onRestoreInstanceState(Parcelable state) { 144 | super.onRestoreInstanceState(Ass.onLoad(this, state)); 145 | } 146 | 147 | @Override 148 | protected void dispatchSaveInstanceState(SparseArray container) { 149 | dispatchFreezeSelfOnly(container); 150 | } 151 | 152 | @Override 153 | protected void dispatchRestoreInstanceState(SparseArray container) { 154 | dispatchThawSelfOnly(container); 155 | } 156 | 157 | @Override public boolean onBackPressed() { 158 | if (popover.isShown()) { 159 | popover.hide(); 160 | return true; 161 | } 162 | return false; 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /notes/src/main/java/com/eyeem/notes/view/NoteView.java: -------------------------------------------------------------------------------- 1 | package com.eyeem.notes.view; 2 | 3 | import android.annotation.TargetApi; 4 | import android.content.Context; 5 | import android.os.Build; 6 | import android.support.v4.view.ViewPager; 7 | import android.text.TextUtils; 8 | import android.util.AttributeSet; 9 | import android.util.Log; 10 | import android.view.View; 11 | import android.widget.LinearLayout; 12 | 13 | import com.eyeem.notes.R; 14 | import com.eyeem.notes.mortarflow.HandlesBack; 15 | import com.eyeem.notes.screen.Edit; 16 | import com.eyeem.notes.screen.Note; 17 | import com.eyeem.notes.screen.Preview; 18 | import com.eyeem.notes.widget.ScreenPagerAdapter; 19 | 20 | import javax.inject.Inject; 21 | 22 | import butterknife.ButterKnife; 23 | import butterknife.InjectView; 24 | import lombok.Getter; 25 | import mortar.MortarScope; 26 | 27 | import static mortar.MortarScope.getScope; 28 | import static com.eyeem.notes.mortarflow.Utils.DAGGER_SERVICE; 29 | 30 | /** 31 | * Created by vishna on 16/02/15. 32 | */ 33 | public class NoteView extends LinearLayout implements ViewPager.OnPageChangeListener, HandlesBack { 34 | 35 | @InjectView(R.id.screens_pager) @Getter ViewPager pager; 36 | ScreenPagerAdapter pagerAdapter; 37 | 38 | @Inject Note.Presenter presenter; 39 | 40 | public NoteView(Context context) { 41 | super(context); 42 | init(); 43 | } 44 | 45 | public NoteView(Context context, AttributeSet attrs) { 46 | super(context, attrs); 47 | init(); 48 | } 49 | 50 | public NoteView(Context context, AttributeSet attrs, int defStyleAttr) { 51 | super(context, attrs, defStyleAttr); 52 | init(); 53 | } 54 | 55 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) public NoteView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 56 | super(context, attrs, defStyleAttr, defStyleRes); 57 | init(); 58 | } 59 | 60 | private void init() { 61 | getScope(getContext()).getService(DAGGER_SERVICE).inject(this); 62 | } 63 | 64 | @Override protected void onFinishInflate() { 65 | super.onFinishInflate(); 66 | ButterKnife.inject(this, this); 67 | 68 | pagerAdapter = new ScreenPagerAdapter(getContext()); 69 | pagerAdapter.addScreen(new Edit(), new Preview()); 70 | pager.setAdapter(pagerAdapter); 71 | pager.setOnPageChangeListener(this); 72 | } 73 | 74 | @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 75 | // no-op 76 | } 77 | 78 | @Override public void onPageSelected(int position) { 79 | Log.d(NoteView.class.getSimpleName(), "onPageSelected = " + position); 80 | presenter.onPageSelected(position); 81 | } 82 | 83 | @Override public void onPageScrollStateChanged(int state) { 84 | String description = ""; 85 | 86 | switch (state) { 87 | case ViewPager.SCROLL_STATE_IDLE: 88 | description = "idle"; 89 | break; 90 | case ViewPager.SCROLL_STATE_DRAGGING: 91 | description = "dragging"; 92 | break; 93 | case ViewPager.SCROLL_STATE_SETTLING: 94 | description = "settling"; 95 | break; 96 | } 97 | 98 | Log.d(NoteView.class.getSimpleName(), description); 99 | } 100 | 101 | @Override protected void onAttachedToWindow() { 102 | super.onAttachedToWindow(); 103 | presenter.takeView(this); 104 | } 105 | 106 | @Override protected void onDetachedFromWindow() { 107 | super.onDetachedFromWindow(); 108 | presenter.dropView(this); 109 | } 110 | 111 | @Override public boolean onBackPressed() { 112 | int currentPosition = pager.getCurrentItem(); 113 | 114 | View view = pagerAdapter.getViewForPosition(currentPosition); 115 | if (view instanceof HandlesBack) { 116 | return ((HandlesBack)view).onBackPressed(); 117 | } 118 | 119 | return false; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /notes/src/main/java/com/eyeem/notes/view/NotesView.java: -------------------------------------------------------------------------------- 1 | package com.eyeem.notes.view; 2 | 3 | import android.annotation.TargetApi; 4 | import android.content.Context; 5 | import android.graphics.Color; 6 | import android.os.Build; 7 | import android.support.v7.widget.LinearLayoutManager; 8 | import android.support.v7.widget.RecyclerView; 9 | import android.util.AttributeSet; 10 | import android.view.View; 11 | import android.widget.FrameLayout; 12 | import android.widget.ImageButton; 13 | 14 | import com.eyeem.notes.R; 15 | import com.eyeem.notes.adapter.NotesAdapter; 16 | import com.eyeem.notes.event.NewNoteEvent; 17 | import com.eyeem.notes.screen.Notes; 18 | import com.mikepenz.iconics.IconicsDrawable; 19 | import com.mikepenz.iconics.typeface.FontAwesome; 20 | import com.squareup.otto.Bus; 21 | 22 | import javax.inject.Inject; 23 | 24 | import butterknife.ButterKnife; 25 | import butterknife.InjectView; 26 | import butterknife.OnClick; 27 | 28 | import static mortar.MortarScope.getScope; 29 | import static com.eyeem.notes.mortarflow.Utils.DAGGER_SERVICE; 30 | 31 | /** 32 | * Created by vishna on 03/02/15. 33 | */ 34 | public class NotesView extends FrameLayout { 35 | 36 | @InjectView(R.id.fab_button) ImageButton fabButton; 37 | @InjectView(R.id.recycler_view) RecyclerView rv; 38 | 39 | @Inject Notes.Presenter presenter; 40 | @Inject NotesAdapter adapter; 41 | @Inject Bus bus; 42 | 43 | LinearLayoutManager llm; 44 | 45 | public NotesView(Context context) { 46 | super(context); 47 | init(); 48 | } 49 | 50 | public NotesView(Context context, AttributeSet attrs) { 51 | super(context, attrs); 52 | init(); 53 | } 54 | 55 | public NotesView(Context context, AttributeSet attrs, int defStyleAttr) { 56 | super(context, attrs, defStyleAttr); 57 | init(); 58 | } 59 | 60 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) public NotesView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 61 | super(context, attrs, defStyleAttr, defStyleRes); 62 | init(); 63 | } 64 | 65 | private void init() { 66 | getScope(getContext()).getService(DAGGER_SERVICE).inject(this); 67 | setSaveEnabled(true); 68 | } 69 | 70 | @Override protected void onFinishInflate() { 71 | super.onFinishInflate(); 72 | ButterKnife.inject(this, this); 73 | llm = new LinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, false); 74 | rv.setLayoutManager(llm); 75 | rv.setAdapter(adapter); 76 | rv.setOnScrollListener(adapter.getCacheOnScroll()); 77 | 78 | fabButton.setImageDrawable(new IconicsDrawable(getContext(), FontAwesome.Icon.faw_pencil).color(Color.WHITE).actionBarSize()); 79 | } 80 | 81 | @Override protected void onAttachedToWindow() { 82 | super.onAttachedToWindow(); 83 | presenter.takeView(this); 84 | } 85 | 86 | @Override protected void onDetachedFromWindow() { 87 | super.onDetachedFromWindow(); 88 | presenter.dropView(this); 89 | } 90 | 91 | @OnClick(R.id.fab_button) void onFabButton(View view) { 92 | bus.post(new NewNoteEvent()); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /notes/src/main/java/com/eyeem/notes/view/StartView.java: -------------------------------------------------------------------------------- 1 | package com.eyeem.notes.view; 2 | 3 | import android.annotation.TargetApi; 4 | import android.content.Context; 5 | import android.os.Build; 6 | import android.util.AttributeSet; 7 | import android.view.View; 8 | import android.widget.LinearLayout; 9 | 10 | import com.eyeem.notes.R; 11 | import com.eyeem.notes.screen.Edit; 12 | import com.eyeem.notes.screen.Start; 13 | 14 | import javax.inject.Inject; 15 | 16 | import butterknife.ButterKnife; 17 | import butterknife.OnClick; 18 | import flow.Flow; 19 | 20 | import static mortar.MortarScope.getScope; 21 | import static com.eyeem.notes.mortarflow.Utils.DAGGER_SERVICE; 22 | 23 | /** 24 | * Created by vishna on 02/02/15. 25 | */ 26 | public class StartView extends LinearLayout { 27 | 28 | @Inject Start.Presenter presenter; 29 | 30 | public StartView(Context context) { 31 | super(context); 32 | init(); 33 | } 34 | 35 | public StartView(Context context, AttributeSet attrs) { 36 | super(context, attrs); 37 | init(); 38 | } 39 | 40 | public StartView(Context context, AttributeSet attrs, int defStyleAttr) { 41 | super(context, attrs, defStyleAttr); 42 | init(); 43 | } 44 | 45 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) public StartView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 46 | super(context, attrs, defStyleAttr, defStyleRes); 47 | init(); 48 | } 49 | 50 | private void init() { 51 | getScope(getContext()).getService(DAGGER_SERVICE).inject(this); 52 | } 53 | 54 | @Override protected void onFinishInflate() { 55 | super.onFinishInflate(); 56 | ButterKnife.inject(this, this); 57 | } 58 | 59 | @Override protected void onAttachedToWindow() { 60 | super.onAttachedToWindow(); 61 | presenter.takeView(this); 62 | } 63 | 64 | @Override protected void onDetachedFromWindow() { 65 | super.onDetachedFromWindow(); 66 | presenter.dropView(this); 67 | } 68 | 69 | @OnClick(R.id.editor) void onEditorClick(View view) { 70 | Flow.get(this).set(new Edit()); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /notes/src/main/java/com/eyeem/notes/widget/AutocompleteHelper.java: -------------------------------------------------------------------------------- 1 | package com.eyeem.notes.widget; 2 | 3 | import android.os.Handler; 4 | import android.os.HandlerThread; 5 | import android.text.TextUtils; 6 | 7 | import java.lang.ref.WeakReference; 8 | import java.util.ArrayList; 9 | import java.util.HashMap; 10 | 11 | /** 12 | * Created by vishna on 10/08/16. 13 | */ 14 | public class AutocompleteHelper { 15 | 16 | String latestQuery; 17 | 18 | Handler ui; 19 | Handler bg; 20 | AutocompletePopover.Resolver resolver; 21 | AutocompletePopover.Adapter adapter; 22 | 23 | AutocompleteHelper(AutocompletePopover.Resolver resolver, AutocompletePopover.Adapter adapter) { 24 | this.resolver = resolver; 25 | this.adapter = adapter; 26 | 27 | ui = new Handler(); 28 | HandlerThread ht = new HandlerThread("AutocompleteHelper.Thread", Thread.MIN_PRIORITY); 29 | ht.start(); 30 | bg = new Handler(ht.getLooper()); 31 | } 32 | 33 | public void search(String query) { 34 | this.latestQuery = query; 35 | ui.removeCallbacks(searchRunnable); 36 | ui.postDelayed(searchRunnable, 300); 37 | } 38 | 39 | public Runnable searchRunnable = new Runnable() { 40 | @Override public void run() { 41 | final String query = latestQuery; 42 | if (queriesSoFar.containsKey(query)) { 43 | adapter.setItems(queriesSoFar.get(query)); 44 | } else { 45 | bg.removeCallbacksAndMessages(null); 46 | bg.post(new QueryRunnable(AutocompleteHelper.this, query)); 47 | } 48 | } 49 | }; 50 | 51 | public static class QueryRunnable implements Runnable { 52 | WeakReference _autocompleteHelper; 53 | String query; 54 | 55 | public QueryRunnable(AutocompleteHelper autocompleteHelper, String query) { 56 | this._autocompleteHelper = new WeakReference<>(autocompleteHelper); 57 | this.query = query; 58 | } 59 | 60 | @Override public void run() { 61 | AutocompleteHelper h = _autocompleteHelper.get(); 62 | if (h == null) { 63 | return; 64 | } 65 | try { 66 | final ArrayList results = TextUtils.isEmpty(query) ? h.resolver.getDefaultSuggestions() : h.resolver.getSuggestions(query); 67 | h.queriesSoFar.put(query, results); 68 | final AutocompletePopover.Adapter adapter = h.adapter; 69 | h.ui.post(new Runnable() { 70 | @Override public void run() { 71 | adapter.setItems(results); 72 | } 73 | }); 74 | } catch (Exception e) {} 75 | } 76 | } 77 | 78 | private HashMap> queriesSoFar = new HashMap<>(); 79 | } 80 | -------------------------------------------------------------------------------- /notes/src/main/java/com/eyeem/notes/widget/ScreenPagerAdapter.java: -------------------------------------------------------------------------------- 1 | package com.eyeem.notes.widget; 2 | 3 | import android.content.Context; 4 | import android.support.v4.view.PagerAdapter; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | 8 | import com.eyeem.notes.mortarflow.Layout; 9 | import com.eyeem.notes.mortarflow.ScreenScoper; 10 | import com.eyeem.notes.mortarflow.Utils; 11 | 12 | import java.util.ArrayList; 13 | import java.util.HashMap; 14 | import java.util.List; 15 | import java.util.Map; 16 | import java.util.WeakHashMap; 17 | 18 | import flow.path.Path; 19 | import mortar.MortarScope; 20 | 21 | import static mortar.MortarScope.getScope; 22 | 23 | /** 24 | * Created by vishna on 16/02/15. 25 | */ 26 | public class ScreenPagerAdapter extends PagerAdapter { 27 | 28 | private final Context mContext; 29 | private final List screens; 30 | private final WeakHashMap viewsCache; 31 | private ScreenScoper screenScoper; 32 | 33 | public ScreenPagerAdapter(Context context) { 34 | mContext = context; 35 | this.screens = new ArrayList<>(); 36 | this.screenScoper = new ScreenScoper(); 37 | this.viewsCache = new WeakHashMap<>(); 38 | } 39 | 40 | public void addScreen(Path... newScreens) { 41 | for (Path newScreen : newScreens) { 42 | screens.add(newScreen); 43 | } 44 | notifyDataSetChanged(); 45 | } 46 | 47 | protected Context getContext() 48 | { 49 | return mContext; 50 | } 51 | 52 | @Override public Object instantiateItem(ViewGroup container, int position) { 53 | Path screen = screens.get(position); 54 | String childName = screen.getClass().getSimpleName() + "#" + position; 55 | 56 | MortarScope newChildScope = screenScoper.getScreenScope(mContext, childName, screen); 57 | 58 | Context childContext = newChildScope.createContext(mContext); 59 | 60 | Layout layout = screen.getClass().getAnnotation(Layout.class); 61 | View newChild = Utils.Layouts.createView(childContext, screen); 62 | container.addView(newChild); 63 | 64 | viewsCache.put(newChild, position); 65 | 66 | return newChild; 67 | } 68 | 69 | @Override public void destroyItem(ViewGroup container, int position, Object object) { 70 | View view = ((View) object); 71 | MortarScope childScope = getScope(view.getContext()); 72 | container.removeView(view); 73 | childScope.destroy(); 74 | } 75 | 76 | @Override public int getCount() { 77 | return screens.size(); 78 | } 79 | 80 | public final Path getItem(int position) { 81 | return screens.get(position); 82 | } 83 | 84 | @Override public boolean isViewFromObject(View view, Object object) { 85 | return view.equals(object); 86 | } 87 | 88 | public View getViewForPosition(int position) { 89 | for (Map.Entry e : viewsCache.entrySet()) { 90 | if (e.getValue() == position) { 91 | return e.getKey(); 92 | } 93 | } 94 | return null; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /notes/src/main/res/anim/fab_anim.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 13 | 14 | 15 | 18 | 19 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /notes/src/main/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eyeem/chips-android/cd9ad3fdac6c621095570713ca92a33b1bdfa22a/notes/src/main/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /notes/src/main/res/drawable-mdpi/australia.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eyeem/chips-android/cd9ad3fdac6c621095570713ca92a33b1bdfa22a/notes/src/main/res/drawable-mdpi/australia.png -------------------------------------------------------------------------------- /notes/src/main/res/drawable-mdpi/canada.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eyeem/chips-android/cd9ad3fdac6c621095570713ca92a33b1bdfa22a/notes/src/main/res/drawable-mdpi/canada.png -------------------------------------------------------------------------------- /notes/src/main/res/drawable-mdpi/france.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eyeem/chips-android/cd9ad3fdac6c621095570713ca92a33b1bdfa22a/notes/src/main/res/drawable-mdpi/france.png -------------------------------------------------------------------------------- /notes/src/main/res/drawable-mdpi/germany.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eyeem/chips-android/cd9ad3fdac6c621095570713ca92a33b1bdfa22a/notes/src/main/res/drawable-mdpi/germany.png -------------------------------------------------------------------------------- /notes/src/main/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eyeem/chips-android/cd9ad3fdac6c621095570713ca92a33b1bdfa22a/notes/src/main/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /notes/src/main/res/drawable-mdpi/india.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eyeem/chips-android/cd9ad3fdac6c621095570713ca92a33b1bdfa22a/notes/src/main/res/drawable-mdpi/india.png -------------------------------------------------------------------------------- /notes/src/main/res/drawable-mdpi/italy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eyeem/chips-android/cd9ad3fdac6c621095570713ca92a33b1bdfa22a/notes/src/main/res/drawable-mdpi/italy.png -------------------------------------------------------------------------------- /notes/src/main/res/drawable-mdpi/japan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eyeem/chips-android/cd9ad3fdac6c621095570713ca92a33b1bdfa22a/notes/src/main/res/drawable-mdpi/japan.png -------------------------------------------------------------------------------- /notes/src/main/res/drawable-mdpi/malaysia.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eyeem/chips-android/cd9ad3fdac6c621095570713ca92a33b1bdfa22a/notes/src/main/res/drawable-mdpi/malaysia.png -------------------------------------------------------------------------------- /notes/src/main/res/drawable-mdpi/newzealand.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eyeem/chips-android/cd9ad3fdac6c621095570713ca92a33b1bdfa22a/notes/src/main/res/drawable-mdpi/newzealand.png -------------------------------------------------------------------------------- /notes/src/main/res/drawable-mdpi/philippines.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eyeem/chips-android/cd9ad3fdac6c621095570713ca92a33b1bdfa22a/notes/src/main/res/drawable-mdpi/philippines.png -------------------------------------------------------------------------------- /notes/src/main/res/drawable-mdpi/russia.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eyeem/chips-android/cd9ad3fdac6c621095570713ca92a33b1bdfa22a/notes/src/main/res/drawable-mdpi/russia.png -------------------------------------------------------------------------------- /notes/src/main/res/drawable-mdpi/singapore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eyeem/chips-android/cd9ad3fdac6c621095570713ca92a33b1bdfa22a/notes/src/main/res/drawable-mdpi/singapore.png -------------------------------------------------------------------------------- /notes/src/main/res/drawable-mdpi/sweden.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eyeem/chips-android/cd9ad3fdac6c621095570713ca92a33b1bdfa22a/notes/src/main/res/drawable-mdpi/sweden.png -------------------------------------------------------------------------------- /notes/src/main/res/drawable-mdpi/unitedkingdom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eyeem/chips-android/cd9ad3fdac6c621095570713ca92a33b1bdfa22a/notes/src/main/res/drawable-mdpi/unitedkingdom.png -------------------------------------------------------------------------------- /notes/src/main/res/drawable-mdpi/unitedstates.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eyeem/chips-android/cd9ad3fdac6c621095570713ca92a33b1bdfa22a/notes/src/main/res/drawable-mdpi/unitedstates.png -------------------------------------------------------------------------------- /notes/src/main/res/drawable-v21/button_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /notes/src/main/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eyeem/chips-android/cd9ad3fdac6c621095570713ca92a33b1bdfa22a/notes/src/main/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /notes/src/main/res/drawable-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eyeem/chips-android/cd9ad3fdac6c621095570713ca92a33b1bdfa22a/notes/src/main/res/drawable-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /notes/src/main/res/drawable/button_round.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /notes/src/main/res/drawable/chips_edittext_gb.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 10 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /notes/src/main/res/drawable/default_bubble_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /notes/src/main/res/drawable/default_bubble_background_pressed.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /notes/src/main/res/drawable/greentext_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 8 | 9 | -------------------------------------------------------------------------------- /notes/src/main/res/drawable/greentext_background_active.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /notes/src/main/res/drawable/greentext_background_pressed.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /notes/src/main/res/drawable/greybubble_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /notes/src/main/res/drawable/greybubble_background_edit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /notes/src/main/res/drawable/greybubble_background_pressed.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /notes/src/main/res/drawable/greybubble_background_states.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 8 | 9 | -------------------------------------------------------------------------------- /notes/src/main/res/drawable/greybubble_edit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /notes/src/main/res/drawable/greytext_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 8 | 9 | -------------------------------------------------------------------------------- /notes/src/main/res/drawable/greytext_background_active.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /notes/src/main/res/drawable/greytext_background_pressed.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /notes/src/main/res/drawable/lilatext_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 8 | 9 | -------------------------------------------------------------------------------- /notes/src/main/res/drawable/lilatext_background_active.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /notes/src/main/res/drawable/lilatext_background_active_transparent.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | -------------------------------------------------------------------------------- /notes/src/main/res/drawable/lilatext_background_pressed.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /notes/src/main/res/drawable/lilatext_background_transparent.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 8 | 9 | -------------------------------------------------------------------------------- /notes/src/main/res/drawable/pinktext_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 8 | 9 | -------------------------------------------------------------------------------- /notes/src/main/res/drawable/pinktext_background_active.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | -------------------------------------------------------------------------------- /notes/src/main/res/drawable/pinktext_background_pressed.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /notes/src/main/res/drawable/round.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /notes/src/main/res/drawable/round_touch.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /notes/src/main/res/drawable/xml_pressed_state.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /notes/src/main/res/layout/autocomplete_popover.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 16 | 17 | -------------------------------------------------------------------------------- /notes/src/main/res/layout/autocomplete_row.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 20 | 21 | -------------------------------------------------------------------------------- /notes/src/main/res/layout/edit.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 14 | 15 | 30 | 31 | 32 |