diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0301369 --- /dev/null +++ b/.gitignore @@ -0,0 +1,64 @@ +# Built application files +/*/build/ + +# Crashlytics configuations +com_crashlytics_export_strings.xml + +# Signing files +.signing/ + +# User-specific configurations +.idea/ +*.iml + +# OS-specific files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Built application files +*.apk +*.ap_ + +# Files for the ART/Dalvik VM +*.dex + +# Java class files +*.class + +# Generated files +bin/ +gen/ +out/ + +# Gradle files +.gradle/ +build/ + +# Local configuration file (sdk path, etc) +local.properties + +# Proguard folder generated by Eclipse +proguard/ + +# Log Files +*.log + +# Android Studio Navigation editor temp files +.navigation/ + +# Android Studio captures folder +captures/ + +# Intellij +.idea/libraries + +# Keystore files +*.jks + +# Temp files +*~ diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..b2b8732 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2016 Mateusz Koślacz + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..591c392 --- /dev/null +++ b/README.md @@ -0,0 +1,51 @@ +# Moviper + +### A [Mosby](https://github.com/sockeqwe/mosby) based [VIPER](https://www.objc.io/issues/13-architecture/viper/) library for Android + +## Why Moviper? + +You got tired because of fact that your Activities and Fragments were becoming god classes, so you have migrated to MVP. Now you're tired of your god-class presenters and you just want to stop continuously wondering if you should pass a context to your presenters and make them platform-dependent and harder to mock and test, or maybe you should let your view-activity manage the system connected work. That's why you're here. My Android VIPER interpretation allows you keep your code clean, neat, and more [SRP](https://en.wikipedia.org/wiki/Single_responsibility_principle) with minimal effort. + +### OK, but for every screen I have to create so many files! + +To avoid manually creating all VIPER class files and managing their dependencies every time you want to create a new screen I created a generator that does all of necessary work for you. You can find it [here](https://github.com/mkoslacz/MoviperTemplateGenerator). + +### Great! But I'm used to go with Mosby. What about all of its goodies? + +You are able to use all of the Mosby's MVP Views with Moviper. MvpFragment, MvpLceActivity, ViewStateFragment etc. are fully compatibile with Moviper presenters. + +## Dependency + +I haven't yet deployed the library to any repo, so for now you will have to do a `git clone` and manually link the library to your project like [this](http://stackoverflow.com/a/31366602/3898686). I will deploy the library very soon. + +## Getting started + +Just create a VIPER files set using [Moviper Template Generator](https://github.com/mkoslacz/MoviperTemplateGenerator), fill up the contract, generate missing methods using Android Studio autofix +and implement them. Most probably you will want to check out a sample module provided in this repository to see how to use Moviper. + +## Examples + +Just check out the sample module in this repo. + +## Credits + +This library is built on top of a great [Mosby](https://github.com/sockeqwe/mosby) library by [Hannes Dorfmann](http://hannesdorfmann.com/). Check out the [project website](http://hannesdorfmann.com/mosby/). + +I've also used a Lucas Urbas Interactor implementation idea presented in his [Search viper architecture example](https://github.com/lurbas/Search). + +## License +``` +Copyright 2016 Mateusz Koślacz + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +``` \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..3eebc9b --- /dev/null +++ b/build.gradle @@ -0,0 +1,26 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + repositories { + jcenter() + mavenCentral() + } + dependencies { + classpath 'com.android.tools.build:gradle:2.1.3' + classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' + classpath 'me.tatarka:gradle-retrolambda:3.2.5' + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + jcenter() + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..4b601f2 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,19 @@ +# Project-wide Gradle settings. + +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. + +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html + +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +# Default value: -Xmx10248m -XX:MaxPermSize=256m +# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 +org.gradle.jvmargs=-Xmx4096m + +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..13372ae Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..f573d78 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Tue Aug 16 17:56:20 CEST 2016 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..9d82f78 --- /dev/null +++ b/gradlew @@ -0,0 +1,160 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..aec9973 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/moviper/.gitignore b/moviper/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/moviper/.gitignore @@ -0,0 +1 @@ +/build diff --git a/moviper/build.gradle b/moviper/build.gradle new file mode 100644 index 0000000..85e9bf6 --- /dev/null +++ b/moviper/build.gradle @@ -0,0 +1,27 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion 24 + buildToolsVersion "24.0.1" + + defaultConfig { + minSdkVersion 16 + targetSdkVersion 24 + versionCode 1 + versionName "1.0" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) + testCompile 'junit:junit:4.12' + compile 'com.android.support:appcompat-v7:24.2.0' + compile 'com.hannesdorfmann.mosby:mvp:2.0.1' + compile 'com.hannesdorfmann.mosby:viewstate:2.0.1' +} diff --git a/moviper/proguard-rules.pro b/moviper/proguard-rules.pro new file mode 100644 index 0000000..c9d9908 --- /dev/null +++ b/moviper/proguard-rules.pro @@ -0,0 +1,17 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /Users/mateuszkoslacz/Library/Android/sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/moviper/src/androidTest/java/com/mateuszkoslacz/moviper/ApplicationTest.java b/moviper/src/androidTest/java/com/mateuszkoslacz/moviper/ApplicationTest.java new file mode 100644 index 0000000..f895e3b --- /dev/null +++ b/moviper/src/androidTest/java/com/mateuszkoslacz/moviper/ApplicationTest.java @@ -0,0 +1,13 @@ +package com.mateuszkoslacz.moviper; + +import android.app.Application; +import android.test.ApplicationTestCase; + +/** + * Testing Fundamentals + */ +public class ApplicationTest extends ApplicationTestCase { + public ApplicationTest() { + super(Application.class); + } +} \ No newline at end of file diff --git a/moviper/src/main/AndroidManifest.xml b/moviper/src/main/AndroidManifest.xml new file mode 100644 index 0000000..b0a6f24 --- /dev/null +++ b/moviper/src/main/AndroidManifest.xml @@ -0,0 +1,12 @@ + + + + + + + diff --git a/moviper/src/main/java/com/mateuszkoslacz/moviper/base/interactor/BaseInteractor.java b/moviper/src/main/java/com/mateuszkoslacz/moviper/base/interactor/BaseInteractor.java new file mode 100644 index 0000000..210f66c --- /dev/null +++ b/moviper/src/main/java/com/mateuszkoslacz/moviper/base/interactor/BaseInteractor.java @@ -0,0 +1,47 @@ +package com.mateuszkoslacz.moviper.base.interactor; + + +import android.support.annotation.Nullable; + +import com.mateuszkoslacz.moviper.iface.interactor.MoviperInteractor; +import com.mateuszkoslacz.moviper.iface.presenter.interactor.MoviperPresenterForInteractor; +import com.mateuszkoslacz.moviper.util.WeakReferenceUtils; + +import java.lang.ref.WeakReference; + +/** + * Created by lucas.urbas on 29/08/15. + *

+ * Adapted and modified by mateuszkoslacz on 08.08.2016. + *

+ * Base Interactor class. (see {@link MoviperInteractor}) + */ +public abstract class BaseInteractor + + implements MoviperInteractor { + + @Nullable + private WeakReference presenter; + + @Nullable + @Override + public PresenterType getPresenter() { + return WeakReferenceUtils.get(presenter); + } + + @Override + public boolean isPresenterAttached() { + return WeakReferenceUtils.isAttached(presenter); + } + + @Override + public void attachPresenter(PresenterType presenter) { + this.presenter = new WeakReference<>(presenter); + } + + @Override + public void detachPresenter() { + WeakReferenceUtils.detach(presenter); + } +} diff --git a/moviper/src/main/java/com/mateuszkoslacz/moviper/base/presenter/PervActivityBasePresenter.java b/moviper/src/main/java/com/mateuszkoslacz/moviper/base/presenter/PervActivityBasePresenter.java new file mode 100644 index 0000000..6b7fdb9 --- /dev/null +++ b/moviper/src/main/java/com/mateuszkoslacz/moviper/base/presenter/PervActivityBasePresenter.java @@ -0,0 +1,70 @@ +package com.mateuszkoslacz.moviper.base.presenter; + +import android.app.Activity; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import com.hannesdorfmann.mosby.mvp.MvpBasePresenter; +import com.hannesdorfmann.mosby.mvp.MvpPresenter; +import com.hannesdorfmann.mosby.mvp.MvpView; +import com.mateuszkoslacz.moviper.iface.presenter.routing.MoviperActivityPresenterForRouting; +import com.mateuszkoslacz.moviper.iface.routing.MoviperRouting; + +/** + * Created by mateuszkoslacz on 09.08.2016. + *

+ * Perv - Presenter, Entities, Routing, View. + *

+ * This is an Activity version of base presenter class for mentioned set of concepts. + * (see {@link MvpBasePresenter}) + *

+ * You can use any Mosby Activity View with this class + * ({@link com.hannesdorfmann.mosby.mvp.MvpActivity}, + * {@link com.hannesdorfmann.mosby.mvp.lce.MvpLceActivity}, + * {@link com.hannesdorfmann.mosby.mvp.viewstate.MvpViewStateActivity}, + * {@link com.hannesdorfmann.mosby.mvp.viewstate.lce.MvpLceViewStateActivity}) + */ +//TODO migrate to MvpNullObjectPresenter base class? +public abstract class PervActivityBasePresenter + + extends MvpBasePresenter + implements MoviperActivityPresenterForRouting, + MvpPresenter { + + @Nullable + private RoutingType routing; + + public PervActivityBasePresenter(@NonNull Activity activity) { + super(); + this.routing = createRouting(activity); + } + + @Override + public boolean isRoutingAttached() { + return routing != null; + } + + @Override + public void attachView(ViewType view) { + super.attachView(view); + assert routing != null; + //noinspection unchecked + routing.attachPresenter(this); + } + + @Override + public void detachView(boolean retainInstance) { + super.detachView(retainInstance); + assert routing != null; + routing.detachPresenter(); + routing = null; + } + + @Nullable + @Override + public RoutingType getRouting() { + return routing; + } + +} diff --git a/moviper/src/main/java/com/mateuszkoslacz/moviper/base/presenter/PervFragmentBasePresenter.java b/moviper/src/main/java/com/mateuszkoslacz/moviper/base/presenter/PervFragmentBasePresenter.java new file mode 100644 index 0000000..bd8dfe5 --- /dev/null +++ b/moviper/src/main/java/com/mateuszkoslacz/moviper/base/presenter/PervFragmentBasePresenter.java @@ -0,0 +1,70 @@ +package com.mateuszkoslacz.moviper.base.presenter; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; + +import com.hannesdorfmann.mosby.mvp.MvpBasePresenter; +import com.hannesdorfmann.mosby.mvp.MvpPresenter; +import com.hannesdorfmann.mosby.mvp.MvpView; +import com.mateuszkoslacz.moviper.iface.presenter.routing.MoviperFragmentPresenterForRouting; +import com.mateuszkoslacz.moviper.iface.routing.MoviperRouting; + +/** + * Created by mateuszkoslacz on 09.08.2016. + *

+ * Perv - Presenter, Entities, Routing, View. + *

+ * This is a Fragment version of base presenter class for mentioned set of concepts. + * (see {@link MvpBasePresenter}) + *

+ * You can use any Mosby Activity View with this class + * ({@link com.hannesdorfmann.mosby.mvp.MvpFragment}, + * {@link com.hannesdorfmann.mosby.mvp.lce.MvpLceFragment}, + * {@link com.hannesdorfmann.mosby.mvp.viewstate.MvpViewStateFragment}, + * {@link com.hannesdorfmann.mosby.mvp.viewstate.lce.MvpLceViewStateFragment}) + */ +//TODO migrate to MvpNullObjectPresenter base class? +public abstract class PervFragmentBasePresenter + + extends MvpBasePresenter + implements MoviperFragmentPresenterForRouting, + MvpPresenter { + + @Nullable + private RoutingType routing; + + public PervFragmentBasePresenter(@NonNull Fragment fragment) { + super(); + this.routing = createRouting(fragment); + } + + @Override + public boolean isRoutingAttached() { + return routing != null; + } + + @Override + public void attachView(ViewType view) { + super.attachView(view); + assert routing != null; + //noinspection unchecked + routing.attachPresenter(this); + } + + @Override + public void detachView(boolean retainInstance) { + super.detachView(retainInstance); + assert routing != null; + routing.detachPresenter(); + routing = null; + } + + @Nullable + @Override + public RoutingType getRouting() { + return routing; + } + +} diff --git a/moviper/src/main/java/com/mateuszkoslacz/moviper/base/presenter/ViperActivityBasePresenter.java b/moviper/src/main/java/com/mateuszkoslacz/moviper/base/presenter/ViperActivityBasePresenter.java new file mode 100644 index 0000000..14e36e2 --- /dev/null +++ b/moviper/src/main/java/com/mateuszkoslacz/moviper/base/presenter/ViperActivityBasePresenter.java @@ -0,0 +1,73 @@ +package com.mateuszkoslacz.moviper.base.presenter; + +import android.app.Activity; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import com.hannesdorfmann.mosby.mvp.MvpBasePresenter; +import com.hannesdorfmann.mosby.mvp.MvpPresenter; +import com.hannesdorfmann.mosby.mvp.MvpView; +import com.mateuszkoslacz.moviper.iface.interactor.MoviperInteractor; +import com.mateuszkoslacz.moviper.iface.presenter.interactor.MoviperPresenterForInteractor; +import com.mateuszkoslacz.moviper.iface.presenter.routing.MoviperActivityPresenterForRouting; +import com.mateuszkoslacz.moviper.iface.routing.MoviperRouting; + +/** + * Created by mateuszkoslacz on 08.08.2016. + *

+ * Viper - View, Interactor, Presenter, Entities, Routing + *

+ * This is a Activity version of base presenter class for mentioned set of concepts. + * (see {@link MvpBasePresenter}) + *

+ * You can use any Mosby Activity View with this class + * ({@link com.hannesdorfmann.mosby.mvp.MvpActivity}, + * {@link com.hannesdorfmann.mosby.mvp.lce.MvpLceActivity}, + * {@link com.hannesdorfmann.mosby.mvp.viewstate.MvpViewStateActivity}, + * {@link com.hannesdorfmann.mosby.mvp.viewstate.lce.MvpLceViewStateActivity}) + */ +//TODO migrate to MvpNullObjectPresenter base class? +public abstract class ViperActivityBasePresenter + + extends WipeBasePresenter + implements MvpPresenter, + MoviperPresenterForInteractor, + MoviperActivityPresenterForRouting { + + @Nullable + private RoutingType routing; + + public ViperActivityBasePresenter(@NonNull Activity activity) { + super(); + this.routing = createRouting(activity); + } + + @Override + public boolean isRoutingAttached() { + return routing != null; + } + + @Override + public void attachView(ViewType view) { + super.attachView(view); + assert routing != null; + //noinspection unchecked + routing.attachPresenter(this); + } + + @Override + public void detachView(boolean retainInstance) { + super.detachView(retainInstance); + assert routing != null; + routing.detachPresenter(); + routing = null; + } + + @Nullable + @Override + public RoutingType getRouting() { + return routing; + } +} diff --git a/moviper/src/main/java/com/mateuszkoslacz/moviper/base/presenter/ViperFragmentBasePresenter.java b/moviper/src/main/java/com/mateuszkoslacz/moviper/base/presenter/ViperFragmentBasePresenter.java new file mode 100644 index 0000000..19ebd5d --- /dev/null +++ b/moviper/src/main/java/com/mateuszkoslacz/moviper/base/presenter/ViperFragmentBasePresenter.java @@ -0,0 +1,73 @@ +package com.mateuszkoslacz.moviper.base.presenter; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; + +import com.hannesdorfmann.mosby.mvp.MvpBasePresenter; +import com.hannesdorfmann.mosby.mvp.MvpPresenter; +import com.hannesdorfmann.mosby.mvp.MvpView; +import com.mateuszkoslacz.moviper.iface.interactor.MoviperInteractor; +import com.mateuszkoslacz.moviper.iface.presenter.interactor.MoviperPresenterForInteractor; +import com.mateuszkoslacz.moviper.iface.presenter.routing.MoviperFragmentPresenterForRouting; +import com.mateuszkoslacz.moviper.iface.routing.MoviperRouting; + +/** + * Created by mateuszkoslacz on 08.08.2016. + *

+ * Viper - View, Interactor, Presenter, Entities, Routing + *

+ * This is a Fragment version of base presenter class for mentioned set of concepts. + * (see {@link MvpBasePresenter}) + *

+ * You can use any Mosby Activity View with this class + * ({@link com.hannesdorfmann.mosby.mvp.MvpFragment}, + * {@link com.hannesdorfmann.mosby.mvp.lce.MvpLceFragment}, + * {@link com.hannesdorfmann.mosby.mvp.viewstate.MvpViewStateFragment}, + * {@link com.hannesdorfmann.mosby.mvp.viewstate.lce.MvpLceViewStateFragment}) + */ +//TODO migrate to MvpNullObjectPresenter base class? +public abstract class ViperFragmentBasePresenter + + extends WipeBasePresenter + implements MvpPresenter, + MoviperPresenterForInteractor, + MoviperFragmentPresenterForRouting { + + @Nullable + private RoutingType routing; + + public ViperFragmentBasePresenter(@NonNull Fragment fragment) { + super(); + this.routing = createRouting(fragment); + } + + @Override + public boolean isRoutingAttached() { + return routing != null; + } + + @Override + public void attachView(ViewType view) { + super.attachView(view); + assert routing != null; + //noinspection unchecked + routing.attachPresenter(this); + } + + @Override + public void detachView(boolean retainInstance) { + super.detachView(retainInstance); + assert routing != null; + routing.detachPresenter(); + routing = null; + } + + @Nullable + @Override + public RoutingType getRouting() { + return routing; + } +} diff --git a/moviper/src/main/java/com/mateuszkoslacz/moviper/base/presenter/WipeBasePresenter.java b/moviper/src/main/java/com/mateuszkoslacz/moviper/base/presenter/WipeBasePresenter.java new file mode 100644 index 0000000..7a71324 --- /dev/null +++ b/moviper/src/main/java/com/mateuszkoslacz/moviper/base/presenter/WipeBasePresenter.java @@ -0,0 +1,74 @@ +package com.mateuszkoslacz.moviper.base.presenter; + +import android.support.annotation.Nullable; + +import com.hannesdorfmann.mosby.mvp.MvpBasePresenter; +import com.hannesdorfmann.mosby.mvp.MvpPresenter; +import com.hannesdorfmann.mosby.mvp.MvpView; +import com.mateuszkoslacz.moviper.iface.interactor.MoviperInteractor; +import com.mateuszkoslacz.moviper.iface.presenter.interactor.MoviperPresenterForInteractor; + +/** + * Created by mateuszkoslacz on 08.08.2016. based on original lucas.urbas idea from 29/08/15. + *

+ * Wipe - Wiew/View, Interactor, Presenter, Entities + *

+ * This is a universal base presenter class for mentioned set of concepts. + * (see {@link MvpBasePresenter}) + *

+ * You can use any Mosby View with this class (activity views: + * {@link com.hannesdorfmann.mosby.mvp.MvpActivity}, + * {@link com.hannesdorfmann.mosby.mvp.lce.MvpLceActivity}, + * {@link com.hannesdorfmann.mosby.mvp.viewstate.MvpViewStateActivity}, + * {@link com.hannesdorfmann.mosby.mvp.viewstate.lce.MvpLceViewStateActivity}, + * and fragment ones: + * {@link com.hannesdorfmann.mosby.mvp.MvpFragment}, + * {@link com.hannesdorfmann.mosby.mvp.lce.MvpLceFragment}, + * {@link com.hannesdorfmann.mosby.mvp.viewstate.MvpViewStateFragment}, + * {@link com.hannesdorfmann.mosby.mvp.viewstate.lce.MvpLceViewStateFragment}) + */ +//TODO migrate to MvpNullObjectPresenter base class? +public abstract class WipeBasePresenter + + extends MvpBasePresenter + implements MvpPresenter, + MoviperPresenterForInteractor { + + @Nullable + private InteractorType interactor; + + public WipeBasePresenter() { + super(); + this.interactor = createInteractor(); + } + + @Override + public boolean isInteractorAttached() { + return interactor != null; + } + + @Override + public void attachView(ViewType view) { + super.attachView(view); + assert interactor != null; + //noinspection unchecked + interactor.attachPresenter(this); + } + + @Override + public void detachView(boolean retainInstance) { + super.detachView(retainInstance); + assert interactor != null; + interactor.detachPresenter(); + interactor = null; + } + + @Nullable + @Override + public InteractorType getInteractor() { + return interactor; + } + + +} diff --git a/moviper/src/main/java/com/mateuszkoslacz/moviper/base/routing/ActivityBaseRouting.java b/moviper/src/main/java/com/mateuszkoslacz/moviper/base/routing/ActivityBaseRouting.java new file mode 100644 index 0000000..b1c3400 --- /dev/null +++ b/moviper/src/main/java/com/mateuszkoslacz/moviper/base/routing/ActivityBaseRouting.java @@ -0,0 +1,66 @@ +package com.mateuszkoslacz.moviper.base.routing; + +import android.app.Activity; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import com.mateuszkoslacz.moviper.iface.presenter.routing.MoviperPresenterForRouting; +import com.mateuszkoslacz.moviper.iface.routing.MoviperRouting; +import com.mateuszkoslacz.moviper.util.WeakReferenceUtils; + +import java.lang.ref.WeakReference; + +/** + * Created by mateuszkoslacz on 08.08.2016. + *

+ * Activity version of base Routing class. + * (see {@link MoviperRouting} and {@link MoviperPresenterForRouting}) + */ +public abstract class ActivityBaseRouting + // I prefer readability rather than conventions + implements MoviperRouting { + + @Nullable + protected WeakReference activity; + + @Nullable + private WeakReference presenter; + + public ActivityBaseRouting(@NonNull Activity activity) { + this.activity = new WeakReference<>(activity); + } + + @Override + public boolean isActivityAttached() { + return WeakReferenceUtils.isAttached(activity); + } + + @Nullable + @Override + public Activity getActivity() { + return WeakReferenceUtils.get(activity); + } + + @Override + public void attachPresenter(PresenterType presenter) { + this.presenter = new WeakReference<>(presenter); + } + + @Nullable + @Override + public PresenterType getPresenter() { + return WeakReferenceUtils.get(presenter); + } + + @Override + public boolean isPresenterAttached() { + return WeakReferenceUtils.isAttached(presenter); + } + + @Override + public void detachPresenter() { + WeakReferenceUtils.detach(presenter); + WeakReferenceUtils.detach(activity); + } + +} diff --git a/moviper/src/main/java/com/mateuszkoslacz/moviper/base/routing/ActivityBaseViewHelperRouting.java b/moviper/src/main/java/com/mateuszkoslacz/moviper/base/routing/ActivityBaseViewHelperRouting.java new file mode 100644 index 0000000..895d9ea --- /dev/null +++ b/moviper/src/main/java/com/mateuszkoslacz/moviper/base/routing/ActivityBaseViewHelperRouting.java @@ -0,0 +1,40 @@ +package com.mateuszkoslacz.moviper.base.routing; + +import android.app.Activity; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import com.mateuszkoslacz.moviper.iface.presenter.routing.MoviperPresenterForRouting; +import com.mateuszkoslacz.moviper.iface.routing.MoviperViewHelperRouting; +import com.mateuszkoslacz.moviper.iface.viewhelper.MoviperViewHelper; +import com.mateuszkoslacz.moviper.util.WeakReferenceUtils; + +/** + * Created by mateuszkoslacz on 08.08.2016. + *

+ * Activity version of base Routing class with ViewHelper. (see {@link MoviperViewHelperRouting}, + * {@link MoviperViewHelper} and {@link MoviperPresenterForRouting}) + */ +public abstract class ActivityBaseViewHelperRouting + + extends ActivityBaseRouting + implements MoviperViewHelperRouting { + + public ActivityBaseViewHelperRouting(@NonNull Activity activity) { + super(activity); + } + + @Nullable + @Override + public ViewHelperType getViewHelper() { + //noinspection unchecked + return (ViewHelperType) WeakReferenceUtils.get(activity); + } + + @Override + public boolean isViewHelperAttached() { + return isActivityAttached(); + } + +} diff --git a/moviper/src/main/java/com/mateuszkoslacz/moviper/base/routing/FragmentBaseRouting.java b/moviper/src/main/java/com/mateuszkoslacz/moviper/base/routing/FragmentBaseRouting.java new file mode 100644 index 0000000..7a4aeaf --- /dev/null +++ b/moviper/src/main/java/com/mateuszkoslacz/moviper/base/routing/FragmentBaseRouting.java @@ -0,0 +1,23 @@ +package com.mateuszkoslacz.moviper.base.routing; + +import android.support.annotation.NonNull; +import android.support.v4.app.Fragment; + +import com.mateuszkoslacz.moviper.iface.presenter.routing.MoviperPresenterForRouting; +import com.mateuszkoslacz.moviper.iface.routing.MoviperRouting; + +/** + * Created by mateuszkoslacz on 08.08.2016. + *

+ * Fragment version of base Routing class. + * (see {@link MoviperRouting} and {@link MoviperPresenterForRouting}) + */ +public abstract class FragmentBaseRouting + // I prefer readability rather than conventions + extends ActivityBaseRouting + implements MoviperRouting { + + public FragmentBaseRouting(@NonNull Fragment fragment) { + super(fragment.getActivity()); + } +} diff --git a/moviper/src/main/java/com/mateuszkoslacz/moviper/base/routing/FragmentBaseViewHelperRouting.java b/moviper/src/main/java/com/mateuszkoslacz/moviper/base/routing/FragmentBaseViewHelperRouting.java new file mode 100644 index 0000000..7896d00 --- /dev/null +++ b/moviper/src/main/java/com/mateuszkoslacz/moviper/base/routing/FragmentBaseViewHelperRouting.java @@ -0,0 +1,51 @@ +package com.mateuszkoslacz.moviper.base.routing; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; + +import com.mateuszkoslacz.moviper.iface.presenter.routing.MoviperPresenterForRouting; +import com.mateuszkoslacz.moviper.iface.routing.MoviperViewHelperRouting; +import com.mateuszkoslacz.moviper.iface.viewhelper.MoviperViewHelper; +import com.mateuszkoslacz.moviper.util.WeakReferenceUtils; + +import java.lang.ref.WeakReference; + +/** + * Created by mateuszkoslacz on 08.08.2016. + *

+ * Fragment version of base Routing class with ViewHelper. (see {@link MoviperViewHelperRouting}, + * {@link MoviperViewHelper} and {@link MoviperPresenterForRouting}) + */ +public abstract class FragmentBaseViewHelperRouting + + extends ActivityBaseViewHelperRouting + implements MoviperViewHelperRouting { + + @Nullable + private WeakReference fragment; + + public FragmentBaseViewHelperRouting(@NonNull Fragment fragment) { + super(fragment.getActivity()); + this.fragment = new WeakReference<>(fragment); + } + + @Override + public boolean isViewHelperAttached() { + return WeakReferenceUtils.isAttached(fragment); + } + + @Nullable + @Override + public ViewHelperType getViewHelper() { + //noinspection unchecked + return ((ViewHelperType) WeakReferenceUtils.get(fragment)); + } + + @Override + public void detachPresenter() { + super.detachPresenter(); + WeakReferenceUtils.detach(fragment); + } +} diff --git a/moviper/src/main/java/com/mateuszkoslacz/moviper/iface/interactor/MoviperInteractor.java b/moviper/src/main/java/com/mateuszkoslacz/moviper/iface/interactor/MoviperInteractor.java new file mode 100644 index 0000000..b20415c --- /dev/null +++ b/moviper/src/main/java/com/mateuszkoslacz/moviper/iface/interactor/MoviperInteractor.java @@ -0,0 +1,33 @@ +package com.mateuszkoslacz.moviper.iface.interactor; + +import android.support.annotation.Nullable; + +import com.mateuszkoslacz.moviper.iface.presenter.interactor.MoviperPresenterForInteractor; + +/** + * Created by lucas.urbas on 29/08/15. + *

+ * Adapted and modified by mateuszkoslacz on 08.08.2016. + *

+ * It's responsible of manipulating data on behalf of Presenter (see Mosby's + * {@link com.hannesdorfmann.mosby.mvp.MvpPresenter}). It queries an API, DB etc. + */ +// I prefer readability rather than conventions +public interface MoviperInteractor { + + /** + * Remember to call {@link #isPresenterAttached()} before getting the Presenter to avoid + * {@link NullPointerException}s. + * + * @return attached {@link MoviperPresenterForInteractor} subclass instance or + * null if it's detached (View got destroyed) + */ + @Nullable + PresenterType getPresenter(); + + boolean isPresenterAttached(); + + void attachPresenter(PresenterType presenter); + + void detachPresenter(); +} diff --git a/moviper/src/main/java/com/mateuszkoslacz/moviper/iface/presenter/interactor/MoviperPresenterForInteractor.java b/moviper/src/main/java/com/mateuszkoslacz/moviper/iface/presenter/interactor/MoviperPresenterForInteractor.java new file mode 100644 index 0000000..b56ca06 --- /dev/null +++ b/moviper/src/main/java/com/mateuszkoslacz/moviper/iface/presenter/interactor/MoviperPresenterForInteractor.java @@ -0,0 +1,41 @@ +package com.mateuszkoslacz.moviper.iface.presenter.interactor; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import com.mateuszkoslacz.moviper.iface.interactor.MoviperInteractor; + +/** + * Created by lucas.urbas on 29/08/15. + *

+ * Adapted and modified by mateuszkoslacz on 08.08.2016. + *

+ * It defines presenter interface which Interactor ({@link MoviperInteractor}) needs to communicate + * with Presenter ({@link com.hannesdorfmann.mosby.mvp.MvpBasePresenter} for proper + * execution of given tasks. There are mostly callbacks for displaying data from interactor, + * interactor's error messages etc. + */ +// I prefer readability rather than conventions +public interface MoviperPresenterForInteractor { + + boolean isInteractorAttached(); + + /** + * Remember to call {@link #isInteractorAttached()} before getting the Interactor to avoid + * {@link NullPointerException}s. + * + * @return attached {@link MoviperInteractor} subclass instance or + * null if it's detached (View got destroyed) + */ + @Nullable + InteractorType getInteractor(); + + /** + * Instantiate a Interactor instance here. + * + * @return The {@link MoviperInteractor} for this view + */ + @NonNull + InteractorType createInteractor(); + +} diff --git a/moviper/src/main/java/com/mateuszkoslacz/moviper/iface/presenter/routing/MoviperActivityPresenterForRouting.java b/moviper/src/main/java/com/mateuszkoslacz/moviper/iface/presenter/routing/MoviperActivityPresenterForRouting.java new file mode 100644 index 0000000..1c9d238 --- /dev/null +++ b/moviper/src/main/java/com/mateuszkoslacz/moviper/iface/presenter/routing/MoviperActivityPresenterForRouting.java @@ -0,0 +1,25 @@ +package com.mateuszkoslacz.moviper.iface.presenter.routing; + +import android.app.Activity; +import android.support.annotation.NonNull; + +import com.mateuszkoslacz.moviper.iface.routing.MoviperRouting; + +/** + * Created by mateuszkoslacz on 08.08.2016. + *

+ * MoviperPresenterForRouting version for Activity presenters. + * (see {@link MoviperPresenterForRouting}) + */ +// I prefer readability rather than conventions +public interface MoviperActivityPresenterForRouting + extends MoviperPresenterForRouting { + + /** + * Instantiate a Routing instance here. + * + * @return The {@link MoviperRouting} for this view + */ + @NonNull + RoutingType createRouting(@NonNull Activity activity); +} diff --git a/moviper/src/main/java/com/mateuszkoslacz/moviper/iface/presenter/routing/MoviperFragmentPresenterForRouting.java b/moviper/src/main/java/com/mateuszkoslacz/moviper/iface/presenter/routing/MoviperFragmentPresenterForRouting.java new file mode 100644 index 0000000..b88f92b --- /dev/null +++ b/moviper/src/main/java/com/mateuszkoslacz/moviper/iface/presenter/routing/MoviperFragmentPresenterForRouting.java @@ -0,0 +1,25 @@ +package com.mateuszkoslacz.moviper.iface.presenter.routing; + +import android.support.annotation.NonNull; +import android.support.v4.app.Fragment; + +import com.mateuszkoslacz.moviper.iface.routing.MoviperRouting; + +/** + * Created by mateuszkoslacz on 08.08.2016. + *

+ * MoviperPresenterForRouting version for Fragment presenters. + * (see {@link MoviperPresenterForRouting}) + */ +// I prefer readability rather than conventions +public interface MoviperFragmentPresenterForRouting + extends MoviperPresenterForRouting { + + /** + * Instantiate a Routing instance here. + * + * @return The {@link MoviperRouting} for this view + */ + @NonNull + RoutingType createRouting(@NonNull Fragment fragment); +} diff --git a/moviper/src/main/java/com/mateuszkoslacz/moviper/iface/presenter/routing/MoviperPresenterForRouting.java b/moviper/src/main/java/com/mateuszkoslacz/moviper/iface/presenter/routing/MoviperPresenterForRouting.java new file mode 100644 index 0000000..4b870b6 --- /dev/null +++ b/moviper/src/main/java/com/mateuszkoslacz/moviper/iface/presenter/routing/MoviperPresenterForRouting.java @@ -0,0 +1,29 @@ +package com.mateuszkoslacz.moviper.iface.presenter.routing; + +import android.support.annotation.Nullable; + +import com.mateuszkoslacz.moviper.iface.routing.MoviperRouting; + +/** + * Created by mateuszkoslacz on 08.08.2016. + *

+ * It defines presenter interface which Routing ({@link MoviperRouting}) needs to communicate with + * Presenter ({@link com.hannesdorfmann.mosby.mvp.MvpBasePresenter}) for proper + * execution of given tasks. There are mostly callbacks for creating alarms, + * routing's error messages etc. + */ +// I prefer readability rather than conventions +public interface MoviperPresenterForRouting { + + boolean isRoutingAttached(); + + /** + * Remember to call {@link #isRoutingAttached()} before getting the Routing to avoid + * {@link NullPointerException}s. + * + * @return attached {@link MoviperRouting} subclass instance or + * null if it's detached (View got destroyed) + */ + @Nullable + RoutingType getRouting(); +} diff --git a/moviper/src/main/java/com/mateuszkoslacz/moviper/iface/routing/MoviperRouting.java b/moviper/src/main/java/com/mateuszkoslacz/moviper/iface/routing/MoviperRouting.java new file mode 100644 index 0000000..e9cc249 --- /dev/null +++ b/moviper/src/main/java/com/mateuszkoslacz/moviper/iface/routing/MoviperRouting.java @@ -0,0 +1,52 @@ +package com.mateuszkoslacz.moviper.iface.routing; + +import android.app.Activity; +import android.support.annotation.Nullable; + +import com.mateuszkoslacz.moviper.iface.presenter.routing.MoviperPresenterForRouting; + +/** + * Created by mateuszkoslacz on 08.08.2016. + *

+ * It's responsible for performing all platform-specific tasks in behalf of presenter, ie. starting + * a new Activity, a new Service, scheduling alarms etc. + *

+ * It's also responsible of UI changes outside of given view, ie. Fragment presenter uses this + * routing for switching Fragments in parent Activity. + *

+ * In complex use cases you will probably want to include here separate classes for handling alarms + * scheduling, Services creating etc. for better separation of concepts. + *

+ * If you are looking for solution providing Android Views to use in Android Transaction with shared + * views, see {@link MoviperViewHelperRouting} + */ +// I prefer readability rather than conventions +public interface MoviperRouting { + + /** + * Remember to call {@link #isActivityAttached()} before getting the Activity to avoid + * {@link NullPointerException}s. + * + * @return attached Activity instance or null if it's detached (View got destroyed) + */ + @Nullable + Activity getActivity(); + + boolean isActivityAttached(); + + /** + * Remember to call {@link #isPresenterAttached()} ()} before getting the Presenter to avoid + * {@link NullPointerException}s. + * + * @return attached Moviper {@link com.hannesdorfmann.mosby.mvp.MvpBasePresenter} subclass + * instance or null if it's detached (View got destroyed) + */ + @Nullable + PresenterType getPresenter(); + + boolean isPresenterAttached(); + + void attachPresenter(PresenterType presenter); + + void detachPresenter(); +} diff --git a/moviper/src/main/java/com/mateuszkoslacz/moviper/iface/routing/MoviperViewHelperRouting.java b/moviper/src/main/java/com/mateuszkoslacz/moviper/iface/routing/MoviperViewHelperRouting.java new file mode 100644 index 0000000..f80511d --- /dev/null +++ b/moviper/src/main/java/com/mateuszkoslacz/moviper/iface/routing/MoviperViewHelperRouting.java @@ -0,0 +1,32 @@ +package com.mateuszkoslacz.moviper.iface.routing; + +import android.support.annotation.Nullable; + +import com.mateuszkoslacz.moviper.iface.presenter.routing.MoviperPresenterForRouting; +import com.mateuszkoslacz.moviper.iface.viewhelper.MoviperViewHelper; + +/** + * Created by mateuszkoslacz on 08.08.2016. + *

+ * It's a Routing ({@link MoviperRouting}) that also provides a ViewHelper + * (see {@link MoviperViewHelper}), which is responsible for providing + * Android Views (ImageView, TextView etc.) to allow performing Android Transitions with shared Views. + *

+ */ +public interface MoviperViewHelperRouting + + extends MoviperRouting { + + /** + * Remember to call {@link #isViewHelperAttached()} before getting the ViewHelper to avoid + * {@link NullPointerException}s. + * + * @return attached {@link MoviperViewHelper} or null if it's detached (View got destroyed) + */ + @Nullable + ViewHelperType getViewHelper(); + + boolean isViewHelperAttached(); + +} diff --git a/moviper/src/main/java/com/mateuszkoslacz/moviper/iface/viewhelper/MoviperViewHelper.java b/moviper/src/main/java/com/mateuszkoslacz/moviper/iface/viewhelper/MoviperViewHelper.java new file mode 100644 index 0000000..91fe52c --- /dev/null +++ b/moviper/src/main/java/com/mateuszkoslacz/moviper/iface/viewhelper/MoviperViewHelper.java @@ -0,0 +1,13 @@ +package com.mateuszkoslacz.moviper.iface.viewhelper; + +/** + * Created by mateuszkoslacz on 09.08.2016. + *

+ * It's used for getting Android Views (ImageView, TextView etc.) for Routing (see + * {@link com.mateuszkoslacz.moviper.iface.routing.MoviperRouting}) to allow it perform + * Android Transitions with shared Views. + *

+ * You should NOT manipulate views given by ViewHelper in any way! + */ +public interface MoviperViewHelper { +} diff --git a/moviper/src/main/java/com/mateuszkoslacz/moviper/util/WeakReferenceUtils.java b/moviper/src/main/java/com/mateuszkoslacz/moviper/util/WeakReferenceUtils.java new file mode 100644 index 0000000..52f6b99 --- /dev/null +++ b/moviper/src/main/java/com/mateuszkoslacz/moviper/util/WeakReferenceUtils.java @@ -0,0 +1,27 @@ +package com.mateuszkoslacz.moviper.util; + +import android.support.annotation.Nullable; + +import java.lang.ref.WeakReference; + +/** + * Created by mateuszkoslacz on 21.08.2016. + */ +public class WeakReferenceUtils { + + @Nullable + public static T get(@Nullable WeakReference weakReferenceToGetFrom) { + return weakReferenceToGetFrom == null ? null : weakReferenceToGetFrom.get(); + } + + public static void detach(@Nullable WeakReference weakReferenceToClear) { + if (weakReferenceToClear != null) { + weakReferenceToClear.clear(); + weakReferenceToClear = null; + } + } + + public static boolean isAttached(@Nullable WeakReference weakReferenceToCheck) { + return weakReferenceToCheck != null && weakReferenceToCheck.get() != null; + } +} diff --git a/moviper/src/main/res/values/strings.xml b/moviper/src/main/res/values/strings.xml new file mode 100644 index 0000000..5f1c25c --- /dev/null +++ b/moviper/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + Moviper + diff --git a/moviper/src/test/java/com/mateuszkoslacz/moviper/ExampleUnitTest.java b/moviper/src/test/java/com/mateuszkoslacz/moviper/ExampleUnitTest.java new file mode 100644 index 0000000..cfde71b --- /dev/null +++ b/moviper/src/test/java/com/mateuszkoslacz/moviper/ExampleUnitTest.java @@ -0,0 +1,15 @@ +package com.mateuszkoslacz.moviper; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * To work on unit tests, switch the Test Artifact in the Build Variants view. + */ +public class ExampleUnitTest { + @Test + public void addition_isCorrect() throws Exception { + assertEquals(4, 2 + 2); + } +} \ No newline at end of file diff --git a/sample/.gitignore b/sample/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/sample/.gitignore @@ -0,0 +1 @@ +/build diff --git a/sample/build.gradle b/sample/build.gradle new file mode 100644 index 0000000..9c16c11 --- /dev/null +++ b/sample/build.gradle @@ -0,0 +1,65 @@ +apply plugin: 'com.android.application' +apply plugin: 'me.tatarka.retrolambda' +apply plugin: 'android-apt' + + +android { + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + compileSdkVersion 24 + buildToolsVersion "24.0.1" + + defaultConfig { + applicationId "com.mateuszkoslacz.moviper.sample" + minSdkVersion 21 + targetSdkVersion 24 + versionCode 1 + versionName "1.0" + vectorDrawables.useSupportLibrary = true + } + buildTypes { + release { + minifyEnabled true + fileTree(dir: 'proguard-rules', include: ['*.pro']).each { File file -> + proguardFile file + } + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + debug { + minifyEnabled false + fileTree(dir: 'proguard-rules', include: ['*.pro']).each { File file -> + proguardFile file + } + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) + testCompile 'junit:junit:4.12' + + compile 'com.android.support:appcompat-v7:24.2.0' + compile 'com.android.support:design:24.2.0' + compile 'com.android.support:support-v4:24.2.0' + + compile 'com.google.android.gms:play-services-location:9.4.0' + + debugCompile 'com.squareup.leakcanary:leakcanary-android:1.4-beta2' + releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta2' + testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta2' + + compile 'com.jakewharton:butterknife:8.2.1' + + retrolambdaConfig 'net.orfjackal.retrolambda:retrolambda:2.3.0' + + compile 'com.parse:parse-android:1.13.1' + + apt 'com.jakewharton:butterknife-compiler:8.2.1' + + compile project(path: ':moviper') +} diff --git a/sample/proguard-rules.pro b/sample/proguard-rules.pro new file mode 100644 index 0000000..c9d9908 --- /dev/null +++ b/sample/proguard-rules.pro @@ -0,0 +1,17 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /Users/mateuszkoslacz/Library/Android/sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/sample/proguard-rules/proguard-butterknife-7.pro b/sample/proguard-rules/proguard-butterknife-7.pro new file mode 100644 index 0000000..a04d9bc --- /dev/null +++ b/sample/proguard-rules/proguard-butterknife-7.pro @@ -0,0 +1,13 @@ +# ButterKnife 7 + +-keep class butterknife.** { *; } +-dontwarn butterknife.internal.** +-keep class **$$ViewBinder { *; } + +-keepclasseswithmembernames class * { + @butterknife.* ; +} + +-keepclasseswithmembernames class * { + @butterknife.* ; +} \ No newline at end of file diff --git a/sample/proguard-rules/proguard-support-design.pro b/sample/proguard-rules/proguard-support-design.pro new file mode 100644 index 0000000..8883f26 --- /dev/null +++ b/sample/proguard-rules/proguard-support-design.pro @@ -0,0 +1,4 @@ +-dontwarn android.support.design.** +-keep class android.support.design.** { *; } +-keep interface android.support.design.** { *; } +-keep public class android.support.design.R$* { *; } \ No newline at end of file diff --git a/sample/proguard-rules/proguard-support-v7-appcompat.pro b/sample/proguard-rules/proguard-support-v7-appcompat.pro new file mode 100644 index 0000000..65068d0 --- /dev/null +++ b/sample/proguard-rules/proguard-support-v7-appcompat.pro @@ -0,0 +1,7 @@ +-keep public class android.support.v7.widget.** { *; } +-keep public class android.support.v7.internal.widget.** { *; } +-keep public class android.support.v7.internal.view.menu.** { *; } + +-keep public class * extends android.support.v4.view.ActionProvider { + public (android.content.Context); +} diff --git a/sample/proguard-rules/proguard-support-v7-cardview.pro b/sample/proguard-rules/proguard-support-v7-cardview.pro new file mode 100644 index 0000000..e605293 --- /dev/null +++ b/sample/proguard-rules/proguard-support-v7-cardview.pro @@ -0,0 +1,2 @@ +# http://stackoverflow.com/questions/29679177/cardview-shadow-not-appearing-in-lollipop-after-obfuscate-with-proguard/29698051 +-keep class android.support.v7.widget.RoundRectDrawable { *; } diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml new file mode 100644 index 0000000..1c970e1 --- /dev/null +++ b/sample/src/main/AndroidManifest.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/sample/src/main/java/com/mateuszkoslacz/moviper/sample/SampleApp.java b/sample/src/main/java/com/mateuszkoslacz/moviper/sample/SampleApp.java new file mode 100644 index 0000000..fc76229 --- /dev/null +++ b/sample/src/main/java/com/mateuszkoslacz/moviper/sample/SampleApp.java @@ -0,0 +1,19 @@ +package com.mateuszkoslacz.moviper.sample; + +import android.app.Application; + +import com.mateuszkoslacz.moviper.sample.data.parse.util.ParseInitializator; +import com.squareup.leakcanary.LeakCanary; + +/** + * Created by mateuszkoslacz on 12.08.2016. + */ +public class SampleApp extends Application { + + @Override + public void onCreate() { + super.onCreate(); + ParseInitializator.init(this); + LeakCanary.install(this); // to ensure that Moviper does not introduce any leaks + } +} diff --git a/sample/src/main/java/com/mateuszkoslacz/moviper/sample/config/ParseConsts.java b/sample/src/main/java/com/mateuszkoslacz/moviper/sample/config/ParseConsts.java new file mode 100644 index 0000000..723386c --- /dev/null +++ b/sample/src/main/java/com/mateuszkoslacz/moviper/sample/config/ParseConsts.java @@ -0,0 +1,16 @@ +package com.mateuszkoslacz.moviper.sample.config; + +/** + * Created by mateuszkoslacz on 12.08.2016. + *

+ * If you want to see how logging in and stuff works in action you have to run your own + * parse local instance described here and change constants in + * this class to point there. This sample should be probably migrated to some mocked up db-based + * local logging in, but actually it's not needed to understand concepts of viper architecture. + */ +public class ParseConsts { + + public static final String SERVER_URL = "http://your.parse.server.com"; + public static final String PARSE_PATH = "/parse"; + public static final String APP_ID = "myAppId"; +} diff --git a/sample/src/main/java/com/mateuszkoslacz/moviper/sample/data/bundle/LocationPoint.java b/sample/src/main/java/com/mateuszkoslacz/moviper/sample/data/bundle/LocationPoint.java new file mode 100644 index 0000000..f4bf33a --- /dev/null +++ b/sample/src/main/java/com/mateuszkoslacz/moviper/sample/data/bundle/LocationPoint.java @@ -0,0 +1,58 @@ +package com.mateuszkoslacz.moviper.sample.data.bundle; + +/** + * Created by mateuszkoslacz on 12.08.2016. + *

+ * To be used in the business logic layer to separate it from view and data layer specific objects. + */ +public class LocationPoint { + + private final double latitude; + private final double longitude; + private final boolean unknown; + + /** + * A null object constructor. + */ + public LocationPoint() { + unknown = true; + latitude = Double.MIN_VALUE; + longitude = Double.MIN_VALUE; + } + + public LocationPoint(double latitudeDegrees, double longitudeDegrees) { + unknown = false; + this.latitude = latitudeDegrees; + this.longitude = longitudeDegrees; + } + + public boolean isUnknown() { + return unknown; + } + + /** + * It will throw a RuntimeException if location is unknown. Always check + * LocationPoint#isUnknown() before invoking this method. + * + * @return + */ + public double getLatitude() { + if (isUnknown()) throw new RuntimeException("cannot get latitude, location is unknown! " + + "Make sure that you have checked LocationPoint#isUnknown() " + + "before trying to get latitude"); + return latitude; + } + + /** + * It will throw a RuntimeException if location is unknown. Always check + * LocationPoint#isUnknown() before invoking this method. + * + * @return + */ + public double getLongitude() { + if (isUnknown()) throw new RuntimeException("cannot get longitude, location is unknown! " + + "Make sure that you have checked LocationPoint#isUnknown() " + + "before trying to get longitude"); + return longitude; + } +} diff --git a/sample/src/main/java/com/mateuszkoslacz/moviper/sample/data/bundle/RegisterBundle.java b/sample/src/main/java/com/mateuszkoslacz/moviper/sample/data/bundle/RegisterBundle.java new file mode 100644 index 0000000..0db5000 --- /dev/null +++ b/sample/src/main/java/com/mateuszkoslacz/moviper/sample/data/bundle/RegisterBundle.java @@ -0,0 +1,69 @@ +package com.mateuszkoslacz.moviper.sample.data.bundle; + + +import com.mateuszkoslacz.moviper.sample.util.StringUtils; + +/** + * Created by mateuszkoslacz on 12.08.2016. + *

+ * Created to avoid passing 3 arguments to register methods. + */ +public class RegisterBundle { + + private final String login; + private final String email; + private final String password; + + private RegisterBundle(Builder builder) { + login = builder.login; + email = builder.email; + password = builder.password; + } + + public String getLogin() { + return login; + } + + public String getEmail() { + return email; + } + + public String getPassword() { + return password; + } + + public boolean isComplete() { + return !StringUtils.isNullOrEmpty(login) && + !StringUtils.isNullOrEmpty(email) && + !StringUtils.isNullOrEmpty(password); + } + + public static final class Builder { + private String login; + private String email; + private String password; + + public Builder() { + } + + public Builder withLogin(String login) { + this.login = login; + return this; + } + + public Builder withEmail(String email) { + this.email = email; + return this; + } + + public Builder withPassword(String password) { + this.password = password; + return this; + } + + public RegisterBundle build() { + return new RegisterBundle(this); + } + } + +} diff --git a/sample/src/main/java/com/mateuszkoslacz/moviper/sample/data/parse/User.java b/sample/src/main/java/com/mateuszkoslacz/moviper/sample/data/parse/User.java new file mode 100644 index 0000000..8a6d027 --- /dev/null +++ b/sample/src/main/java/com/mateuszkoslacz/moviper/sample/data/parse/User.java @@ -0,0 +1,77 @@ +package com.mateuszkoslacz.moviper.sample.data.parse; + +import com.mateuszkoslacz.moviper.sample.util.StringUtils; +import com.parse.ParseClassName; +import com.parse.ParseGeoPoint; +import com.parse.ParseUser; + +/** + * Created by mateuszkoslacz on 11.08.2016. + */ +@ParseClassName(User.PARSE_CLASS_NAME) +public class User extends ParseUser { + + public static final String PARSE_CLASS_NAME = "_User"; + + public static final String C_LOCALIZATION_PGP = "currentPosition"; + + // just for reference + private static ParseGeoPoint localization; + + public ParseGeoPoint getLocalization() { + return getParseGeoPoint(C_LOCALIZATION_PGP); + } + + public void setLocalization(ParseGeoPoint localization) { + put(C_LOCALIZATION_PGP, localization); + } + + public static class Builder { + private String login; + private String email; + private String password; + private ParseGeoPoint localization; + + + public Builder withLogin(String login) { + this.login = login; + return this; + } + + public Builder withEmail(String email) { + this.email = email; + return this; + } + + public Builder withPassword(String password) { + this.password = password; + return this; + } + + public Builder withLocalization(ParseGeoPoint localization) { + this.localization = localization; + return this; + } + + public User build() { + interruptEarlyIfNoEssentialDataProvided(); + User user = ParseUser.create(User.class); + user.setUsername(login); + user.setEmail(email); + user.setPassword(password); + if (localization != null) user.setLocalization(localization); + return user; + } + + private void interruptEarlyIfNoEssentialDataProvided() { + if (StringUtils.isNullOrEmpty(login) || + StringUtils.isNullOrEmpty(password) || + StringUtils.isNullOrEmpty(email)) { + throw new RuntimeException( + String.format("No essential data provided! " + + "login: %s, password: %s, email: %s", + login, password, email)); + } + } + } +} diff --git a/sample/src/main/java/com/mateuszkoslacz/moviper/sample/data/parse/util/ParseInitializator.java b/sample/src/main/java/com/mateuszkoslacz/moviper/sample/data/parse/util/ParseInitializator.java new file mode 100644 index 0000000..f5c15e8 --- /dev/null +++ b/sample/src/main/java/com/mateuszkoslacz/moviper/sample/data/parse/util/ParseInitializator.java @@ -0,0 +1,33 @@ +package com.mateuszkoslacz.moviper.sample.data.parse.util; + +import android.app.Application; + +import com.mateuszkoslacz.moviper.sample.config.ParseConsts; +import com.mateuszkoslacz.moviper.sample.data.parse.User; +import com.parse.Parse; +import com.parse.ParseObject; + +/** + * Created by mateuszkoslacz on 12.08.2016. + */ +public class ParseInitializator { + + public static void init(Application app) { + registerSubclasses(); + initializeParse(app); + } + + /** + * Remember to register all of your subclasses here. + */ + private static void registerSubclasses() { + ParseObject.registerSubclass(User.class); + } + + private static void initializeParse(Application app) { + Parse.initialize(new Parse.Configuration.Builder(app) + .applicationId(ParseConsts.APP_ID) + .server(ParseConsts.SERVER_URL + ParseConsts.PARSE_PATH) + .build()); + } +} diff --git a/sample/src/main/java/com/mateuszkoslacz/moviper/sample/util/LocationUtils.java b/sample/src/main/java/com/mateuszkoslacz/moviper/sample/util/LocationUtils.java new file mode 100644 index 0000000..4fee2f0 --- /dev/null +++ b/sample/src/main/java/com/mateuszkoslacz/moviper/sample/util/LocationUtils.java @@ -0,0 +1,24 @@ +package com.mateuszkoslacz.moviper.sample.util; + +import com.mateuszkoslacz.moviper.sample.data.bundle.LocationPoint; + +/** + * Created by mateuszkoslacz on 14.08.2016. + */ +public interface LocationUtils { + + int PERMISSION_REQUEST_ACCES_FINE_LOCATION = 123; + + void subscribeToGetLocalization(); + + // stuff caused by Android need of registering permission result listener being + // a fragment or activity + void onRequestLocalizationPermissionsResult(boolean granted); + + interface LocationRequestsCallback { + + void onLocalizationAquired(LocationPoint location); + + void onLocalizationAquireFailed(String message); + } +} diff --git a/sample/src/main/java/com/mateuszkoslacz/moviper/sample/util/LocationUtilsImpl.java b/sample/src/main/java/com/mateuszkoslacz/moviper/sample/util/LocationUtilsImpl.java new file mode 100644 index 0000000..803d29d --- /dev/null +++ b/sample/src/main/java/com/mateuszkoslacz/moviper/sample/util/LocationUtilsImpl.java @@ -0,0 +1,138 @@ +package com.mateuszkoslacz.moviper.sample.util; + +import android.Manifest; +import android.app.Activity; +import android.content.pm.PackageManager; +import android.location.Location; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.app.ActivityCompat; + +import com.google.android.gms.common.ConnectionResult; +import com.google.android.gms.common.api.GoogleApiClient; +import com.google.android.gms.location.LocationServices; +import com.mateuszkoslacz.moviper.sample.data.bundle.LocationPoint; + +/** + * Created by mateuszkoslacz on 14.08.2016. + */ +public class LocationUtilsImpl + implements + LocationUtils, + GoogleApiClient.ConnectionCallbacks, + GoogleApiClient.OnConnectionFailedListener { + + private Activity mActivity; + private GoogleApiClient mGoogleApiClient; + private LocationRequestsCallback mCallback; + + public LocationUtilsImpl(Activity mActivity, LocationRequestsCallback mCallback) { + this.mActivity = mActivity; + this.mCallback = mCallback; + } + + @Override + public void subscribeToGetLocalization() { + // TODO migrate to rx approach + if (isActivityAttached()) { + if (mGoogleApiClient == null) { + mGoogleApiClient = new GoogleApiClient.Builder(getActivity()) + .addConnectionCallbacks(this) + .addOnConnectionFailedListener(this) + .addApi(LocationServices.API) + .build(); + } + mGoogleApiClient.connect(); + } + // will call onConnected, onConnectionSuspended, or onConnectionFailed + } + + @Override + public void onRequestLocalizationPermissionsResult(boolean granted) { + if (isCallbackAttached()) { + if (granted) getCallback().onLocalizationAquired(getLastLocation()); + else getCallback().onLocalizationAquireFailed("We need the localization permission!"); + } + } + + @Override + public void onConnected(@Nullable Bundle bundle) { + if (weHavePermissionToLocationAcces()) { + if (isCallbackAttached()) getCallback().onLocalizationAquired(getLastLocation()); +// mGoogleApiClient.disconnect(); // TODO: 15.08.2016 should we uncomment this? + } else { + if (weShouldShowWhyWeNeedUserLocation()) { + // Show an expanation to the user *asynchronously* -- don't block + // this thread waiting for the user's response! After the user + // sees the explanation, try again to request the permission. + } + // else // wrap it onto else block if you are showing the explanation + requestLocationPermission(); + } + } + + @Override + public void onConnectionSuspended(int i) { + //whatever + } + + @Override + public void onConnectionFailed(@NonNull ConnectionResult connectionResult) { + if (isCallbackAttached()) + getCallback().onLocalizationAquireFailed( + StringUtils.isNullOrEmpty(connectionResult.getErrorMessage()) ? + String.format("Google Play Services Error: %d. Make sure that you have " + + "Google Play Services installed and up-to-date.", + connectionResult.getErrorCode()) : + connectionResult.getErrorMessage()); + } + + private boolean weHavePermissionToLocationAcces() { + return ActivityCompat.checkSelfPermission(getActivity(), + Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED; + } + + private void requestLocationPermission() { + //TODO for now we use activity to handle permission, in future use maybe + // https://github.com/bignerdranch/permission-manager to move the logic to routing + ActivityCompat.requestPermissions(getActivity(), + new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, + PERMISSION_REQUEST_ACCES_FINE_LOCATION); + } + + private boolean weShouldShowWhyWeNeedUserLocation() { + return ActivityCompat.shouldShowRequestPermissionRationale( + getActivity(), Manifest.permission.ACCESS_FINE_LOCATION); + } + + @NonNull + private LocationPoint getLastLocation() { + //noinspection MissingPermission to be checked in upper level + Location lastLocation = LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient); + LocationPoint location; + if (lastLocation != null) { + location = new LocationPoint(lastLocation.getLatitude(), lastLocation.getLongitude()); + } else { + location = new LocationPoint(); + } + return location; + } + + public LocationRequestsCallback getCallback() { + return mCallback; + } + + private boolean isCallbackAttached() { + return mCallback != null; + } + + private Activity getActivity() { + return mActivity; + } + + private boolean isActivityAttached() { + return mActivity != null; + } + +} diff --git a/sample/src/main/java/com/mateuszkoslacz/moviper/sample/util/StringUtils.java b/sample/src/main/java/com/mateuszkoslacz/moviper/sample/util/StringUtils.java new file mode 100644 index 0000000..4a79009 --- /dev/null +++ b/sample/src/main/java/com/mateuszkoslacz/moviper/sample/util/StringUtils.java @@ -0,0 +1,13 @@ +package com.mateuszkoslacz.moviper.sample.util; + +/** + * Created by mateuszkoslacz on 12.08.2016. + *

+ * Created instead of Android TextUtils class to have presentation-layer Java-only + */ +public class StringUtils { + + public static boolean isNullOrEmpty(String string) { + return string == null || string.trim().isEmpty(); + } +} diff --git a/sample/src/main/java/com/mateuszkoslacz/moviper/sample/util/UiUtils.java b/sample/src/main/java/com/mateuszkoslacz/moviper/sample/util/UiUtils.java new file mode 100644 index 0000000..935fe23 --- /dev/null +++ b/sample/src/main/java/com/mateuszkoslacz/moviper/sample/util/UiUtils.java @@ -0,0 +1,21 @@ +package com.mateuszkoslacz.moviper.sample.util; + +import android.app.Activity; +import android.content.Context; +import android.view.View; +import android.view.inputmethod.InputMethodManager; + +/** + * Created by mateuszkoslacz on 21.08.2016. + */ +public class UiUtils { + + public static void hideSoftKeyboard(Activity activity) { + View view = activity.getCurrentFocus(); + if (view != null) { + InputMethodManager imm = + (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(view.getWindowToken(), 0); + } + } +} diff --git a/sample/src/main/java/com/mateuszkoslacz/moviper/sample/util/mapper/LocationPointToParseGeoPointMapper.java b/sample/src/main/java/com/mateuszkoslacz/moviper/sample/util/mapper/LocationPointToParseGeoPointMapper.java new file mode 100644 index 0000000..3a4bc83 --- /dev/null +++ b/sample/src/main/java/com/mateuszkoslacz/moviper/sample/util/mapper/LocationPointToParseGeoPointMapper.java @@ -0,0 +1,15 @@ +package com.mateuszkoslacz.moviper.sample.util.mapper; + +import com.mateuszkoslacz.moviper.sample.data.bundle.LocationPoint; +import com.mateuszkoslacz.moviper.sample.util.mapper.iface.Mapper; +import com.parse.ParseGeoPoint; + +/** + * Created by mateuszkoslacz on 15.08.2016. + */ +public class LocationPointToParseGeoPointMapper implements Mapper { + @Override + public ParseGeoPoint map(LocationPoint locationPoint) { + return new ParseGeoPoint(locationPoint.getLatitude(), locationPoint.getLongitude()); + } +} diff --git a/sample/src/main/java/com/mateuszkoslacz/moviper/sample/util/mapper/iface/Mapper.java b/sample/src/main/java/com/mateuszkoslacz/moviper/sample/util/mapper/iface/Mapper.java new file mode 100644 index 0000000..ea0ee27 --- /dev/null +++ b/sample/src/main/java/com/mateuszkoslacz/moviper/sample/util/mapper/iface/Mapper.java @@ -0,0 +1,8 @@ +package com.mateuszkoslacz.moviper.sample.util.mapper.iface; + +/** + * Created by mateuszkoslacz on 13.08.2016. + */ +public interface Mapper { + To map(From from); +} \ No newline at end of file diff --git a/sample/src/main/java/com/mateuszkoslacz/moviper/sample/viper/contract/AuthorizationContract.java b/sample/src/main/java/com/mateuszkoslacz/moviper/sample/viper/contract/AuthorizationContract.java new file mode 100644 index 0000000..1e153c0 --- /dev/null +++ b/sample/src/main/java/com/mateuszkoslacz/moviper/sample/viper/contract/AuthorizationContract.java @@ -0,0 +1,24 @@ +package com.mateuszkoslacz.moviper.sample.viper.contract; + +import com.hannesdorfmann.mosby.mvp.MvpPresenter; +import com.hannesdorfmann.mosby.mvp.MvpView; + +/** + * Created by mateuszkoslacz on 10.08.2016. + *

+ * It has less interfaces because it's a pure MVP contract. + */ +public interface AuthorizationContract { + + interface Presenter extends MvpPresenter { + + void onUiCreated(); + } + + interface View extends MvpView { + + void showLoginFragment(); + + void showRegisterFragment(); + } +} diff --git a/sample/src/main/java/com/mateuszkoslacz/moviper/sample/viper/contract/LoginContract.java b/sample/src/main/java/com/mateuszkoslacz/moviper/sample/viper/contract/LoginContract.java new file mode 100644 index 0000000..610c5a0 --- /dev/null +++ b/sample/src/main/java/com/mateuszkoslacz/moviper/sample/viper/contract/LoginContract.java @@ -0,0 +1,74 @@ +package com.mateuszkoslacz.moviper.sample.viper.contract; + +import com.hannesdorfmann.mosby.mvp.MvpPresenter; +import com.hannesdorfmann.mosby.mvp.lce.MvpLceView; +import com.mateuszkoslacz.moviper.iface.interactor.MoviperInteractor; +import com.mateuszkoslacz.moviper.iface.presenter.interactor.MoviperPresenterForInteractor; +import com.mateuszkoslacz.moviper.iface.presenter.routing.MoviperFragmentPresenterForRouting; +import com.mateuszkoslacz.moviper.iface.routing.MoviperViewHelperRouting; +import com.mateuszkoslacz.moviper.iface.viewhelper.MoviperViewHelper; + +/** + * Created by mateuszkoslacz on 10.08.2016. + *

+ * See {@link MainContract} to check out the freshly generated contract with usage description. + */ +public interface LoginContract { + + interface Presenter extends MvpPresenter { + + void onLoginClicked(String username, String password); + + void onShowRegisterFragmentClicked(); + } + + interface View extends MvpLceView { + + // all lce methods provided by Mosby MvpLceView + + } + + interface Interactor extends MoviperInteractor { + + void login(String username, String password); + } + + interface PresenterForInteractor extends MoviperPresenterForInteractor { + + /** + * It's called a callback hell. To avoid duplicating (or even tripling if you want to + * handle errors) move to rx approach. + * It will be showed in the future samples. + * + * @param e + */ + void showLoginError(Throwable e); + + /** + * It's called a callback hell. To avoid duplicating (or even tripling if you want to + * handle errors) move to rx approach. + * It will be showed in the future samples. + */ + void proceedAfterLogin(); + } + + interface Routing extends MoviperViewHelperRouting { + + void gotoRegisterFragment(); + + void goToMainActivity(); + + void hideSoftKeyboard(); + } + + interface PresenterForRouting extends MoviperFragmentPresenterForRouting { + + } + + interface ViewHelper extends MoviperViewHelper { + + android.view.View getLoadingLogo(); + } + + +} diff --git a/sample/src/main/java/com/mateuszkoslacz/moviper/sample/viper/contract/MainContract.java b/sample/src/main/java/com/mateuszkoslacz/moviper/sample/viper/contract/MainContract.java new file mode 100644 index 0000000..e5ccdcf --- /dev/null +++ b/sample/src/main/java/com/mateuszkoslacz/moviper/sample/viper/contract/MainContract.java @@ -0,0 +1,65 @@ +package com.mateuszkoslacz.moviper.sample.viper.contract; + +import com.hannesdorfmann.mosby.mvp.MvpPresenter; +import com.hannesdorfmann.mosby.mvp.MvpView; +import com.mateuszkoslacz.moviper.iface.interactor.MoviperInteractor; +import com.mateuszkoslacz.moviper.iface.presenter.interactor.MoviperPresenterForInteractor; +import com.mateuszkoslacz.moviper.iface.presenter.routing.MoviperActivityPresenterForRouting; +import com.mateuszkoslacz.moviper.iface.routing.MoviperViewHelperRouting; +import com.mateuszkoslacz.moviper.iface.viewhelper.MoviperViewHelper; + +/** + * Created by mateuszkoslacz on 10.08.2016. + *

+ * It's an example of a template generator generated Viper contract. There is no single line of code + * added excluding this javadoc comment. All explanations that you can see here are provided in + * generated contract for making contract projecting process easier for developers. + */ +public interface MainContract { + + interface Presenter extends MvpPresenter { + // Defines what methods the View can invoke on the Presenter. + // In most cases there will be user interactions and View lifecycle events. + + } + + interface View extends MvpView { + // Defines what methods the Presenter can invoke on the View + // In most cases there will be manipulating ui and displaying data or errors. + + } + + interface Interactor extends MoviperInteractor { + // Defines what methods the Presenter can invoke on the Interactor. + // In most cases there will be data saving and querying. + + } + + interface PresenterForInteractor extends MoviperPresenterForInteractor { + // Defines what methods the Interactor could invoke on the Presenter. + // In most cases there will be data received callbacks and error notifying. + + } + + interface Routing extends MoviperViewHelperRouting { + // Defines what methods the Presenter can invoke on the Routing. + // In most cases there will be starting another activities, services and using system + // framework, ie. scheduling alarms or sending broadcasts. + // In the case of a fragment being the view, there also will be manipulating + // the root Activity, ie. switching fragments. + + } + + interface PresenterForRouting extends MoviperActivityPresenterForRouting { + // Defines what methods the Routing can invoke on the Presenter. + // In most cases there will be system framework interaction callbacks and error notifying. + + } + + interface ViewHelper extends MoviperViewHelper { + // Defines what Android views the Routing can get from the Viper View. + // There should only be Android View getter methods to provide the Routing elements + // to be used on advanced Android transitions. + + } +} diff --git a/sample/src/main/java/com/mateuszkoslacz/moviper/sample/viper/contract/RegisterContract.java b/sample/src/main/java/com/mateuszkoslacz/moviper/sample/viper/contract/RegisterContract.java new file mode 100644 index 0000000..a7d9cb4 --- /dev/null +++ b/sample/src/main/java/com/mateuszkoslacz/moviper/sample/viper/contract/RegisterContract.java @@ -0,0 +1,89 @@ +package com.mateuszkoslacz.moviper.sample.viper.contract; + +import com.hannesdorfmann.mosby.mvp.MvpPresenter; +import com.hannesdorfmann.mosby.mvp.MvpView; +import com.mateuszkoslacz.moviper.iface.interactor.MoviperInteractor; +import com.mateuszkoslacz.moviper.iface.presenter.interactor.MoviperPresenterForInteractor; +import com.mateuszkoslacz.moviper.iface.presenter.routing.MoviperFragmentPresenterForRouting; +import com.mateuszkoslacz.moviper.iface.routing.MoviperViewHelperRouting; +import com.mateuszkoslacz.moviper.iface.viewhelper.MoviperViewHelper; +import com.mateuszkoslacz.moviper.sample.data.bundle.LocationPoint; +import com.mateuszkoslacz.moviper.sample.data.bundle.RegisterBundle; + +/** + * Created by mateuszkoslacz on 10.08.2016. + *

+ * See {@link MainContract} to check out the freshly generated contract with usage description. + */ +public interface RegisterContract { + + interface Presenter extends MvpPresenter { + + void onRegisterClicked(RegisterBundle bundle); + + void onShowLoginFragmentClicked(); + } + + interface View extends MvpView { + + void showLoadingView(); + + void displayError(String msg); + } + + interface Interactor extends MoviperInteractor { + + void register(RegisterBundle bundle, LocationPoint point); + } + + interface PresenterForInteractor extends MoviperPresenterForInteractor { + + void showRegisterError(String msg); + + void proceedAfterRegister(); + } + + interface Routing extends MoviperViewHelperRouting { + + void gotoLoginFragment(); + + void goToMainActivity(); + + void subscribeToGetLocalization(); + + // stuff caused by Android need of registering permission result listener being + // a fragment or activity + void onRequestLocalizationPermissionsResult(boolean granted); + + void hideSoftKeyboard(); + } + + interface PresenterForRouting extends MoviperFragmentPresenterForRouting { + + // stuff caused by Android need of registering permission result listener being + // a fragment or activity + // IT'S CALLED BY AN ACTIVITY OR A FRAGMENT! + void onRequestLocalizationPermissionsResult(boolean granted); + + /** + * It's called a callback hell. To avoid duplicating (or even tripling if you want to + * handle errors) move to rx approach. + * It will be showed in the future samples. + * + * @param location + */ + void onLocalizationAquired(LocationPoint location); + + /** + * It's called a callback hell. To avoid duplicating (or even tripling if you want to + * handle errors) move to rx approach. + * It will be showed in the future samples. + * + * @param message + */ + void onLocalizationAquireFailed(String message); + } + + interface ViewHelper extends MoviperViewHelper { + } +} diff --git a/sample/src/main/java/com/mateuszkoslacz/moviper/sample/viper/contract/SplashContract.java b/sample/src/main/java/com/mateuszkoslacz/moviper/sample/viper/contract/SplashContract.java new file mode 100644 index 0000000..07e42a6 --- /dev/null +++ b/sample/src/main/java/com/mateuszkoslacz/moviper/sample/viper/contract/SplashContract.java @@ -0,0 +1,60 @@ +package com.mateuszkoslacz.moviper.sample.viper.contract; + +import com.hannesdorfmann.mosby.mvp.MvpPresenter; +import com.hannesdorfmann.mosby.mvp.MvpView; +import com.mateuszkoslacz.moviper.iface.interactor.MoviperInteractor; +import com.mateuszkoslacz.moviper.iface.presenter.interactor.MoviperPresenterForInteractor; +import com.mateuszkoslacz.moviper.iface.presenter.routing.MoviperActivityPresenterForRouting; +import com.mateuszkoslacz.moviper.iface.routing.MoviperViewHelperRouting; +import com.mateuszkoslacz.moviper.iface.viewhelper.MoviperViewHelper; + +/** + * Created by mateuszkoslacz on 10.08.2016. + *

+ * See {@link MainContract} to check out the freshly generated contract with usage description. + */ +public interface SplashContract { + + interface Presenter extends MvpPresenter { + + void onViewLoaded(); + } + + interface View extends MvpView { + + } + + interface Interactor extends MoviperInteractor { + + void subscribeToHasActiveUserSession(); + } + + interface PresenterForInteractor extends MoviperPresenterForInteractor { + + /** + * It's called a callback hell. To avoid duplicating (or even tripling if you want to + * handle errors) move to rx approach. + * It will be showed in the future samples. + * + * @param hasActiveUserSession + */ + void onHasActiveUserSessionResponse(boolean hasActiveUserSession); + + } + + interface Routing extends MoviperViewHelperRouting { + + void goToMainView(); + + void goToAuthorizationView(); + } + + interface PresenterForRouting extends MoviperActivityPresenterForRouting { + + } + + interface ViewHelper extends MoviperViewHelper { + + android.view.View getLogo(); + } +} diff --git a/sample/src/main/java/com/mateuszkoslacz/moviper/sample/viper/interactor/LoginInteractor.java b/sample/src/main/java/com/mateuszkoslacz/moviper/sample/viper/interactor/LoginInteractor.java new file mode 100644 index 0000000..d0cd6e7 --- /dev/null +++ b/sample/src/main/java/com/mateuszkoslacz/moviper/sample/viper/interactor/LoginInteractor.java @@ -0,0 +1,23 @@ +package com.mateuszkoslacz.moviper.sample.viper.interactor; + +import com.mateuszkoslacz.moviper.base.interactor.BaseInteractor; +import com.mateuszkoslacz.moviper.sample.viper.contract.LoginContract; +import com.parse.ParseUser; + +/** + * Created by mateuszkoslacz on 09.08.2016. + */ +public class LoginInteractor + extends BaseInteractor + implements LoginContract.Interactor { + + @Override + public void login(String username, String password) { + ParseUser.logInInBackground(username, password, (user, e) -> { + if (isPresenterAttached()) { + if (e == null) getPresenter().proceedAfterLogin(); + else getPresenter().showLoginError(e); + } + }); + } +} diff --git a/sample/src/main/java/com/mateuszkoslacz/moviper/sample/viper/interactor/MainInteractor.java b/sample/src/main/java/com/mateuszkoslacz/moviper/sample/viper/interactor/MainInteractor.java new file mode 100644 index 0000000..f926223 --- /dev/null +++ b/sample/src/main/java/com/mateuszkoslacz/moviper/sample/viper/interactor/MainInteractor.java @@ -0,0 +1,13 @@ +package com.mateuszkoslacz.moviper.sample.viper.interactor; + +import com.mateuszkoslacz.moviper.base.interactor.BaseInteractor; +import com.mateuszkoslacz.moviper.sample.viper.contract.MainContract; + +/** + * Created by mateuszkoslacz on 09.08.2016. + */ +public class MainInteractor + extends BaseInteractor + implements MainContract.Interactor { + +} diff --git a/sample/src/main/java/com/mateuszkoslacz/moviper/sample/viper/interactor/RegisterInteractor.java b/sample/src/main/java/com/mateuszkoslacz/moviper/sample/viper/interactor/RegisterInteractor.java new file mode 100644 index 0000000..1ce127c --- /dev/null +++ b/sample/src/main/java/com/mateuszkoslacz/moviper/sample/viper/interactor/RegisterInteractor.java @@ -0,0 +1,34 @@ +package com.mateuszkoslacz.moviper.sample.viper.interactor; + +import com.mateuszkoslacz.moviper.base.interactor.BaseInteractor; +import com.mateuszkoslacz.moviper.sample.data.bundle.LocationPoint; +import com.mateuszkoslacz.moviper.sample.data.bundle.RegisterBundle; +import com.mateuszkoslacz.moviper.sample.data.parse.User; +import com.mateuszkoslacz.moviper.sample.util.mapper.LocationPointToParseGeoPointMapper; +import com.mateuszkoslacz.moviper.sample.viper.contract.RegisterContract; + +/** + * Created by mateuszkoslacz on 09.08.2016. + */ +public class RegisterInteractor + extends BaseInteractor + implements RegisterContract.Interactor { + + @Override + public void register(RegisterBundle bundle, LocationPoint localization) { + User.Builder builder = new User.Builder(); + builder.withLogin(bundle.getLogin()); + builder.withEmail(bundle.getEmail()); + builder.withPassword(bundle.getPassword()); + if (!localization.isUnknown()) + builder.withLocalization( + new LocationPointToParseGeoPointMapper().map(localization)); + User newUser = builder.build(); + newUser.signUpInBackground(e -> { + if (isPresenterAttached()) { + if (e != null) getPresenter().showRegisterError(e.getLocalizedMessage()); + else getPresenter().proceedAfterRegister(); + } + }); + } +} diff --git a/sample/src/main/java/com/mateuszkoslacz/moviper/sample/viper/interactor/SplashInteractor.java b/sample/src/main/java/com/mateuszkoslacz/moviper/sample/viper/interactor/SplashInteractor.java new file mode 100644 index 0000000..09ff6af --- /dev/null +++ b/sample/src/main/java/com/mateuszkoslacz/moviper/sample/viper/interactor/SplashInteractor.java @@ -0,0 +1,39 @@ +package com.mateuszkoslacz.moviper.sample.viper.interactor; + +import com.mateuszkoslacz.moviper.base.interactor.BaseInteractor; +import com.mateuszkoslacz.moviper.sample.viper.contract.SplashContract; +import com.parse.ParseUser; + +import java.util.Timer; +import java.util.TimerTask; + +/** + * Created by mateuszkoslacz on 09.08.2016. + */ +public class SplashInteractor + extends BaseInteractor + implements SplashContract.Interactor { + + /** + * There is an example how to suppress yellow lint remarks because of probable + * NullPointerException (the "//noinspection ConstantConditions" part). I skipped this + * in the rest of the sample to improve readability. + */ + @Override + public void subscribeToHasActiveUserSession() { + // simulate heavy work + // not using Android Runnable to make this separated from the platform framework + new Timer().schedule( + new TimerTask() { + @Override + public void run() { + if (isPresenterAttached()) + //noinspection ConstantConditions because we checked isPresenterAttached() + getPresenter().onHasActiveUserSessionResponse( + ParseUser.getCurrentUser() != null); + } + }, + 3000 + ); + } +} diff --git a/sample/src/main/java/com/mateuszkoslacz/moviper/sample/viper/presenter/AuthorizationPresenter.java b/sample/src/main/java/com/mateuszkoslacz/moviper/sample/viper/presenter/AuthorizationPresenter.java new file mode 100644 index 0000000..beabb65 --- /dev/null +++ b/sample/src/main/java/com/mateuszkoslacz/moviper/sample/viper/presenter/AuthorizationPresenter.java @@ -0,0 +1,17 @@ +package com.mateuszkoslacz.moviper.sample.viper.presenter; + +import com.hannesdorfmann.mosby.mvp.MvpBasePresenter; +import com.mateuszkoslacz.moviper.sample.viper.contract.AuthorizationContract; + +/** + * Created by mateuszkoslacz on 08.08.2016. + */ +public class AuthorizationPresenter + extends MvpBasePresenter + implements AuthorizationContract.Presenter { + + @Override + public void onUiCreated() { + if (isViewAttached()) getView().showLoginFragment(); + } +} diff --git a/sample/src/main/java/com/mateuszkoslacz/moviper/sample/viper/presenter/LoginPresenter.java b/sample/src/main/java/com/mateuszkoslacz/moviper/sample/viper/presenter/LoginPresenter.java new file mode 100644 index 0000000..63419db --- /dev/null +++ b/sample/src/main/java/com/mateuszkoslacz/moviper/sample/viper/presenter/LoginPresenter.java @@ -0,0 +1,89 @@ +package com.mateuszkoslacz.moviper.sample.viper.presenter; + +import android.support.annotation.NonNull; +import android.support.v4.app.Fragment; + +import com.mateuszkoslacz.moviper.base.presenter.ViperFragmentBasePresenter; +import com.mateuszkoslacz.moviper.sample.util.StringUtils; +import com.mateuszkoslacz.moviper.sample.viper.contract.LoginContract; +import com.mateuszkoslacz.moviper.sample.viper.interactor.LoginInteractor; +import com.mateuszkoslacz.moviper.sample.viper.routing.LoginRouting; + +/** + * Created by mateuszkoslacz on 08.08.2016. + */ +public class LoginPresenter + extends ViperFragmentBasePresenter + implements + LoginContract.Presenter, + LoginContract.PresenterForInteractor, + LoginContract.PresenterForRouting { + + + // I DO NOT encourage block comments in non-sample code! Organize your code + // in the self-explaining way and block comments will become redundant. + /////////////////////////////////////////////////////////////////////////// + // initializers + /////////////////////////////////////////////////////////////////////////// + + public LoginPresenter(Fragment fragment) { + super(fragment); + } + + @NonNull + @Override + public LoginContract.Interactor createInteractor() { + return new LoginInteractor(); + } + + @NonNull + @Override + public LoginContract.Routing createRouting(@NonNull Fragment fragment) { + return new LoginRouting(fragment); + } + + + /////////////////////////////////////////////////////////////////////////// + // logic + /////////////////////////////////////////////////////////////////////////// + + /** + * I'm not using Android string resources here to detach the Presenter from system Framework, + * but actually the View should know what exactly to display and the Presenter should have no + * strings at all. It will be changed in future. For more info see + * here + * + * @param username + * @param password + */ + @Override + public void onLoginClicked(String username, String password) { + if (isRoutingAttached()) getRouting().hideSoftKeyboard(); + if (StringUtils.isNullOrEmpty(username) || StringUtils.isNullOrEmpty(password)) { + if (isViewAttached()) + getView().showError(new Exception("Provide login and password!"), false); + } else { + if (isViewAttached()) getView().showLoading(false); + if (isInteractorAttached()) getInteractor().login(username, password); + } + } + + @Override + public void onShowRegisterFragmentClicked() { + if (isRoutingAttached()) getRouting().gotoRegisterFragment(); + } + + @Override + public void showLoginError(Throwable e) { + if (isViewAttached()) getView().showError(e, false); + } + + @Override + public void proceedAfterLogin() { + if (isRoutingAttached()) getRouting().goToMainActivity(); + } + + +} diff --git a/sample/src/main/java/com/mateuszkoslacz/moviper/sample/viper/presenter/MainPresenter.java b/sample/src/main/java/com/mateuszkoslacz/moviper/sample/viper/presenter/MainPresenter.java new file mode 100644 index 0000000..9a79482 --- /dev/null +++ b/sample/src/main/java/com/mateuszkoslacz/moviper/sample/viper/presenter/MainPresenter.java @@ -0,0 +1,39 @@ +package com.mateuszkoslacz.moviper.sample.viper.presenter; + +import android.app.Activity; +import android.support.annotation.NonNull; + +import com.mateuszkoslacz.moviper.base.presenter.ViperActivityBasePresenter; +import com.mateuszkoslacz.moviper.sample.viper.contract.MainContract; +import com.mateuszkoslacz.moviper.sample.viper.interactor.MainInteractor; +import com.mateuszkoslacz.moviper.sample.viper.routing.MainRouting; + +/** + * Created by mateuszkoslacz on 08.08.2016. + */ +public class MainPresenter + extends ViperActivityBasePresenter + implements + MainContract.Presenter, + MainContract.PresenterForInteractor, + MainContract.PresenterForRouting { + + + public MainPresenter(Activity activity) { + super(activity); + } + + @NonNull + @Override + public MainContract.Interactor createInteractor() { + return new MainInteractor(); + } + + @NonNull + @Override + public MainContract.Routing createRouting(@NonNull Activity activity) { + return new MainRouting(activity); + } +} diff --git a/sample/src/main/java/com/mateuszkoslacz/moviper/sample/viper/presenter/RegisterPresenter.java b/sample/src/main/java/com/mateuszkoslacz/moviper/sample/viper/presenter/RegisterPresenter.java new file mode 100644 index 0000000..1d357e6 --- /dev/null +++ b/sample/src/main/java/com/mateuszkoslacz/moviper/sample/viper/presenter/RegisterPresenter.java @@ -0,0 +1,96 @@ +package com.mateuszkoslacz.moviper.sample.viper.presenter; + +import android.support.annotation.NonNull; +import android.support.v4.app.Fragment; + +import com.mateuszkoslacz.moviper.base.presenter.ViperFragmentBasePresenter; +import com.mateuszkoslacz.moviper.sample.data.bundle.LocationPoint; +import com.mateuszkoslacz.moviper.sample.data.bundle.RegisterBundle; +import com.mateuszkoslacz.moviper.sample.viper.contract.RegisterContract; +import com.mateuszkoslacz.moviper.sample.viper.interactor.RegisterInteractor; +import com.mateuszkoslacz.moviper.sample.viper.routing.RegisterRouting; + +/** + * Created by mateuszkoslacz on 08.08.2016. + */ +public class RegisterPresenter + extends ViperFragmentBasePresenter + implements RegisterContract.Presenter, + RegisterContract.PresenterForInteractor, + RegisterContract.PresenterForRouting { + + private RegisterBundle cachedRegisterBundle; + + + // I DO NOT encourage block comments in non-sample code! Organize your code + // in the self-explaining way and block comments will become redundant. + /////////////////////////////////////////////////////////////////////////// + // initializers + /////////////////////////////////////////////////////////////////////////// + + public RegisterPresenter(Fragment fragment) { + super(fragment); + } + + @NonNull + @Override + public RegisterContract.Routing createRouting(@NonNull Fragment fragment) { + return new RegisterRouting(fragment); + } + + @NonNull + @Override + public RegisterContract.Interactor createInteractor() { + return new RegisterInteractor(); + } + + + /////////////////////////////////////////////////////////////////////////// + // logic + /////////////////////////////////////////////////////////////////////////// + + @Override + public void onRegisterClicked(RegisterBundle bundle) { + if (isRoutingAttached()) getRouting().hideSoftKeyboard(); + if (!bundle.isComplete()) { // it's not needed with Parse, just for illustration purposes + if (isViewAttached()) getView().displayError("Provide login, email and password!"); + } else { + if (isViewAttached()) getView().showLoadingView(); + cachedRegisterBundle = bundle; + if (isRoutingAttached()) getRouting().subscribeToGetLocalization(); + } + } + + @Override + public void onShowLoginFragmentClicked() { + if (isRoutingAttached()) getRouting().gotoLoginFragment(); + } + + @Override + public void showRegisterError(String msg) { + if (isViewAttached()) getView().displayError(msg); + } + + @Override + public void proceedAfterRegister() { + if (isRoutingAttached()) getRouting().goToMainActivity(); + } + + @Override + public void onRequestLocalizationPermissionsResult(boolean granted) { + if (isRoutingAttached()) getRouting().onRequestLocalizationPermissionsResult(granted); + } + + @Override + public void onLocalizationAquired(LocationPoint location) { + if (isInteractorAttached()) getInteractor().register(cachedRegisterBundle, location); + } + + @Override + public void onLocalizationAquireFailed(String message) { + if (isViewAttached()) getView().displayError(message); + } + +} diff --git a/sample/src/main/java/com/mateuszkoslacz/moviper/sample/viper/presenter/SplashPresenter.java b/sample/src/main/java/com/mateuszkoslacz/moviper/sample/viper/presenter/SplashPresenter.java new file mode 100644 index 0000000..9de7b94 --- /dev/null +++ b/sample/src/main/java/com/mateuszkoslacz/moviper/sample/viper/presenter/SplashPresenter.java @@ -0,0 +1,63 @@ +package com.mateuszkoslacz.moviper.sample.viper.presenter; + +import android.app.Activity; +import android.support.annotation.NonNull; + +import com.mateuszkoslacz.moviper.base.presenter.ViperActivityBasePresenter; +import com.mateuszkoslacz.moviper.sample.viper.contract.SplashContract; +import com.mateuszkoslacz.moviper.sample.viper.interactor.SplashInteractor; +import com.mateuszkoslacz.moviper.sample.viper.routing.SplashRouting; + +/** + * Created by mateuszkoslacz on 08.08.2016. + */ +public class SplashPresenter + extends ViperActivityBasePresenter + implements + SplashContract.Presenter, + SplashContract.PresenterForInteractor, + SplashContract.PresenterForRouting { + + + // I DO NOT encourage block comments in non-sample code! Organize your code + // in the self-explaining way and block comments will become redundant. + /////////////////////////////////////////////////////////////////////////// + // initializers + /////////////////////////////////////////////////////////////////////////// + + public SplashPresenter(Activity activity) { + super(activity); + } + + @NonNull + @Override + public SplashContract.Interactor createInteractor() { + return new SplashInteractor(); + } + + @NonNull + @Override + public SplashContract.Routing createRouting(@NonNull Activity activity) { + return new SplashRouting(activity); + } + + + /////////////////////////////////////////////////////////////////////////// + // logic + /////////////////////////////////////////////////////////////////////////// + + @Override + public void onViewLoaded() { + if (isInteractorAttached()) getInteractor().subscribeToHasActiveUserSession(); + } + + @Override + public void onHasActiveUserSessionResponse(boolean hasActiveUserSession) { + if (isRoutingAttached()) { + if (hasActiveUserSession) getRouting().goToMainView(); + else getRouting().goToAuthorizationView(); + } + } +} diff --git a/sample/src/main/java/com/mateuszkoslacz/moviper/sample/viper/routing/LoginRouting.java b/sample/src/main/java/com/mateuszkoslacz/moviper/sample/viper/routing/LoginRouting.java new file mode 100644 index 0000000..75c5f14 --- /dev/null +++ b/sample/src/main/java/com/mateuszkoslacz/moviper/sample/viper/routing/LoginRouting.java @@ -0,0 +1,42 @@ +package com.mateuszkoslacz.moviper.sample.viper.routing; + +import android.support.v4.app.Fragment; + +import com.mateuszkoslacz.moviper.base.routing.FragmentBaseViewHelperRouting; +import com.mateuszkoslacz.moviper.sample.util.UiUtils; +import com.mateuszkoslacz.moviper.sample.viper.contract.AuthorizationContract; +import com.mateuszkoslacz.moviper.sample.viper.contract.LoginContract; +import com.mateuszkoslacz.moviper.sample.viper.view.activity.MainActivity; + +/** + * Created by mateuszkoslacz on 09.08.2016. + */ +public class LoginRouting + extends FragmentBaseViewHelperRouting< + LoginContract.PresenterForRouting, + LoginContract.ViewHelper> + implements LoginContract.Routing { + + public LoginRouting(Fragment fragment) { + super(fragment); + } + + @Override + public void gotoRegisterFragment() { + if (isActivityAttached()) + ((AuthorizationContract.View) getActivity()).showRegisterFragment(); + } + + @Override + public void goToMainActivity() { + if (isActivityAttached()) { + getActivity().runOnUiThread(() -> + MainActivity.startWithTransition(getActivity(), getViewHelper().getLoadingLogo())); + } + } + + @Override + public void hideSoftKeyboard() { + if (isActivityAttached()) UiUtils.hideSoftKeyboard(getActivity()); + } +} diff --git a/sample/src/main/java/com/mateuszkoslacz/moviper/sample/viper/routing/MainRouting.java b/sample/src/main/java/com/mateuszkoslacz/moviper/sample/viper/routing/MainRouting.java new file mode 100644 index 0000000..947d421 --- /dev/null +++ b/sample/src/main/java/com/mateuszkoslacz/moviper/sample/viper/routing/MainRouting.java @@ -0,0 +1,21 @@ +package com.mateuszkoslacz.moviper.sample.viper.routing; + +import android.app.Activity; +import android.support.annotation.NonNull; + +import com.mateuszkoslacz.moviper.base.routing.ActivityBaseViewHelperRouting; +import com.mateuszkoslacz.moviper.sample.viper.contract.MainContract; + +/** + * Created by mateuszkoslacz on 09.08.2016. + */ +public class MainRouting + extends ActivityBaseViewHelperRouting< + MainContract.PresenterForRouting, + MainContract.ViewHelper> + implements MainContract.Routing { + + public MainRouting(@NonNull Activity activity) { + super(activity); + } +} diff --git a/sample/src/main/java/com/mateuszkoslacz/moviper/sample/viper/routing/RegisterRouting.java b/sample/src/main/java/com/mateuszkoslacz/moviper/sample/viper/routing/RegisterRouting.java new file mode 100644 index 0000000..3460651 --- /dev/null +++ b/sample/src/main/java/com/mateuszkoslacz/moviper/sample/viper/routing/RegisterRouting.java @@ -0,0 +1,92 @@ +package com.mateuszkoslacz.moviper.sample.viper.routing; + +import android.support.annotation.NonNull; +import android.support.v4.app.Fragment; + +import com.mateuszkoslacz.moviper.base.routing.FragmentBaseViewHelperRouting; +import com.mateuszkoslacz.moviper.sample.data.bundle.LocationPoint; +import com.mateuszkoslacz.moviper.sample.util.LocationUtils; +import com.mateuszkoslacz.moviper.sample.util.LocationUtilsImpl; +import com.mateuszkoslacz.moviper.sample.util.UiUtils; +import com.mateuszkoslacz.moviper.sample.viper.contract.AuthorizationContract; +import com.mateuszkoslacz.moviper.sample.viper.contract.RegisterContract; +import com.mateuszkoslacz.moviper.sample.viper.view.activity.MainActivity; + + +/** + * Created by mateuszkoslacz on 09.08.2016. + */ +// TODO change behaviour to allow user registering without getting a localization +public class RegisterRouting + extends FragmentBaseViewHelperRouting< + RegisterContract.PresenterForRouting, + RegisterContract.ViewHelper> + implements + RegisterContract.Routing, + LocationUtils.LocationRequestsCallback { + + private LocationUtils mLocationUtils; + + public RegisterRouting(@NonNull Fragment fragment) { + super(fragment); + } + + @Override + public void gotoLoginFragment() { + if (isActivityAttached()) + ((AuthorizationContract.View) getActivity()).showLoginFragment(); + } + + @Override + public void goToMainActivity() { + if (isActivityAttached()) + getActivity().runOnUiThread(() -> MainActivity.start(getActivity())); + } + + @Override + public void subscribeToGetLocalization() { + // TODO migrate to rx approach + if (isActivityAttached()) { + if (!isLocationUtilsAttached()) + mLocationUtils = new LocationUtilsImpl(getActivity(), this); + mLocationUtils.subscribeToGetLocalization(); + } + } + + @Override + public void onRequestLocalizationPermissionsResult(boolean granted) { + if (isLocationUtilsAttached()) + getLocationUtils().onRequestLocalizationPermissionsResult(granted); + } + + @Override + public void onLocalizationAquired(LocationPoint location) { + if (isPresenterAttached()) getPresenter().onLocalizationAquired(location); + } + + @Override + public void onLocalizationAquireFailed(String message) { + if (isPresenterAttached()) getPresenter().onLocalizationAquireFailed(message); + } + + private LocationUtils getLocationUtils() { + return mLocationUtils; + } + + // it should be "are", but I decided to stick to convention + private boolean isLocationUtilsAttached() { + return mLocationUtils != null; + } + + // TODO: 14.08.2016 will it be ok, ie if presenter could be detached and re-attached? + @Override + public void detachPresenter() { + super.detachPresenter(); + mLocationUtils = null; + } + + @Override + public void hideSoftKeyboard() { + if (isActivityAttached()) UiUtils.hideSoftKeyboard(getActivity()); + } +} diff --git a/sample/src/main/java/com/mateuszkoslacz/moviper/sample/viper/routing/SplashRouting.java b/sample/src/main/java/com/mateuszkoslacz/moviper/sample/viper/routing/SplashRouting.java new file mode 100644 index 0000000..a054598 --- /dev/null +++ b/sample/src/main/java/com/mateuszkoslacz/moviper/sample/viper/routing/SplashRouting.java @@ -0,0 +1,40 @@ +package com.mateuszkoslacz.moviper.sample.viper.routing; + +import android.app.Activity; +import android.support.annotation.NonNull; + +import com.mateuszkoslacz.moviper.base.routing.ActivityBaseViewHelperRouting; +import com.mateuszkoslacz.moviper.sample.viper.contract.SplashContract; +import com.mateuszkoslacz.moviper.sample.viper.view.activity.AuthorizationActivity; +import com.mateuszkoslacz.moviper.sample.viper.view.activity.MainActivity; + +/** + * Created by mateuszkoslacz on 09.08.2016. + */ +public class SplashRouting + extends ActivityBaseViewHelperRouting< + SplashContract.PresenterForRouting, + SplashContract.ViewHelper> + implements SplashContract.Routing { + + + public SplashRouting(@NonNull Activity activity) { + super(activity); + } + + @Override + public void goToMainView() { + //TODO add transition from splash to main + if (isActivityAttached() && isViewHelperAttached()) + getActivity().runOnUiThread(() -> + MainActivity.startWithTransition(getActivity(), getViewHelper().getLogo())); + } + + @Override + public void goToAuthorizationView() { + if (isActivityAttached() && isViewHelperAttached()) + getActivity().runOnUiThread(() -> + AuthorizationActivity.startWithTransiton( + getActivity(), getViewHelper().getLogo())); + } +} diff --git a/sample/src/main/java/com/mateuszkoslacz/moviper/sample/viper/view/activity/AuthorizationActivity.java b/sample/src/main/java/com/mateuszkoslacz/moviper/sample/viper/view/activity/AuthorizationActivity.java new file mode 100644 index 0000000..a3d8d91 --- /dev/null +++ b/sample/src/main/java/com/mateuszkoslacz/moviper/sample/viper/view/activity/AuthorizationActivity.java @@ -0,0 +1,134 @@ +package com.mateuszkoslacz.moviper.sample.viper.view.activity; + +import android.annotation.TargetApi; +import android.app.Activity; +import android.content.Intent; +import android.os.Build; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.app.ActivityOptionsCompat; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentTransaction; +import android.transition.ChangeBounds; +import android.transition.Slide; +import android.view.Gravity; + +import com.hannesdorfmann.mosby.mvp.MvpActivity; +import com.mateuszkoslacz.moviper.sample.R; +import com.mateuszkoslacz.moviper.sample.viper.contract.AuthorizationContract; +import com.mateuszkoslacz.moviper.sample.viper.presenter.AuthorizationPresenter; +import com.mateuszkoslacz.moviper.sample.viper.view.fragment.LoginFragment; +import com.mateuszkoslacz.moviper.sample.viper.view.fragment.RegisterFragment; +import com.mateuszkoslacz.moviper.sample.viper.view.fragment.iface.AuthorizationFragment; + +public class AuthorizationActivity + // you can change base class to any Mosby Activity, ie. MvpLceActivity, MvpViewStateActivity, etc. + extends MvpActivity + implements AuthorizationContract.View { + + LoginFragment mLoginFragment; + RegisterFragment mRegisterFragment; + + // I DO NOT encourage block comments in non-sample code! Organize your code + // in the self-explaining way and block comments will become redundant. + /////////////////////////////////////////////////////////////////////////// + // initializers + /////////////////////////////////////////////////////////////////////////// + + public static void startWithTransiton(Activity activity, android.view.View logo) { + Intent intent = new Intent(activity, AuthorizationActivity.class); + ActivityOptionsCompat options = ActivityOptionsCompat + .makeSceneTransitionAnimation( + activity, + logo, + activity.getString(R.string.logoTransition)); + activity.startActivity(intent, options.toBundle()); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_authorization); + mLoginFragment = (LoginFragment) addTransitions(new LoginFragment(), Gravity.LEFT); + mRegisterFragment = (RegisterFragment) addTransitions(new RegisterFragment(), Gravity.RIGHT); + presenter.onUiCreated(); + } + + + /////////////////////////////////////////////////////////////////////////// + // creating presenter + /////////////////////////////////////////////////////////////////////////// + + @NonNull + @Override + public AuthorizationContract.Presenter createPresenter() { + return new AuthorizationPresenter(); + } + + + /////////////////////////////////////////////////////////////////////////// + // view interface methods + /////////////////////////////////////////////////////////////////////////// + + @Override + public void showRegisterFragment() { + switchFragments(mRegisterFragment, mLoginFragment); + } + + @Override + public void showLoginFragment() { + switchFragments(mLoginFragment, mRegisterFragment); + } + + + /////////////////////////////////////////////////////////////////////////// + // internal methods + /////////////////////////////////////////////////////////////////////////// + + private void switchFragments(@NonNull Fragment fragmentToShow, + @Nullable Fragment fragmentToHide) { + FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction(); + showFragment(fragmentTransaction, fragmentToShow); + hideFragment(fragmentTransaction, fragmentToHide); + fragmentTransaction.commit(); + } + + private void showFragment(@NonNull FragmentTransaction fragmentTransaction, + @NonNull Fragment fragmentToShow) { + if (fragmentToShow.isAdded()) { + //TODO it's not animating using supportFragmentManager + fragmentTransaction.show(fragmentToShow); + } else { + fragmentTransaction.add(R.id.activity_authorization_login_register_fragment_container, + fragmentToShow, + fragmentToShow.getClass().getName()); + } + } + + private void hideFragment(@NonNull FragmentTransaction fragmentTransaction, + @Nullable Fragment fragmentToHide) { + if (fragmentToHide != null && fragmentToHide.isAdded()) { + fragmentTransaction.hide(fragmentToHide); + fragmentTransaction.addSharedElement( + ((AuthorizationFragment) fragmentToHide).getSharedLoginEditText(), + getString(R.string.loginTransition)); + fragmentTransaction.addSharedElement( + ((AuthorizationFragment) fragmentToHide).getSharedPasswordEditText(), + getString(R.string.passwordTransition)); + } + } + + // TODO: 21.08.2016 I have no idea why it does work only for a first time, it works everytime + // when using non-support fragments + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + private Fragment addTransitions(@NonNull Fragment fragment, int slideEdge) { + fragment.setSharedElementEnterTransition(new ChangeBounds()); + fragment.setEnterTransition(new Slide(slideEdge)); + fragment.setReenterTransition(new Slide(slideEdge)); + fragment.setExitTransition(new Slide(slideEdge)); + fragment.setReturnTransition(new Slide(slideEdge)); + fragment.setSharedElementReturnTransition(new ChangeBounds()); + return fragment; + } +} diff --git a/sample/src/main/java/com/mateuszkoslacz/moviper/sample/viper/view/activity/MainActivity.java b/sample/src/main/java/com/mateuszkoslacz/moviper/sample/viper/view/activity/MainActivity.java new file mode 100644 index 0000000..717c3ee --- /dev/null +++ b/sample/src/main/java/com/mateuszkoslacz/moviper/sample/viper/view/activity/MainActivity.java @@ -0,0 +1,74 @@ +package com.mateuszkoslacz.moviper.sample.viper.view.activity; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.support.v4.app.ActivityOptionsCompat; +import android.view.View; + +import com.hannesdorfmann.mosby.mvp.MvpActivity; +import com.mateuszkoslacz.moviper.sample.R; +import com.mateuszkoslacz.moviper.sample.viper.contract.MainContract; +import com.mateuszkoslacz.moviper.sample.viper.presenter.MainPresenter; + +/** + * Created by mateuszkoslacz on 25.07.2016. + *

+ * It's just an empty activity. + */ +public class MainActivity + // you can change base class to any Mosby Activity, ie. MvpLceActivity, MvpViewStateActivity, etc. + extends MvpActivity + implements MainContract.View, MainContract.ViewHelper { + + //Viper-framework TODOs: + //TODO implement sample with Dagger2 Viper injection (to use in example and maybe to remove passing + // activity through presenter to let it be android-free. another (no-dagger) option is to pass + // newly created routing to presenter constructor) + //TODO implement sample with component tests using Dagger & Mocks + //TODO implement sample presenting rx approach + //TODO create rx versions of base classes + //TODO add proguard rules file + //TODO add mosby version choosing in gradle + //TODO add own lce views with TransitionManager.beginDelayedTransition(((ViewGroup) getView())); animations + //TODO add own lce fragments with viewGroup error + + //TODO create Android Studio plugin for creating appropriate classes and interfaces set + // choosing root viper dir + // choosing view name + // choosing framework (Wipe, Viper, Perv) with use-case description + // for Viper and Perv - choosing Activity or Fragment base + // for Viper and Perv - choosing if ViewHelper should be included + // for Activities - adding Activity to manifest + + public static void start(Context context) { + Intent starter = new Intent(context, MainActivity.class); + context.startActivity( + starter, + ActivityOptionsCompat.makeSceneTransitionAnimation(((Activity) context)).toBundle()); + } + + public static void startWithTransition(Activity activity, View logo) { + Intent starter = new Intent(activity, MainActivity.class); + ActivityOptionsCompat options = ActivityOptionsCompat + .makeSceneTransitionAnimation( + activity, + logo, + activity.getString(R.string.logoTransition)); + activity.startActivity( + starter, + options.toBundle()); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + } + + @Override + public MainContract.Presenter createPresenter() { + return new MainPresenter(this); + } +} diff --git a/sample/src/main/java/com/mateuszkoslacz/moviper/sample/viper/view/activity/SplashActivity.java b/sample/src/main/java/com/mateuszkoslacz/moviper/sample/viper/view/activity/SplashActivity.java new file mode 100644 index 0000000..fd452b2 --- /dev/null +++ b/sample/src/main/java/com/mateuszkoslacz/moviper/sample/viper/view/activity/SplashActivity.java @@ -0,0 +1,63 @@ +package com.mateuszkoslacz.moviper.sample.viper.view.activity; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.view.View; + +import com.hannesdorfmann.mosby.mvp.MvpActivity; +import com.mateuszkoslacz.moviper.sample.R; +import com.mateuszkoslacz.moviper.sample.viper.contract.SplashContract; +import com.mateuszkoslacz.moviper.sample.viper.presenter.SplashPresenter; + +import butterknife.BindView; +import butterknife.ButterKnife; + +/** + * Created by mateuszkoslacz on 25.07.2016. + */ +public class SplashActivity + // you can change base class to any Mosby Activity, ie. MvpLceActivity, MvpViewStateActivity, etc. + extends MvpActivity + implements SplashContract.View, SplashContract.ViewHelper { + + @BindView(R.id.logo) + View logo; + + + // I DO NOT encourage block comments in non-sample code! Organize your code + // in the self-explaining way and block comments will become redundant. + /////////////////////////////////////////////////////////////////////////// + // initializer + /////////////////////////////////////////////////////////////////////////// + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_splash); + ButterKnife.bind(this); + getPresenter().onViewLoaded(); + } + + + // I DO NOT encourage block comments in non-sample code! Organize your code + // in the self-explaining way and block comments will become redundant. + /////////////////////////////////////////////////////////////////////////// + // creating presenter + /////////////////////////////////////////////////////////////////////////// + + @NonNull + @Override + public SplashContract.Presenter createPresenter() { + return new SplashPresenter(this); + } + + + /////////////////////////////////////////////////////////////////////////// + // ViewHelper interface method + /////////////////////////////////////////////////////////////////////////// + + @Override + public View getLogo() { + return logo; + } +} diff --git a/sample/src/main/java/com/mateuszkoslacz/moviper/sample/viper/view/fragment/LoginFragment.java b/sample/src/main/java/com/mateuszkoslacz/moviper/sample/viper/view/fragment/LoginFragment.java new file mode 100644 index 0000000..796c982 --- /dev/null +++ b/sample/src/main/java/com/mateuszkoslacz/moviper/sample/viper/view/fragment/LoginFragment.java @@ -0,0 +1,131 @@ +package com.mateuszkoslacz.moviper.sample.viper.view.fragment; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.LayoutInflater; +import android.view.ViewGroup; +import android.widget.EditText; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.ProgressBar; + +import com.hannesdorfmann.mosby.mvp.lce.MvpLceFragment; +import com.mateuszkoslacz.moviper.sample.R; +import com.mateuszkoslacz.moviper.sample.viper.contract.LoginContract; +import com.mateuszkoslacz.moviper.sample.viper.presenter.LoginPresenter; +import com.mateuszkoslacz.moviper.sample.viper.view.fragment.iface.AuthorizationFragment; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; + +/** + * Created by mateuszkoslacz on 25.07.2016. + *

+ * Sample of Mosby LCE management. See {@link RegisterFragment} to check out custom LCE management. + */ +// TODO blocking touch ui on loading +public class LoginFragment extends MvpLceFragment + implements AuthorizationFragment, LoginContract.View, LoginContract.ViewHelper { + + @BindView(R.id.fragment_authorization_login_edittext_login) + EditText mLoginEditText; + @BindView(R.id.fragment_authorization_login_edittext_password) + EditText mPasswordEditText; + @BindView(R.id.loading_logo) + ImageView loadingLogo; + @BindView(R.id.progress_bar) + ProgressBar progressBar; + + // I DO NOT encourage block comments in non-sample code! Organize your code + // in the self-explaining way and block comments will become redundant. + /////////////////////////////////////////////////////////////////////////// + // initializer + /////////////////////////////////////////////////////////////////////////// + + @Nullable + @Override + public android.view.View onCreateView(LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + android.view.View view = inflater.inflate( + R.layout.fragment_authorization_login, + container, + false); + ButterKnife.bind(this, view); + return view; + } + + + /////////////////////////////////////////////////////////////////////////// + // events handling - propagating UI events to the Presenter + /////////////////////////////////////////////////////////////////////////// + + @OnClick(R.id.fragment_authorization_login_btn_show_register) + void onGotoRegisterButtonClick() { + presenter.onShowRegisterFragmentClicked(); + } + + @OnClick(R.id.fragment_authorization_login_btn_login) + void onLoginButtonClick() { + presenter.onLoginClicked(mLoginEditText.getText().toString(), + mPasswordEditText.getText().toString()); + } + + + /////////////////////////////////////////////////////////////////////////// + // creating the Presenter + /////////////////////////////////////////////////////////////////////////// + + @NonNull + @Override + public LoginContract.Presenter createPresenter() { + return new LoginPresenter(this); + } + + + /////////////////////////////////////////////////////////////////////////// + // ViewHelper interface method + /////////////////////////////////////////////////////////////////////////// + + public android.view.View getLoadingLogo() { + return loadingLogo; + } + + + /////////////////////////////////////////////////////////////////////////// + // AuthorizationFragment interface methods - see AuthorizationFragment + // interface javadoc + /////////////////////////////////////////////////////////////////////////// + + @Override + public android.view.View getSharedLoginEditText() { + return mLoginEditText; + } + + @Override + public android.view.View getSharedPasswordEditText() { + return mPasswordEditText; + } + + + /////////////////////////////////////////////////////////////////////////// + // Mosby Lce Fragment methods + /////////////////////////////////////////////////////////////////////////// + + @Override + protected String getErrorMessage(Throwable e, boolean pullToRefresh) { + return e.getLocalizedMessage(); + } + + @Override + public void setData(Object data) { + // no-op, we have no exact data here + } + + @Override + public void loadData(boolean pullToRefresh) { + // no-op, we have no exact data here + } +} diff --git a/sample/src/main/java/com/mateuszkoslacz/moviper/sample/viper/view/fragment/RegisterFragment.java b/sample/src/main/java/com/mateuszkoslacz/moviper/sample/viper/view/fragment/RegisterFragment.java new file mode 100644 index 0000000..2dc588b --- /dev/null +++ b/sample/src/main/java/com/mateuszkoslacz/moviper/sample/viper/view/fragment/RegisterFragment.java @@ -0,0 +1,156 @@ +package com.mateuszkoslacz.moviper.sample.viper.view.fragment; + +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.transition.TransitionManager; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.Toast; + +import com.hannesdorfmann.mosby.mvp.MvpFragment; +import com.mateuszkoslacz.moviper.sample.R; +import com.mateuszkoslacz.moviper.sample.data.bundle.RegisterBundle; +import com.mateuszkoslacz.moviper.sample.util.LocationUtils; +import com.mateuszkoslacz.moviper.sample.viper.contract.RegisterContract; +import com.mateuszkoslacz.moviper.sample.viper.presenter.RegisterPresenter; +import com.mateuszkoslacz.moviper.sample.viper.view.fragment.iface.AuthorizationFragment; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; + +/** + * Created by mateuszkoslacz on 25.07.2016. + *

+ * Sample of custom LCE management. See {@link LoginFragment} to check out Mosby LCE management. + */ +// TODO blocking touch ui on loading +public class RegisterFragment extends MvpFragment + implements AuthorizationFragment, RegisterContract.View, RegisterContract.ViewHelper { + + @BindView(R.id.fragment_authorization_register_edittext_login) + EditText mLoginEditText; + @BindView(R.id.fragment_authorization_register_edittext_email) + EditText mEmailEditText; + @BindView(R.id.fragment_authorization_register_edittext_password) + EditText mPasswordEditText; + @BindView(R.id.loadingView) + LinearLayout loadingView; + + + // I DO NOT encourage block comments in non-sample code! Organize your code + // in the self-explaining way and block comments will become redundant. + /////////////////////////////////////////////////////////////////////////// + // initializer + /////////////////////////////////////////////////////////////////////////// + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + View view = inflater.inflate( + R.layout.fragment_authorization_register, + container, + false); + ButterKnife.bind(this, view); + return view; + } + + + /////////////////////////////////////////////////////////////////////////// + // events handling - propagating UI events to the Presenter + /////////////////////////////////////////////////////////////////////////// + + @OnClick(R.id.fragment_authorization_register_btn_show_login) + void onGotoLoginBtnClick() { + presenter.onShowLoginFragmentClicked(); + } + + @OnClick(R.id.fragment_authorization_register_btn_register) + void onRegisterButtonClick() { + getPresenter().onRegisterClicked( + new RegisterBundle.Builder() + .withLogin(mLoginEditText.getText().toString()) + .withEmail(mEmailEditText.getText().toString()) + .withPassword(mPasswordEditText.getText().toString()) + .build()); + } + + /** + * Example of propagating an Activity/Fragment specific Android behaviour to the Routing. + * In this case it's handling of a permissions request result. + *

+ * Well, this isn't the best thing we have here and at the moment it cannot be detached from + * Android components. Actually, this is a logic of the Routing in the View. + * + * @param requestCode + * @param permissions + * @param grantResults + */ + @Override + public void onRequestPermissionsResult(int requestCode, + @NonNull String[] permissions, + @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + if (requestCode == LocationUtils.PERMISSION_REQUEST_ACCES_FINE_LOCATION && + permissions.length != 0 && + grantResults.length != 0) { + ((RegisterContract.PresenterForRouting) getPresenter()) + .onRequestLocalizationPermissionsResult( + grantResults[0] == PackageManager.PERMISSION_GRANTED); + } + } + + + /////////////////////////////////////////////////////////////////////////// + // creating the Presenter + /////////////////////////////////////////////////////////////////////////// + + @NonNull + @Override + public RegisterContract.Presenter createPresenter() { + return new RegisterPresenter(this); + } + + + /////////////////////////////////////////////////////////////////////////// + // View interface methods + /////////////////////////////////////////////////////////////////////////// + + @Override + public void showLoadingView() { + TransitionManager.beginDelayedTransition(((ViewGroup) getView())); + loadingView.setVisibility(View.VISIBLE); + } + + @Override + public void displayError(String msg) { + //TODO do it better (faster, stronger) + Toast.makeText(getActivity(), msg, Toast.LENGTH_SHORT).show(); + TransitionManager.beginDelayedTransition(((ViewGroup) getView())); + loadingView.setVisibility(View.GONE); + } + + + /////////////////////////////////////////////////////////////////////////// + // AuthorizationFragment interface methods - see AuthorizationFragment + // interface javadoc + /////////////////////////////////////////////////////////////////////////// + + @Override + public View getSharedLoginEditText() { + return mLoginEditText; + } + + @Override + public View getSharedPasswordEditText() { + return mPasswordEditText; + } + +} diff --git a/sample/src/main/java/com/mateuszkoslacz/moviper/sample/viper/view/fragment/iface/AuthorizationFragment.java b/sample/src/main/java/com/mateuszkoslacz/moviper/sample/viper/view/fragment/iface/AuthorizationFragment.java new file mode 100644 index 0000000..8e0cfb4 --- /dev/null +++ b/sample/src/main/java/com/mateuszkoslacz/moviper/sample/viper/view/fragment/iface/AuthorizationFragment.java @@ -0,0 +1,16 @@ +package com.mateuszkoslacz.moviper.sample.viper.view.fragment.iface; + +import android.view.View; + +/** + * Created by mateuszkoslacz on 31.07.2016. + *

+ * Interface for getting views from Fragments for their base Activity's ViewHelper to allow the + * Routing perform transitions between fragments. + */ +public interface AuthorizationFragment { + + View getSharedLoginEditText(); + + View getSharedPasswordEditText(); +} diff --git a/sample/src/main/res/drawable/ic_android_black_24px.xml b/sample/src/main/res/drawable/ic_android_black_24px.xml new file mode 100644 index 0000000..1e71197 --- /dev/null +++ b/sample/src/main/res/drawable/ic_android_black_24px.xml @@ -0,0 +1,20 @@ + + + + + + \ No newline at end of file diff --git a/sample/src/main/res/drawable/ic_clear_black_24px.xml b/sample/src/main/res/drawable/ic_clear_black_24px.xml new file mode 100644 index 0000000..a4c9462 --- /dev/null +++ b/sample/src/main/res/drawable/ic_clear_black_24px.xml @@ -0,0 +1,14 @@ + + + + + + \ No newline at end of file diff --git a/sample/src/main/res/layout/activity_authorization.xml b/sample/src/main/res/layout/activity_authorization.xml new file mode 100644 index 0000000..68ab69d --- /dev/null +++ b/sample/src/main/res/layout/activity_authorization.xml @@ -0,0 +1,29 @@ + + + + + + + + diff --git a/sample/src/main/res/layout/activity_main.xml b/sample/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..1fd1c17 --- /dev/null +++ b/sample/src/main/res/layout/activity_main.xml @@ -0,0 +1,31 @@ + + + + + + + + + + diff --git a/sample/src/main/res/layout/activity_splash.xml b/sample/src/main/res/layout/activity_splash.xml new file mode 100644 index 0000000..cfa27fe --- /dev/null +++ b/sample/src/main/res/layout/activity_splash.xml @@ -0,0 +1,38 @@ + + + + + + + + + + diff --git a/sample/src/main/res/layout/error_layout.xml b/sample/src/main/res/layout/error_layout.xml new file mode 100644 index 0000000..eb23ec8 --- /dev/null +++ b/sample/src/main/res/layout/error_layout.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/sample/src/main/res/layout/faked_content_view.xml b/sample/src/main/res/layout/faked_content_view.xml new file mode 100644 index 0000000..7e59766 --- /dev/null +++ b/sample/src/main/res/layout/faked_content_view.xml @@ -0,0 +1,13 @@ + + + + \ No newline at end of file diff --git a/sample/src/main/res/layout/fragment_authorization_login.xml b/sample/src/main/res/layout/fragment_authorization_login.xml new file mode 100644 index 0000000..16e6de1 --- /dev/null +++ b/sample/src/main/res/layout/fragment_authorization_login.xml @@ -0,0 +1,59 @@ + + + + + + + + +