From 5be1ee6f58d7197cb3cd23fc319e7c70e2a3341a Mon Sep 17 00:00:00 2001 From: Ilya Eremin Date: Wed, 29 Mar 2017 19:58:18 +0300 Subject: [PATCH] User json api with ToDo manager app --- JsonApiExample/.gitignore | 13 + JsonApiExample/.travis.yml | 42 ++ JsonApiExample/README.md | 87 ++++ JsonApiExample/app/build.gradle | 100 +++++ .../app/checkstyle/checkstyle.gradle | 17 + JsonApiExample/app/checkstyle/checkstyle.xml | 419 ++++++++++++++++++ .../app/checkstyle/suppressions.xml | 43 ++ JsonApiExample/app/findbugs-exclude.xml | 9 + JsonApiExample/app/lint.xml | 47 ++ JsonApiExample/app/proguard-rules.pro | 65 +++ JsonApiExample/app/quality.gradle | 20 + .../app/src/main/AndroidManifest.xml | 32 ++ .../app/src/main/ic_launcher-web.png | Bin 0 -> 24064 bytes .../main/java/com/flatstack/android/Api.java | 58 +++ .../main/java/com/flatstack/android/App.java | 15 + .../main/java/com/flatstack/android/Deps.java | 89 ++++ .../com/flatstack/android/InitActivity.java | 25 ++ .../java/com/flatstack/android/Navigator.java | 39 ++ .../flatstack/android/RegistrationScreen.java | 77 ++++ .../com/flatstack/android/SignInScreen.java | 84 ++++ .../com/flatstack/android/models/Status.java | 11 + .../com/flatstack/android/models/ToDo.java | 72 +++ .../com/flatstack/android/models/Token.java | 25 ++ .../com/flatstack/android/models/User.java | 33 ++ .../todo_details/CreateToDoScreen.java | 58 +++ .../todo_details/UpdateToDoScreen.java | 78 ++++ .../android/todo_list/TodoHolder.java | 33 ++ .../android/todo_list/TodosAdapter.java | 21 + .../android/todo_list/TodosListScreen.java | 115 +++++ .../com/flatstack/android/utils/Errors.java | 48 ++ .../com/flatstack/android/utils/Keyboard.java | 40 ++ .../com/flatstack/android/utils/Lists.java | 53 +++ .../java/com/flatstack/android/utils/Rxs.java | 21 + .../android/utils/TextViewUtils.java | 15 + .../com/flatstack/android/utils/Views.java | 24 + .../jsonapi/JsonApiConverterFactory.java | 158 +++++++ .../utils/recycler_view/BaseAdapter.java | 60 +++ .../utils/recycler_view/BaseHolder.java | 16 + .../recycler_view/OnItemClickListener.java | 11 + .../android/utils/storage/IStorage.java | 43 ++ .../android/utils/storage/Storage.java | 83 ++++ .../android/utils/ui/BaseActivity.java | 82 ++++ .../android/utils/ui/BaseDialogFragment.java | 84 ++++ .../android/utils/ui/BaseFragment.java | 40 ++ .../flatstack/android/utils/ui/UiInfo.java | 63 +++ .../app/src/main/res/drawable-hdpi/ic_add.png | Bin 0 -> 223 bytes .../main/res/drawable-hdpi/ic_launcher.png | Bin 0 -> 1804 bytes .../app/src/main/res/drawable-mdpi/ic_add.png | Bin 0 -> 152 bytes .../main/res/drawable-mdpi/ic_launcher.png | Bin 0 -> 971 bytes .../src/main/res/drawable-xhdpi/ic_add.png | Bin 0 -> 197 bytes .../main/res/drawable-xhdpi/ic_launcher.png | Bin 0 -> 2502 bytes .../src/main/res/drawable-xxhdpi/ic_add.png | Bin 0 -> 351 bytes .../main/res/drawable-xxhdpi/ic_launcher.png | Bin 0 -> 4854 bytes .../main/res/drawable-xxxhdpi/ic_launcher.png | Bin 0 -> 5445 bytes .../app/src/main/res/layout/activity_main.xml | 25 ++ .../app/src/main/res/layout/dialog_test.xml | 32 ++ .../app/src/main/res/layout/fragment_main.xml | 27 ++ .../app/src/main/res/layout/item_todo.xml | 62 +++ .../main/res/layout/screen_registration.xml | 55 +++ .../src/main/res/layout/screen_sign_in.xml | 60 +++ .../main/res/layout/screen_todo_details.xml | 60 +++ .../app/src/main/res/layout/screen_todos.xml | 54 +++ .../app/src/main/res/layout/toolbar.xml | 15 + .../src/main/res/menu/menu_with_logout.xml | 12 + .../app/src/main/res/values/colors.xml | 4 + .../app/src/main/res/values/dimens.xml | 3 + .../app/src/main/res/values/strings.xml | 22 + .../app/src/main/res/values/styles.xml | 2 + .../app/src/main/res/values/themes.xml | 14 + .../app/src/main/res/values/tokens.xml | 3 + .../android/utils/storage/RuntimeStorage.java | 71 +++ JsonApiExample/build.gradle | 16 + JsonApiExample/circle.yml | 28 ++ JsonApiExample/deps.gradle | 45 ++ .../gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 51106 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 + JsonApiExample/gradlew | 164 +++++++ JsonApiExample/gradlew.bat | 90 ++++ JsonApiExample/keystore.properties | 4 + JsonApiExample/proguard.cfg | 12 + JsonApiExample/settings.gradle | 1 + 81 files changed, 3455 insertions(+) create mode 100755 JsonApiExample/.gitignore create mode 100755 JsonApiExample/.travis.yml create mode 100755 JsonApiExample/README.md create mode 100755 JsonApiExample/app/build.gradle create mode 100755 JsonApiExample/app/checkstyle/checkstyle.gradle create mode 100755 JsonApiExample/app/checkstyle/checkstyle.xml create mode 100755 JsonApiExample/app/checkstyle/suppressions.xml create mode 100755 JsonApiExample/app/findbugs-exclude.xml create mode 100755 JsonApiExample/app/lint.xml create mode 100755 JsonApiExample/app/proguard-rules.pro create mode 100755 JsonApiExample/app/quality.gradle create mode 100755 JsonApiExample/app/src/main/AndroidManifest.xml create mode 100644 JsonApiExample/app/src/main/ic_launcher-web.png create mode 100644 JsonApiExample/app/src/main/java/com/flatstack/android/Api.java create mode 100755 JsonApiExample/app/src/main/java/com/flatstack/android/App.java create mode 100644 JsonApiExample/app/src/main/java/com/flatstack/android/Deps.java create mode 100644 JsonApiExample/app/src/main/java/com/flatstack/android/InitActivity.java create mode 100644 JsonApiExample/app/src/main/java/com/flatstack/android/Navigator.java create mode 100644 JsonApiExample/app/src/main/java/com/flatstack/android/RegistrationScreen.java create mode 100644 JsonApiExample/app/src/main/java/com/flatstack/android/SignInScreen.java create mode 100644 JsonApiExample/app/src/main/java/com/flatstack/android/models/Status.java create mode 100644 JsonApiExample/app/src/main/java/com/flatstack/android/models/ToDo.java create mode 100644 JsonApiExample/app/src/main/java/com/flatstack/android/models/Token.java create mode 100644 JsonApiExample/app/src/main/java/com/flatstack/android/models/User.java create mode 100644 JsonApiExample/app/src/main/java/com/flatstack/android/todo_details/CreateToDoScreen.java create mode 100644 JsonApiExample/app/src/main/java/com/flatstack/android/todo_details/UpdateToDoScreen.java create mode 100644 JsonApiExample/app/src/main/java/com/flatstack/android/todo_list/TodoHolder.java create mode 100644 JsonApiExample/app/src/main/java/com/flatstack/android/todo_list/TodosAdapter.java create mode 100644 JsonApiExample/app/src/main/java/com/flatstack/android/todo_list/TodosListScreen.java create mode 100644 JsonApiExample/app/src/main/java/com/flatstack/android/utils/Errors.java create mode 100755 JsonApiExample/app/src/main/java/com/flatstack/android/utils/Keyboard.java create mode 100755 JsonApiExample/app/src/main/java/com/flatstack/android/utils/Lists.java create mode 100644 JsonApiExample/app/src/main/java/com/flatstack/android/utils/Rxs.java create mode 100644 JsonApiExample/app/src/main/java/com/flatstack/android/utils/TextViewUtils.java create mode 100755 JsonApiExample/app/src/main/java/com/flatstack/android/utils/Views.java create mode 100644 JsonApiExample/app/src/main/java/com/flatstack/android/utils/jsonapi/JsonApiConverterFactory.java create mode 100755 JsonApiExample/app/src/main/java/com/flatstack/android/utils/recycler_view/BaseAdapter.java create mode 100755 JsonApiExample/app/src/main/java/com/flatstack/android/utils/recycler_view/BaseHolder.java create mode 100755 JsonApiExample/app/src/main/java/com/flatstack/android/utils/recycler_view/OnItemClickListener.java create mode 100755 JsonApiExample/app/src/main/java/com/flatstack/android/utils/storage/IStorage.java create mode 100755 JsonApiExample/app/src/main/java/com/flatstack/android/utils/storage/Storage.java create mode 100755 JsonApiExample/app/src/main/java/com/flatstack/android/utils/ui/BaseActivity.java create mode 100755 JsonApiExample/app/src/main/java/com/flatstack/android/utils/ui/BaseDialogFragment.java create mode 100755 JsonApiExample/app/src/main/java/com/flatstack/android/utils/ui/BaseFragment.java create mode 100755 JsonApiExample/app/src/main/java/com/flatstack/android/utils/ui/UiInfo.java create mode 100644 JsonApiExample/app/src/main/res/drawable-hdpi/ic_add.png create mode 100644 JsonApiExample/app/src/main/res/drawable-hdpi/ic_launcher.png create mode 100644 JsonApiExample/app/src/main/res/drawable-mdpi/ic_add.png create mode 100644 JsonApiExample/app/src/main/res/drawable-mdpi/ic_launcher.png create mode 100644 JsonApiExample/app/src/main/res/drawable-xhdpi/ic_add.png create mode 100644 JsonApiExample/app/src/main/res/drawable-xhdpi/ic_launcher.png create mode 100644 JsonApiExample/app/src/main/res/drawable-xxhdpi/ic_add.png create mode 100644 JsonApiExample/app/src/main/res/drawable-xxhdpi/ic_launcher.png create mode 100644 JsonApiExample/app/src/main/res/drawable-xxxhdpi/ic_launcher.png create mode 100755 JsonApiExample/app/src/main/res/layout/activity_main.xml create mode 100755 JsonApiExample/app/src/main/res/layout/dialog_test.xml create mode 100755 JsonApiExample/app/src/main/res/layout/fragment_main.xml create mode 100644 JsonApiExample/app/src/main/res/layout/item_todo.xml create mode 100644 JsonApiExample/app/src/main/res/layout/screen_registration.xml create mode 100644 JsonApiExample/app/src/main/res/layout/screen_sign_in.xml create mode 100644 JsonApiExample/app/src/main/res/layout/screen_todo_details.xml create mode 100644 JsonApiExample/app/src/main/res/layout/screen_todos.xml create mode 100644 JsonApiExample/app/src/main/res/layout/toolbar.xml create mode 100644 JsonApiExample/app/src/main/res/menu/menu_with_logout.xml create mode 100644 JsonApiExample/app/src/main/res/values/colors.xml create mode 100755 JsonApiExample/app/src/main/res/values/dimens.xml create mode 100755 JsonApiExample/app/src/main/res/values/strings.xml create mode 100755 JsonApiExample/app/src/main/res/values/styles.xml create mode 100755 JsonApiExample/app/src/main/res/values/themes.xml create mode 100755 JsonApiExample/app/src/main/res/values/tokens.xml create mode 100755 JsonApiExample/app/src/test/java/com/flatstack/android/utils/storage/RuntimeStorage.java create mode 100755 JsonApiExample/build.gradle create mode 100755 JsonApiExample/circle.yml create mode 100755 JsonApiExample/deps.gradle create mode 100755 JsonApiExample/gradle/wrapper/gradle-wrapper.jar create mode 100755 JsonApiExample/gradle/wrapper/gradle-wrapper.properties create mode 100755 JsonApiExample/gradlew create mode 100755 JsonApiExample/gradlew.bat create mode 100755 JsonApiExample/keystore.properties create mode 100755 JsonApiExample/proguard.cfg create mode 100755 JsonApiExample/settings.gradle diff --git a/JsonApiExample/.gitignore b/JsonApiExample/.gitignore new file mode 100755 index 0000000..90e5879 --- /dev/null +++ b/JsonApiExample/.gitignore @@ -0,0 +1,13 @@ +.gradle +/local.properties +/.idea/workspace.xml +.DS_Store +.idea/ +*.iml +build/ +.gradletasknamecache + +crashlytics.properties +crashlytics-build.properties +com_crashlytics_export_strings.xml +dagger-proguard-keepnames.cfg \ No newline at end of file diff --git a/JsonApiExample/.travis.yml b/JsonApiExample/.travis.yml new file mode 100755 index 0000000..07dfa37 --- /dev/null +++ b/JsonApiExample/.travis.yml @@ -0,0 +1,42 @@ +language: android +jdk: oraclejdk8 +sudo: false +env: + - GRADLE_OPTS='-Dorg.gradle.jvmargs="-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError"' + +machine: + environment: + GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError"' + +android: + components: + - tools + - platform-tools + - build-tools-25.0.1 + - android-25 + - extra-android-m2repository + # Uncomment this if you need emulator + # - sys-img-x86-android-23 + licenses: + - 'android-sdk-license-.+' + +before_install: + - export TERM=dumb + - chmod +x gradlew + +# Uncomment this if you need emulator +# before_script: +# - echo no | android create avd --force -n test -t android-21 --abi x86 +# - emulator -avd test -no-skin -no-audio -no-window & +# - android-wait-for-emulator +# - adb shell input keyevent 82 & + +script: + - ./gradlew checkstyle lintProductionRelease testProductionReleaseUnitTest + +branches: + only: + - master + - production + +# after_success: uncomment this and add your own deployment targets \ No newline at end of file diff --git a/JsonApiExample/README.md b/JsonApiExample/README.md new file mode 100755 index 0000000..10322e2 --- /dev/null +++ b/JsonApiExample/README.md @@ -0,0 +1,87 @@ +JsonApi Android Example +======================================= + +This example build on top of [retrofit2](https://github.com/square/retrofit) + [moshi](https://github.com/square/moshi) + [moshi-jsonapi](https://github.com/kamikat/moshi-jsonapi) + +Simple ToDo Android application. + +You can: +* Register +* Login +* See ToDo List +* Create new ToDo +* Update existing Todo + +you can't logout :p + +# How can I add my own requests? +### Parse answer +JsonApi dictates following answer format: + +``` +{ + "data": { + "id": "5", + "type": "todo-items", + "links": { + "self": "http://localhost:3000/v1/todo-items/5" + }, + "attributes": { + "title": "Meggings 8-bit irony tousled mlkshk neutra crucifix lumbersexual kogi.", + "text": "Meh wolf whatever salvia cardigan seitan paleo. Health poutine listicle goth scenester kitsch fanny pack. Narwhal cold-pressed hashtag goth umami.", + } + } +} +``` + +Good news, you don't have to parse such a _strange_ json by yourself. Just create following object: +``` +@JsonApi(type = "todo-items") +public class ToDo extends moe.banana.jsonapi2.Resource { + String title; + String text; + + public ToDo(String title, String text) { + this.title = title; + this.text = text; + } +} +``` +First of all your object should extends from `Resource` class. +As you can see `ToDo`'s object fields coincide with json's keys under `attributes` fields. +If you want object field name and json key name to differ then use `@Json(name="first-name") String name` annotation. +Also `type` in json and type inside `@JsonApi(type = )` annotation of `ToDo` class should be the same. + +After than just wrap your class by `ObjectDocument` in case of single object or `ArrayDocument` in case of array to parse answer. + +So your Retrofit will look like: + +``` +@GET("todo-items/") Observable> todos(); +``` + + +### Prepare request + +When you do POST/PUT/PATCH then probably you need to send some info in request body. JsonApi dictates following structure: +``` +{ + "data": { + "type": "todo-items", + "attributes": { + "title": "Nice title here", + "text": "Text you never seen before ;)" + } + } +} +``` +which is the same format as above for answers. So create object following rules above. + +Now, your retrofit request will look like: +``` +@POST("todo-items/") Observable createToDo(@Body moe.banana.jsonapi2.Document todo); +``` + + +[](http://www.flatstack.com) + diff --git a/JsonApiExample/app/build.gradle b/JsonApiExample/app/build.gradle new file mode 100755 index 0000000..7d16041 --- /dev/null +++ b/JsonApiExample/app/build.gradle @@ -0,0 +1,100 @@ +apply plugin: 'com.android.application' +apply plugin: 'me.tatarka.retrolambda' +apply from: 'checkstyle/checkstyle.gradle' + +apply from: '../deps.gradle' + +ext { + APPLICATION_ID = "com.flatstack.android" + isCI = "true".equals(System.getenv("CI")) + commitMessage = 'git log -1 --pretty=%B'.execute().text.trim() +} + +android { + compileSdkVersion versions.TARGET_SDK_VERSION + buildToolsVersion versions.BUILD_TOOLS_VERSION + + defaultConfig { + minSdkVersion versions.MIN_SDK_VERSION + targetSdkVersion versions.TARGET_SDK_VERSION + + applicationId APPLICATION_ID + versionCode 1 + versionName '1.0-beta' + } + + productFlavors { + staging { + buildConfigField "String", "API_URL", "\"https://example-staging.com\"" + applicationIdSuffix ".staging" + } + + production { + buildConfigField "String", "API_URL", "\"https://example.com\"" + } + } + + buildTypes { + debug { + } + release { + minifyEnabled true + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + dexOptions { + preDexLibraries = !isCI + } + + packagingOptions { + exclude 'META-INF/LICENSE' + exclude 'META-INF/NOTICE' + exclude 'META-INF/services/javax.annotation.processing.Processor' + } + + lintOptions { + textReport true + textOutput "stdout" + lintConfig file("$projectDir/lint.xml") + warningsAsErrors true + } +} + +repositories { + mavenCentral() + maven { url "https://jitpack.io" } +} + +dependencies { + compile supportLibs + compile rxJavaLibs + compile retrofitLibs + compile okHttpLibs + + compile 'com.jakewharton:butterknife:7.0.0' // view injection + compile 'com.squareup.moshi:moshi:1.4.0' + compile 'moe.banana:moshi-jsonapi:3.2.0' + compile 'com.github.bumptech.glide:glide:3.7.0' + compile 'com.squareup.retrofit2:converter-moshi:2.2.0' + compile 'com.github.GrenderG:Toasty:1.1.4' + + compile 'com.facebook.stetho:stetho-okhttp3:1.4.2' + compile 'com.facebook.stetho:stetho:1.4.2' + + testCompile unitTestLibs + + compile 'com.google.code.gson:gson:2.4' +} + +configurations { + testCompile.exclude module: 'commons-logging' + testCompile.exclude module: 'httpclient' +} + +apply from: "quality.gradle" \ No newline at end of file diff --git a/JsonApiExample/app/checkstyle/checkstyle.gradle b/JsonApiExample/app/checkstyle/checkstyle.gradle new file mode 100755 index 0000000..a384942 --- /dev/null +++ b/JsonApiExample/app/checkstyle/checkstyle.gradle @@ -0,0 +1,17 @@ +apply plugin: 'checkstyle' + +check.dependsOn 'checkstyle' + +checkstyle { + toolVersion '6.5' + + configFile file("checkstyle/checkstyle.xml") + configProperties.checkstyleSuppressionFilterPath = file("checkstyle/suppressions.xml") + .absolutePath +} +task checkstyle(type: Checkstyle, group: 'verification') { + source 'src' + include '**/*.java' + exclude '**/gen/**' + classpath = files() +} \ No newline at end of file diff --git a/JsonApiExample/app/checkstyle/checkstyle.xml b/JsonApiExample/app/checkstyle/checkstyle.xml new file mode 100755 index 0000000..ae78e69 --- /dev/null +++ b/JsonApiExample/app/checkstyle/checkstyle.xml @@ -0,0 +1,419 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/JsonApiExample/app/checkstyle/suppressions.xml b/JsonApiExample/app/checkstyle/suppressions.xml new file mode 100755 index 0000000..7880252 --- /dev/null +++ b/JsonApiExample/app/checkstyle/suppressions.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/JsonApiExample/app/findbugs-exclude.xml b/JsonApiExample/app/findbugs-exclude.xml new file mode 100755 index 0000000..acdff83 --- /dev/null +++ b/JsonApiExample/app/findbugs-exclude.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/JsonApiExample/app/lint.xml b/JsonApiExample/app/lint.xml new file mode 100755 index 0000000..840a880 --- /dev/null +++ b/JsonApiExample/app/lint.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/JsonApiExample/app/proguard-rules.pro b/JsonApiExample/app/proguard-rules.pro new file mode 100755 index 0000000..7a6d45d --- /dev/null +++ b/JsonApiExample/app/proguard-rules.pro @@ -0,0 +1,65 @@ +#your models +#if you store all model under one package then use: +# -keep class your.package.name.models.** { *; } +#if you store models under different models packages then use: +#-keep class **.models.** { *; } + +#retrolambda +-dontwarn java.lang.invoke.* + +#butterknife +-keep class butterknife.** { *; } +-dontwarn butterknife.internal.** +-keep class **$$ViewBinder { *; } +-keepclasseswithmembernames class * { + @butterknife.* ; +} +-keepclasseswithmembernames class * { + @butterknife.* ; +} + +#picasso +-dontwarn com.squareup.okhttp3.** + +# OkHttp +-keepattributes Signature +-keepattributes *Annotation* +-keep class com.squareup.okhttp3.** { *; } +-keep interface com.squareup.okhttp3.** { *; } +-dontwarn com.squareup.okhttp3.** + +#rxjava +-keepclassmembers class rx.internal.util.unsafe.*ArrayQueue*Field* { + long producerIndex; + long consumerIndex; +} +-keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueProducerNodeRef { + rx.internal.util.atomic.LinkedQueueNode producerNode; +} +-keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueConsumerNodeRef { + rx.internal.util.atomic.LinkedQueueNode consumerNode; +} +-dontwarn sun.misc.Unsafe + +#start gson +-keepattributes Signature +# For using GSON @Expose annotation +-keepattributes *Annotation* + +# Gson specific classes +-keep class sun.misc.Unsafe { *; } +#end of gson + +#glide +-keep public class * implements com.bumptech.glide.module.GlideModule +-keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** { + **[] $VALUES; + public *; +} + +#debug +-renamesourcefileattribute SourceFile +-keepattributes SourceFile,LineNumberTable + + + diff --git a/JsonApiExample/app/quality.gradle b/JsonApiExample/app/quality.gradle new file mode 100755 index 0000000..df2568d --- /dev/null +++ b/JsonApiExample/app/quality.gradle @@ -0,0 +1,20 @@ +apply plugin: 'findbugs' + +final classesDir = "build/intermediates/classes/production/debug" +final sourceDir = "src/main/java" + +task findBugs(type: FindBugs, dependsOn: "assembleProductionDebug") { + classes = fileTree(classesDir) + source = fileTree(sourceDir) + classpath = files() + excludeFilter = file("findbugs-exclude.xml") + + effort = 'max' + + reports { + xml.enabled = false + html.enabled = true + } +} + +tasks.check.dependsOn findBugs diff --git a/JsonApiExample/app/src/main/AndroidManifest.xml b/JsonApiExample/app/src/main/AndroidManifest.xml new file mode 100755 index 0000000..006e8ba --- /dev/null +++ b/JsonApiExample/app/src/main/AndroidManifest.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/JsonApiExample/app/src/main/ic_launcher-web.png b/JsonApiExample/app/src/main/ic_launcher-web.png new file mode 100644 index 0000000000000000000000000000000000000000..b21ba618d2b92cb5cf4f09a13240ab0ba2651902 GIT binary patch literal 24064 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 literal 0 HcmV?d00001 diff --git a/JsonApiExample/app/src/main/java/com/flatstack/android/Api.java b/JsonApiExample/app/src/main/java/com/flatstack/android/Api.java new file mode 100644 index 0000000..994dd1d --- /dev/null +++ b/JsonApiExample/app/src/main/java/com/flatstack/android/Api.java @@ -0,0 +1,58 @@ +package com.flatstack.android; + +import com.flatstack.android.models.ToDo; +import com.flatstack.android.models.Token; +import com.flatstack.android.models.User; + +import moe.banana.jsonapi2.ArrayDocument; +import moe.banana.jsonapi2.Document; +import moe.banana.jsonapi2.ObjectDocument; +import okhttp3.ResponseBody; +import retrofit2.http.Body; +import retrofit2.http.GET; +import retrofit2.http.Headers; +import retrofit2.http.PATCH; +import retrofit2.http.POST; +import retrofit2.http.Path; +import rx.Observable; + +/** + * Created by ereminilya on 24/3/17. + */ + +public interface Api { + + String BASE_URL = "https://fs-rails-base-api-pr-216.herokuapp.com/"; + + @Headers({ + "Accept: application/vnd.api+json", + "Content-Type: application/vnd.api+json" + }) + @POST("users/") Observable> register(@Body Document userToRegister); + + @Headers({ + "Accept: application/vnd.api+json", + "Content-Type: application/vnd.api+json" + }) + @POST("sessions/") Observable> signIn(@Body Document user); + + @Headers({ + "Accept: application/vnd.api+json", + "Content-Type: application/vnd.api+json" + }) + @GET("todo-items/") Observable> todos(); + + @Headers({ + "Accept: application/vnd.api+json", + "Content-Type: application/vnd.api+json" + }) + @POST("todo-items/") + Observable createToDo(@Body Document todo); + + @Headers({ + "Accept: application/vnd.api+json", + "Content-Type: application/vnd.api+json" + }) + @PATCH("todo-items/{id}") + Observable updateToDo(@Path("id") String todoId, @Body Document todo); +} \ No newline at end of file diff --git a/JsonApiExample/app/src/main/java/com/flatstack/android/App.java b/JsonApiExample/app/src/main/java/com/flatstack/android/App.java new file mode 100755 index 0000000..85ae023 --- /dev/null +++ b/JsonApiExample/app/src/main/java/com/flatstack/android/App.java @@ -0,0 +1,15 @@ +package com.flatstack.android; + +import android.app.Application; + +import com.facebook.stetho.Stetho; + +public class App extends Application { + + @Override + public void onCreate() { + super.onCreate(); + Stetho.initializeWithDefaults(this); + Deps.init(this); + } +} \ No newline at end of file diff --git a/JsonApiExample/app/src/main/java/com/flatstack/android/Deps.java b/JsonApiExample/app/src/main/java/com/flatstack/android/Deps.java new file mode 100644 index 0000000..8adfdfa --- /dev/null +++ b/JsonApiExample/app/src/main/java/com/flatstack/android/Deps.java @@ -0,0 +1,89 @@ +package com.flatstack.android; + +import android.content.Context; +import android.preference.PreferenceManager; + +import com.facebook.stetho.okhttp3.StethoInterceptor; +import com.flatstack.android.models.ToDo; +import com.flatstack.android.models.User; +import com.flatstack.android.utils.jsonapi.JsonApiConverterFactory; +import com.flatstack.android.utils.storage.IStorage; +import com.flatstack.android.utils.storage.Storage; +import com.google.gson.Gson; +import com.squareup.moshi.JsonAdapter; +import com.squareup.moshi.Moshi; + +import moe.banana.jsonapi2.ResourceAdapterFactory; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import retrofit2.Retrofit; +import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory; +import retrofit2.converter.moshi.MoshiConverterFactory; + +/** + * Created by ereminilya on 24/3/17. + */ + +public class Deps { + + private static Context appContext; + + public static void init(Context context) { + appContext = context; + } + + private static Storage storage; + + public static IStorage storage() { + if (storage == null) { + storage = new Storage(PreferenceManager.getDefaultSharedPreferences(appContext), new Gson()); + } + return storage; + } + + private static Api api; + + public static Api api() { + if (api == null) { + OkHttpClient client = new OkHttpClient.Builder() + .addNetworkInterceptor(new StethoInterceptor()) + .addInterceptor(chain -> { + String token = storage().getString(IStorage.KEY_TOKEN); + String email = storage().getString(IStorage.KEY_EMAIL); + Request request = chain.request(); + if (token != null && !token.isEmpty() && email != null && !email.isEmpty()) { + request = request.newBuilder() + .addHeader("X-User-Token", token) + .addHeader("X-User-Email", email) + .build(); + } + return chain.proceed(request); + }) + .build(); + api = new Retrofit.Builder() + .client(client) + .baseUrl(Api.BASE_URL) + .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) + .addConverterFactory(MoshiConverterFactory.create(jsonConverter())) + .addConverterFactory(JsonApiConverterFactory.create(jsonConverter())) + .build() + .create(Api.class); + } + return api; + } + + private static Moshi moshi; + + public static Moshi jsonConverter() { + if (moshi == null) { + JsonAdapter.Factory jsonApiAdapterFactory = ResourceAdapterFactory.builder() + .add(User.class) + .add(ToDo.class) + .build(); + moshi = new Moshi.Builder() + .add(jsonApiAdapterFactory) + .build(); + } + return moshi; + } +} \ No newline at end of file diff --git a/JsonApiExample/app/src/main/java/com/flatstack/android/InitActivity.java b/JsonApiExample/app/src/main/java/com/flatstack/android/InitActivity.java new file mode 100644 index 0000000..743abc5 --- /dev/null +++ b/JsonApiExample/app/src/main/java/com/flatstack/android/InitActivity.java @@ -0,0 +1,25 @@ +package com.flatstack.android; + +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v7.app.AppCompatActivity; + +import com.flatstack.android.utils.storage.IStorage; + +/** + * Created by ereminilya on 29/3/17. + */ + +public class InitActivity extends AppCompatActivity { + + @Override protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + String token = Deps.storage().getString(IStorage.KEY_TOKEN); + finish(); + if (token != null) { + Navigator.toDos(this); + } else { + Navigator.signIn(this); + } + } +} diff --git a/JsonApiExample/app/src/main/java/com/flatstack/android/Navigator.java b/JsonApiExample/app/src/main/java/com/flatstack/android/Navigator.java new file mode 100644 index 0000000..633ade7 --- /dev/null +++ b/JsonApiExample/app/src/main/java/com/flatstack/android/Navigator.java @@ -0,0 +1,39 @@ +package com.flatstack.android; + +import android.content.Context; +import android.content.Intent; + +import com.flatstack.android.models.ToDo; +import com.flatstack.android.todo_details.CreateToDoScreen; +import com.flatstack.android.todo_details.UpdateToDoScreen; +import com.flatstack.android.todo_list.TodosListScreen; + +/** + * Created by ereminilya on 29/3/17. + */ +public class Navigator { + + public static void createToDoAndReturnBack(TodosListScreen todosScreen, int requestCode) { + todosScreen.startActivityForResult(new Intent(todosScreen, CreateToDoScreen.class), requestCode); + } + + public static void toDos(Context context) { + Intent intent = new Intent(context, TodosListScreen.class); + intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(intent); + } + + public static void signIn(Context context) { + Intent intent = new Intent(context, SignInScreen.class); + intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(intent); + } + + public static void todoDetails(Context context, ToDo todo) { + context.startActivity(UpdateToDoScreen.newInstance(context, todo)); + } + + public static void createAccount(Context context) { + context.startActivity(new Intent(context, RegistrationScreen.class)); + } +} \ No newline at end of file diff --git a/JsonApiExample/app/src/main/java/com/flatstack/android/RegistrationScreen.java b/JsonApiExample/app/src/main/java/com/flatstack/android/RegistrationScreen.java new file mode 100644 index 0000000..0c0acf2 --- /dev/null +++ b/JsonApiExample/app/src/main/java/com/flatstack/android/RegistrationScreen.java @@ -0,0 +1,77 @@ +package com.flatstack.android; + +import android.app.ProgressDialog; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.widget.EditText; +import android.widget.Toast; + +import com.flatstack.android.models.User; +import com.flatstack.android.utils.Errors; +import com.flatstack.android.utils.Rxs; +import com.flatstack.android.utils.TextViewUtils; +import com.flatstack.android.utils.ui.BaseActivity; +import com.flatstack.android.utils.ui.UiInfo; + +import butterknife.Bind; +import butterknife.OnClick; +import es.dmoral.toasty.Toasty; +import moe.banana.jsonapi2.Document; +import moe.banana.jsonapi2.ObjectDocument; +import rx.Subscription; +import rx.android.schedulers.AndroidSchedulers; +import rx.schedulers.Schedulers; + +/** + * Created by ereminilya on 24/3/17. + */ + +public class RegistrationScreen extends BaseActivity { + + private static final String TAG = "RegistrationScreen"; + + @Bind(R.id.email) EditText uiEmail; + @Bind(R.id.password) EditText uiPassword; + + @Nullable private ProgressDialog dialog; + @Nullable private Subscription registerSubs; + + @NonNull @Override public UiInfo getUiInfo() { + return new UiInfo(R.layout.screen_registration).enableBackButton() + .setTitleRes(R.string.title_register); + } + + @OnClick(R.id.register) void onRegisterClick() { + dialog = ProgressDialog.show(this, getString(R.string.loading), getString(R.string.registration)); + registerSubs = Deps.api().register(createRegistrationBody()) + .compose(Rxs.doInBackgroundDeliverToUI()) + .doOnTerminate(this::closeDialog) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(result -> { + Toasty.success(RegistrationScreen.this, getString(R.string.account_has_been_created), Toast.LENGTH_LONG).show(); + finish(); + }, Errors.handle(RegistrationScreen.this)); + } + + private void closeDialog() { + if (dialog != null) { + dialog.dismiss(); + dialog = null; + } + } + + @Override protected void onStop() { + if (registerSubs != null) { + registerSubs.unsubscribe(); + registerSubs = null; + } + super.onStop(); + } + + @NonNull private Document createRegistrationBody() { + ObjectDocument userDocument = new ObjectDocument<>(); + userDocument.set(new User(TextViewUtils.textOf(uiEmail), TextViewUtils.textOf(uiPassword))); + return userDocument; + } +} \ No newline at end of file diff --git a/JsonApiExample/app/src/main/java/com/flatstack/android/SignInScreen.java b/JsonApiExample/app/src/main/java/com/flatstack/android/SignInScreen.java new file mode 100644 index 0000000..6507357 --- /dev/null +++ b/JsonApiExample/app/src/main/java/com/flatstack/android/SignInScreen.java @@ -0,0 +1,84 @@ +package com.flatstack.android; + +import android.app.ProgressDialog; +import android.content.Intent; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.widget.EditText; + +import com.flatstack.android.models.Token; +import com.flatstack.android.models.User; +import com.flatstack.android.todo_list.TodosListScreen; +import com.flatstack.android.utils.Errors; +import com.flatstack.android.utils.Rxs; +import com.flatstack.android.utils.TextViewUtils; +import com.flatstack.android.utils.storage.IStorage; +import com.flatstack.android.utils.ui.BaseActivity; +import com.flatstack.android.utils.ui.UiInfo; + +import butterknife.Bind; +import butterknife.OnClick; +import moe.banana.jsonapi2.Document; +import moe.banana.jsonapi2.ObjectDocument; +import rx.Subscription; + +import static com.flatstack.android.utils.storage.IStorage.KEY_EMAIL; +import static com.flatstack.android.utils.storage.IStorage.KEY_TOKEN; + +/** + * Created by ereminilya on 24/3/17. + */ + +public class SignInScreen extends BaseActivity { + + @Bind(R.id.email) EditText uiEmail; + @Bind(R.id.password) EditText uiPassword; + + @Nullable private ProgressDialog dialog; + @Nullable private Subscription signingSubs; + + @NonNull @Override public UiInfo getUiInfo() { + return new UiInfo(R.layout.screen_sign_in).setTitleRes(R.string.title_login); + } + + @OnClick(R.id.sign_in) void onSignInClick() { + dialog = ProgressDialog.show(this, getString(R.string.loading), getString(R.string.signing_in)); + signingSubs = Deps.api().signIn(createSignInDoc()) + .compose(Rxs.doInBackgroundDeliverToUI()) + .doOnTerminate(this::closeDialog) + .subscribe(result -> { + Token token = result.get(); + IStorage storage = Deps.storage(); + storage.putString(KEY_TOKEN, token.getAuthenticationToken()); + storage.putString(KEY_EMAIL, token.getEmail()); + startActivity(new Intent(this, TodosListScreen.class)); + }, Errors.handle(this)); + } + + private void closeDialog() { + if (dialog != null) { + dialog.dismiss(); + dialog = null; + } + } + + @OnClick(R.id.create_account) void onCreateAccountClick() { + Navigator.createAccount(this); + } + + @Override protected void onStop() { + if (signingSubs != null) { + signingSubs.unsubscribe(); + signingSubs = null; + } + super.onStop(); + } + + private Document createSignInDoc() { + User user = new User(TextViewUtils.textOf(uiEmail), TextViewUtils.textOf(uiPassword)); + ObjectDocument userDocument = new ObjectDocument<>(); + user.setType("sessions"); + userDocument.set(user); + return userDocument; + } +} \ No newline at end of file diff --git a/JsonApiExample/app/src/main/java/com/flatstack/android/models/Status.java b/JsonApiExample/app/src/main/java/com/flatstack/android/models/Status.java new file mode 100644 index 0000000..73c0ad7 --- /dev/null +++ b/JsonApiExample/app/src/main/java/com/flatstack/android/models/Status.java @@ -0,0 +1,11 @@ +package com.flatstack.android.models; + +import com.squareup.moshi.Json; + +/** + * Created by ereminilya on 29/3/17. + */ +public enum Status { + + @Json(name = "created")CREATED, @Json(name = "completed")COMPLETED +} \ No newline at end of file diff --git a/JsonApiExample/app/src/main/java/com/flatstack/android/models/ToDo.java b/JsonApiExample/app/src/main/java/com/flatstack/android/models/ToDo.java new file mode 100644 index 0000000..1b883ec --- /dev/null +++ b/JsonApiExample/app/src/main/java/com/flatstack/android/models/ToDo.java @@ -0,0 +1,72 @@ +package com.flatstack.android.models; + +import android.os.Parcel; +import android.os.Parcelable; + +import moe.banana.jsonapi2.JsonApi; +import moe.banana.jsonapi2.Resource; + +/** + * Created by ereminilya on 24/3/17. + */ +@JsonApi(type = "todo-items") +public class ToDo extends Resource implements Parcelable { + + private String title; + private String text; + private Status status; + + public ToDo() { + } + + public ToDo(String title, String text) { + this.title = title; + this.text = text; + } + + public String getTitle() { + return title; + } + + public String getText() { + return text; + } + + public void setCompleted(boolean completed) { + status = completed ? Status.COMPLETED : Status.CREATED; + } + + @Override public int describeContents() { + return 0; + } + + @Override public void writeToParcel(Parcel dest, int flags) { + dest.writeString(getId()); + dest.writeString(this.title); + dest.writeString(this.text); + dest.writeInt(this.status == null ? -1 : this.status.ordinal()); + } + + public boolean isCompleted() { + return status == Status.COMPLETED; + } + + protected ToDo(Parcel in) { + this.setId(in.readString()); + + this.title = in.readString(); + this.text = in.readString(); + int tmpStatus = in.readInt(); + this.status = tmpStatus == -1 ? null : Status.values()[tmpStatus]; + } + + public static final Creator CREATOR = new Creator() { + @Override public ToDo createFromParcel(Parcel source) { + return new ToDo(source); + } + + @Override public ToDo[] newArray(int size) { + return new ToDo[size]; + } + }; +} \ No newline at end of file diff --git a/JsonApiExample/app/src/main/java/com/flatstack/android/models/Token.java b/JsonApiExample/app/src/main/java/com/flatstack/android/models/Token.java new file mode 100644 index 0000000..06bd4d8 --- /dev/null +++ b/JsonApiExample/app/src/main/java/com/flatstack/android/models/Token.java @@ -0,0 +1,25 @@ +package com.flatstack.android.models; + +import com.squareup.moshi.Json; + +import moe.banana.jsonapi2.JsonApi; +import moe.banana.jsonapi2.Resource; + +/** + * Created by ereminilya on 24/3/17. + */ +@JsonApi(type = "sessions") +public class Token extends Resource { + + private @Json(name = "authentication-token") + String authenticationToken; + private String email; + + public String getAuthenticationToken() { + return authenticationToken; + } + + public String getEmail() { + return email; + } +} \ No newline at end of file diff --git a/JsonApiExample/app/src/main/java/com/flatstack/android/models/User.java b/JsonApiExample/app/src/main/java/com/flatstack/android/models/User.java new file mode 100644 index 0000000..0416089 --- /dev/null +++ b/JsonApiExample/app/src/main/java/com/flatstack/android/models/User.java @@ -0,0 +1,33 @@ +package com.flatstack.android.models; + +import moe.banana.jsonapi2.JsonApi; +import moe.banana.jsonapi2.Resource; + +/** + * Created by ereminilya on 24/3/17. + */ + +@JsonApi(type = "users") +public class User extends Resource { + + private String id; + private String email; + private String password; + + public User() { + } + + public User(String email, String password) { + this.email = email; + this.password = password; + } + + @Override public String toString() { + return "User{" + + "id='" + id + '\'' + + ", email='" + email + '\'' + + ", password='" + password + '\'' + + '}'; + } +} + diff --git a/JsonApiExample/app/src/main/java/com/flatstack/android/todo_details/CreateToDoScreen.java b/JsonApiExample/app/src/main/java/com/flatstack/android/todo_details/CreateToDoScreen.java new file mode 100644 index 0000000..f6693d9 --- /dev/null +++ b/JsonApiExample/app/src/main/java/com/flatstack/android/todo_details/CreateToDoScreen.java @@ -0,0 +1,58 @@ +package com.flatstack.android.todo_details; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.os.Parcelable; +import android.support.annotation.NonNull; +import android.view.View; +import android.widget.EditText; + +import com.flatstack.android.Deps; +import com.flatstack.android.R; +import com.flatstack.android.models.ToDo; +import com.flatstack.android.utils.Errors; +import com.flatstack.android.utils.Rxs; +import com.flatstack.android.utils.TextViewUtils; +import com.flatstack.android.utils.ui.BaseActivity; +import com.flatstack.android.utils.ui.UiInfo; + +import butterknife.Bind; +import butterknife.OnClick; +import es.dmoral.toasty.Toasty; +import moe.banana.jsonapi2.ObjectDocument; + +/** + * Created by ereminilya on 29/3/17. + */ +public class CreateToDoScreen extends BaseActivity { + + public static final String KEY_NEWLY_CREATED_TODO = "newlyCreatedToDo"; + @Bind(R.id.title) EditText uiTitle; + @Bind(R.id.description) EditText uiDescription; + + @NonNull @Override public UiInfo getUiInfo() { + return new UiInfo(R.layout.screen_todo_details).enableBackButton() + .setTitleRes(R.string.title_create_todo); + } + + @Override protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + findViewById(R.id.completed).setVisibility(View.GONE); + } + + @OnClick(R.id.save) void onSaveClick() { + ToDo toDo = new ToDo(TextViewUtils.textOf(uiTitle), TextViewUtils.textOf(uiDescription)); + ObjectDocument document = new ObjectDocument<>(); + document.set(toDo); + Deps.api().createToDo(document) + .compose(Rxs.doInBackgroundDeliverToUI()) + .subscribe(result -> { + Toasty.success(CreateToDoScreen.this, getString(R.string.todo_has_been_created)).show(); + Intent intent = new Intent(); + intent.putExtra(KEY_NEWLY_CREATED_TODO, (Parcelable) toDo); + setResult(Activity.RESULT_OK, intent); + finish(); + }, Errors.handle(this)); + } +} \ No newline at end of file diff --git a/JsonApiExample/app/src/main/java/com/flatstack/android/todo_details/UpdateToDoScreen.java b/JsonApiExample/app/src/main/java/com/flatstack/android/todo_details/UpdateToDoScreen.java new file mode 100644 index 0000000..2068f8d --- /dev/null +++ b/JsonApiExample/app/src/main/java/com/flatstack/android/todo_details/UpdateToDoScreen.java @@ -0,0 +1,78 @@ +package com.flatstack.android.todo_details; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.os.Parcelable; +import android.support.annotation.NonNull; +import android.widget.CheckBox; +import android.widget.EditText; + +import com.flatstack.android.Deps; +import com.flatstack.android.R; +import com.flatstack.android.models.ToDo; +import com.flatstack.android.utils.Errors; +import com.flatstack.android.utils.Rxs; +import com.flatstack.android.utils.TextViewUtils; +import com.flatstack.android.utils.ui.BaseActivity; +import com.flatstack.android.utils.ui.UiInfo; + +import butterknife.Bind; +import butterknife.OnClick; +import es.dmoral.toasty.Toasty; +import moe.banana.jsonapi2.ObjectDocument; + +/** + * Created by ereminilya on 29/3/17. + */ + +public class UpdateToDoScreen extends BaseActivity { + + private static final String KEY_TODO = "toDo"; + + public static Intent newInstance(@NonNull Context context, @NonNull ToDo toDo) { + Intent intent = new Intent(context, UpdateToDoScreen.class); + intent.putExtra(KEY_TODO, (Parcelable) toDo); + return intent; + } + + @Bind(R.id.title) EditText uiTitle; + @Bind(R.id.description) EditText uiDescription; + @Bind(R.id.completed) CheckBox uiCompletedCb; + + private ToDo todoToUpdate; + + @NonNull @Override public UiInfo getUiInfo() { + return new UiInfo(R.layout.screen_todo_details).enableBackButton() + .setTitleRes(R.string.title_edit_todo); + } + + @Override protected void parseArguments(@NonNull Bundle extras) { + todoToUpdate = extras.getParcelable(KEY_TODO); + } + + @Override protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + uiTitle.setText(todoToUpdate.getTitle()); + uiDescription.setText(todoToUpdate.getText()); + uiCompletedCb.setChecked(todoToUpdate.isCompleted()); + } + + @OnClick(R.id.save) void onSaveClick() { + Deps.api().updateToDo(todoToUpdate.getId(), prepareRequest()) + .compose(Rxs.doInBackgroundDeliverToUI()) + .subscribe(result -> { + Toasty.success(this, getString(R.string.to_do_has_been_updated)).show(); + finish(); + }, Errors.handle(this)); + } + + @NonNull private ObjectDocument prepareRequest() { + ToDo toDo = new ToDo(TextViewUtils.textOf(uiTitle), TextViewUtils.textOf(uiDescription)); + toDo.setId(todoToUpdate.getId()); + toDo.setCompleted(uiCompletedCb.isChecked()); + ObjectDocument doc = new ObjectDocument<>(); + doc.set(toDo); + return doc; + } +} \ No newline at end of file diff --git a/JsonApiExample/app/src/main/java/com/flatstack/android/todo_list/TodoHolder.java b/JsonApiExample/app/src/main/java/com/flatstack/android/todo_list/TodoHolder.java new file mode 100644 index 0000000..a7d1fb5 --- /dev/null +++ b/JsonApiExample/app/src/main/java/com/flatstack/android/todo_list/TodoHolder.java @@ -0,0 +1,33 @@ +package com.flatstack.android.todo_list; + +import android.view.View; +import android.widget.CheckBox; +import android.widget.TextView; + +import com.flatstack.android.R; +import com.flatstack.android.models.ToDo; +import com.flatstack.android.utils.recycler_view.BaseHolder; + +import butterknife.Bind; +import butterknife.ButterKnife; + +/** + * Created by ereminilya on 24/3/17. + */ +public class TodoHolder extends BaseHolder { + + @Bind(R.id.title) TextView uiTitle; + @Bind(R.id.details) TextView uiText; + @Bind(R.id.completed) CheckBox uiStatusCompleted; + + public TodoHolder(View itemView) { + super(itemView); + ButterKnife.bind(this, itemView); + } + + @Override protected void bind(ToDo item) { + uiTitle.setText(item.getTitle()); + uiText.setText(item.getText()); + uiStatusCompleted.setChecked(item.isCompleted()); + } +} \ No newline at end of file diff --git a/JsonApiExample/app/src/main/java/com/flatstack/android/todo_list/TodosAdapter.java b/JsonApiExample/app/src/main/java/com/flatstack/android/todo_list/TodosAdapter.java new file mode 100644 index 0000000..5221879 --- /dev/null +++ b/JsonApiExample/app/src/main/java/com/flatstack/android/todo_list/TodosAdapter.java @@ -0,0 +1,21 @@ +package com.flatstack.android.todo_list; + +import android.view.LayoutInflater; + +import com.flatstack.android.R; +import com.flatstack.android.models.ToDo; +import com.flatstack.android.utils.recycler_view.BaseAdapter; +import com.flatstack.android.utils.recycler_view.OnItemClickListener; + +import java.util.List; + +/** + * Created by ereminilya on 24/3/17. + */ +class TodosAdapter extends BaseAdapter { + + TodosAdapter(List toDos, OnItemClickListener itemClickListener) { + super(toDos, viewGroup -> new TodoHolder(LayoutInflater.from(viewGroup.getContext()) + .inflate(R.layout.item_todo, viewGroup, false)), itemClickListener); + } +} \ No newline at end of file diff --git a/JsonApiExample/app/src/main/java/com/flatstack/android/todo_list/TodosListScreen.java b/JsonApiExample/app/src/main/java/com/flatstack/android/todo_list/TodosListScreen.java new file mode 100644 index 0000000..f6d4cfd --- /dev/null +++ b/JsonApiExample/app/src/main/java/com/flatstack/android/todo_list/TodosListScreen.java @@ -0,0 +1,115 @@ +package com.flatstack.android.todo_list; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.widget.SwipeRefreshLayout; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.MenuItem; +import android.view.View; + +import com.flatstack.android.Deps; +import com.flatstack.android.Navigator; +import com.flatstack.android.R; +import com.flatstack.android.models.ToDo; +import com.flatstack.android.todo_details.CreateToDoScreen; +import com.flatstack.android.utils.Errors; +import com.flatstack.android.utils.Lists; +import com.flatstack.android.utils.Rxs; +import com.flatstack.android.utils.storage.IStorage; +import com.flatstack.android.utils.ui.BaseActivity; +import com.flatstack.android.utils.ui.UiInfo; + +import java.util.ArrayList; +import java.util.List; + +import butterknife.Bind; +import butterknife.OnClick; + +/** + * Created by ereminilya on 24/3/17. + */ + +public class TodosListScreen extends BaseActivity { + + private static final int REQUEST_CODE_CREATE_TODO = 5544; + + @Bind(R.id.list) RecyclerView uiTodos; + @Bind(R.id.pull_to_refresh) SwipeRefreshLayout uiPullToRefresh; + @Bind(R.id.empty) View uiEmptyView; + + private List toDos; + + @NonNull @Override public UiInfo getUiInfo() { + return new UiInfo(R.layout.screen_todos) + .setTitleRes(R.string.title_todos) + .setMenuRes(R.menu.menu_with_logout); + } + + @Override protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + LinearLayoutManager layoutManager = new LinearLayoutManager(this); + layoutManager.setStackFromEnd(true); + layoutManager.setReverseLayout(true); + uiTodos.setLayoutManager(layoutManager); + if (toDos == null) { + retrieveTodos(); + } + uiPullToRefresh.setOnRefreshListener(this::retrieveTodos); + } + + @Override public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == R.id.logout) { + Deps.storage().remove(IStorage.KEY_EMAIL); + Deps.storage().remove(IStorage.KEY_TOKEN); + finish(); + Navigator.signIn(this); + return true; + } + return super.onOptionsItemSelected(item); + } + + private void retrieveTodos() { + uiPullToRefresh.setRefreshing(true); + Deps.api().todos() + .compose(Rxs.doInBackgroundDeliverToUI()) + .doOnTerminate(() -> uiPullToRefresh.setRefreshing(false)) + .subscribe(result -> { + toDos = Lists.copyIterator(result.iterator()); + if (toDos.isEmpty()) { + uiEmptyView.setVisibility(View.VISIBLE); + } else { + fillAdapter(toDos); + } + }, Errors.handle(this)); + } + + private void fillAdapter(List toDos) { + uiEmptyView.setVisibility(View.GONE); + uiTodos.setAdapter(new TodosAdapter(toDos, item -> + Navigator.todoDetails(TodosListScreen.this, item) + ) + ); + } + + @OnClick(R.id.add) void onAddTodoClick() { + Navigator.createToDoAndReturnBack(this, REQUEST_CODE_CREATE_TODO); + } + + @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode == REQUEST_CODE_CREATE_TODO && resultCode == Activity.RESULT_OK) { + ToDo newlyCreatedToDo = data.getExtras() + .getParcelable(CreateToDoScreen.KEY_NEWLY_CREATED_TODO); + if (toDos == null) { + toDos = new ArrayList<>(); + } + toDos.add(newlyCreatedToDo); + fillAdapter(toDos); + } else { + super.onActivityResult(requestCode, resultCode, data); + } + } +} \ No newline at end of file diff --git a/JsonApiExample/app/src/main/java/com/flatstack/android/utils/Errors.java b/JsonApiExample/app/src/main/java/com/flatstack/android/utils/Errors.java new file mode 100644 index 0000000..9bed28f --- /dev/null +++ b/JsonApiExample/app/src/main/java/com/flatstack/android/utils/Errors.java @@ -0,0 +1,48 @@ +package com.flatstack.android.utils; + +import android.content.Context; +import android.util.Log; +import android.widget.Toast; + +import com.flatstack.android.Deps; + +import java.io.IOException; +import java.util.List; + +import es.dmoral.toasty.Toasty; +import moe.banana.jsonapi2.Document; +import moe.banana.jsonapi2.Error; +import retrofit2.adapter.rxjava.HttpException; +import rx.functions.Action1; + +/** + * Created by ereminilya on 24/3/17. + */ + +public class Errors { + + private static final String TAG = "Errors"; + + public static void handle(Context context, Throwable error) { + Document document = parseError((HttpException) error); + String errors = Lists.listToString((List) document.errors(), "\n", + er -> er.getTitle() + (er.getDetail() == null ? "" : (" " + er.getDetail())) + ); + Toasty.error(context, errors, Toast.LENGTH_LONG).show(); + Log.e(TAG, "something bad happens", error); + } + + private static Document parseError(HttpException error) { + try { + return Deps.jsonConverter().adapter(Document.class) + .fromJson(error.response().errorBody().source()); + } catch (IOException e) { + e.printStackTrace(); + return null; + } + } + + public static Action1 handle(Context context) { + return throwable -> handle(context, throwable); + } +} diff --git a/JsonApiExample/app/src/main/java/com/flatstack/android/utils/Keyboard.java b/JsonApiExample/app/src/main/java/com/flatstack/android/utils/Keyboard.java new file mode 100755 index 0000000..295b3e0 --- /dev/null +++ b/JsonApiExample/app/src/main/java/com/flatstack/android/utils/Keyboard.java @@ -0,0 +1,40 @@ +package com.flatstack.android.utils; + +import android.app.Activity; +import android.content.Context; +import android.support.annotation.NonNull; +import android.view.View; +import android.view.inputmethod.InputMethodManager; + +public class Keyboard { + + public static void hide(@NonNull Activity activity) { + InputMethodManager inputManager = (InputMethodManager) activity + .getSystemService(Context.INPUT_METHOD_SERVICE); + View v = activity.getCurrentFocus(); + if (v != null) { + inputManager.hideSoftInputFromWindow(v.getWindowToken(), 0); + } + } + + public static void hide(@NonNull View view) { + InputMethodManager imm = (InputMethodManager) view.getContext() + .getSystemService(Context.INPUT_METHOD_SERVICE); + if (!imm.isActive()) { + return; + } + imm.hideSoftInputFromWindow(view.getWindowToken(), 0); + } + + public static void show(@NonNull Context context) { + InputMethodManager imm = (InputMethodManager) context + .getSystemService(Context.INPUT_METHOD_SERVICE); + imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0); + } + + public static void show(@NonNull View view) { + InputMethodManager inputManager = (InputMethodManager) view.getContext() + .getSystemService(Context.INPUT_METHOD_SERVICE); + inputManager.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT); + } +} diff --git a/JsonApiExample/app/src/main/java/com/flatstack/android/utils/Lists.java b/JsonApiExample/app/src/main/java/com/flatstack/android/utils/Lists.java new file mode 100755 index 0000000..b01512d --- /dev/null +++ b/JsonApiExample/app/src/main/java/com/flatstack/android/utils/Lists.java @@ -0,0 +1,53 @@ +package com.flatstack.android.utils; + +import android.support.annotation.NonNull; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import rx.functions.Func1; + +/** + * Created by adel on 6/7/14 + */ +public class Lists { + @SafeVarargs @NonNull public static List mutableOf(@NonNull T... objects) { + List list = new ArrayList<>(objects.length); + Collections.addAll(list, objects); + return list; + } + + @SafeVarargs @NonNull public static List add(@NonNull List list, + @NonNull T... objects) { + Collections.addAll(list, objects); + return list; + } + + @NonNull + public static String listToString(@NonNull List strings, @NonNull String separator) { + return listToString(strings, separator, s -> s); + } + + @NonNull + public static String listToString(@NonNull List strings, @NonNull String separator, + @NonNull Func1 converter) { + String result = ""; + if (strings.size() > 0) { + for (int i = 0; i < strings.size() - 1; i++) { + result += converter.call(strings.get(i)) + separator; + } + result += converter.call(strings.get(strings.size() - 1)); + } + return result; + } + + public static List copyIterator(Iterator iter) { + List copy = new ArrayList<>(); + while (iter.hasNext()) + copy.add(iter.next()); + return copy; + } + +} \ No newline at end of file diff --git a/JsonApiExample/app/src/main/java/com/flatstack/android/utils/Rxs.java b/JsonApiExample/app/src/main/java/com/flatstack/android/utils/Rxs.java new file mode 100644 index 0000000..518361a --- /dev/null +++ b/JsonApiExample/app/src/main/java/com/flatstack/android/utils/Rxs.java @@ -0,0 +1,21 @@ +package com.flatstack.android.utils; + +import rx.Observable; +import rx.android.schedulers.AndroidSchedulers; +import rx.schedulers.Schedulers; + +/** + * Created by Ilya Eremin on 4/25/16. + */ +public class Rxs { + + public static Observable.Transformer doInBackgroundDeliverToUI() { + return observable -> observable.subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()); + } + + public static Observable.Transformer doInBackgroundDeliverToBackground() { + return observable -> observable.subscribeOn(Schedulers.io()) + .observeOn(Schedulers.io()); + } +} \ No newline at end of file diff --git a/JsonApiExample/app/src/main/java/com/flatstack/android/utils/TextViewUtils.java b/JsonApiExample/app/src/main/java/com/flatstack/android/utils/TextViewUtils.java new file mode 100644 index 0000000..7c058f4 --- /dev/null +++ b/JsonApiExample/app/src/main/java/com/flatstack/android/utils/TextViewUtils.java @@ -0,0 +1,15 @@ +package com.flatstack.android.utils; + +import android.widget.TextView; + +/** + * Created by ereminilya on 24/3/17. + */ + +public class TextViewUtils { + + public static String textOf(TextView tv) { + return tv.getText().toString(); + } + +} diff --git a/JsonApiExample/app/src/main/java/com/flatstack/android/utils/Views.java b/JsonApiExample/app/src/main/java/com/flatstack/android/utils/Views.java new file mode 100755 index 0000000..0ff47cf --- /dev/null +++ b/JsonApiExample/app/src/main/java/com/flatstack/android/utils/Views.java @@ -0,0 +1,24 @@ +package com.flatstack.android.utils; + +import android.app.Activity; +import android.support.annotation.NonNull; +import android.view.View; +import android.view.ViewGroup; + +/** + * Created by adelnizamutdinov on 04/12/14 + */ +public class Views { + + @NonNull public static View root(@NonNull Activity activity) { + return activity.findViewById(android.R.id.content); + } + + public static void setHeight(@NonNull View view, int height) { + final ViewGroup.LayoutParams params = view.getLayoutParams(); + if (params != null) { + params.height = height; + view.setLayoutParams(params); + } + } +} diff --git a/JsonApiExample/app/src/main/java/com/flatstack/android/utils/jsonapi/JsonApiConverterFactory.java b/JsonApiExample/app/src/main/java/com/flatstack/android/utils/jsonapi/JsonApiConverterFactory.java new file mode 100644 index 0000000..8344efa --- /dev/null +++ b/JsonApiExample/app/src/main/java/com/flatstack/android/utils/jsonapi/JsonApiConverterFactory.java @@ -0,0 +1,158 @@ +package com.flatstack.android.utils.jsonapi; + +import com.squareup.moshi.JsonAdapter; +import com.squareup.moshi.Moshi; +import com.squareup.moshi.Types; + +import java.io.IOException; +import java.lang.annotation.Annotation; +import java.lang.reflect.Array; +import java.lang.reflect.Type; + +import moe.banana.jsonapi2.ArrayDocument; +import moe.banana.jsonapi2.Document; +import moe.banana.jsonapi2.ObjectDocument; +import moe.banana.jsonapi2.Resource; +import moe.banana.jsonapi2.ResourceIdentifier; +import okhttp3.MediaType; +import okhttp3.RequestBody; +import okhttp3.ResponseBody; +import okio.Buffer; +import retrofit2.Converter; +import retrofit2.Retrofit; + +@SuppressWarnings("unchecked") +public final class JsonApiConverterFactory extends Converter.Factory { + + public static JsonApiConverterFactory create() { + return create(new Moshi.Builder().build()); + } + + public static JsonApiConverterFactory create(Moshi moshi) { + return new JsonApiConverterFactory(moshi, false); + } + + private final Moshi moshi; + private final boolean lenient; + + private JsonApiConverterFactory(Moshi moshi, boolean lenient) { + if (moshi == null) throw new NullPointerException("moshi == null"); + this.moshi = moshi; + this.lenient = lenient; + } + + public JsonApiConverterFactory asLenient() { + return new JsonApiConverterFactory(moshi, true); + } + + @Override + public Converter responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) { + Class rawType = Types.getRawType(type); + JsonAdapter adapter; + if (rawType.isArray() && ResourceIdentifier.class.isAssignableFrom(rawType.getComponentType())) { + adapter = moshi.adapter(Types.newParameterizedType(Document.class, rawType.getComponentType())); + } else if (ResourceIdentifier.class.isAssignableFrom(rawType)) { + adapter = moshi.adapter(Types.newParameterizedType(Document.class, rawType)); + } else if (Document.class.isAssignableFrom(rawType)) { + adapter = moshi.adapter(Types.newParameterizedType(Document.class, Resource.class)); + } else { + return null; + } + if (lenient) { + adapter = adapter.lenient(); + } + return new MoshiResponseBodyConverter<>((JsonAdapter>) adapter, rawType); + } + + @Override + public Converter requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) { + Class rawType = Types.getRawType(type); + JsonAdapter adapter; + if (rawType.isArray() && ResourceIdentifier.class.isAssignableFrom(rawType.getComponentType())) { + adapter = moshi.adapter(Types.newParameterizedType(Document.class, rawType.getComponentType())); + } else if (ResourceIdentifier.class.isAssignableFrom(rawType)) { + adapter = moshi.adapter(Types.newParameterizedType(Document.class, rawType)); + } else if (Document.class.isAssignableFrom(rawType)) { + adapter = moshi.adapter(Types.newParameterizedType(Document.class, Resource.class)); + } else { + return null; + } + if (lenient) { + adapter = adapter.lenient(); + } + return new MoshiRequestBodyConverter<>((JsonAdapter>) adapter, rawType); + } + + private static final MediaType MEDIA_TYPE = MediaType.parse("application/json; charset=UTF-8"); + + private static class MoshiResponseBodyConverter implements Converter { + private final JsonAdapter> adapter; + private final Class rawType; + + MoshiResponseBodyConverter(JsonAdapter> adapter, Class rawType) { + this.adapter = adapter; + this.rawType = rawType; + } + + @Override + public R convert(ResponseBody value) throws IOException { + try { + Document document = adapter.fromJson(value.source()); + if (Document.class.isAssignableFrom(rawType)) { + return (R) document; + } else if (rawType.isArray()) { + ArrayDocument arrayDocument = document.asArrayDocument(); + Object a = Array.newInstance(rawType.getComponentType(), arrayDocument.size()); + for (int i = 0; i != Array.getLength(a); i++) { + Array.set(a, i, arrayDocument.get(i)); + } + return (R) a; + } else { + return (R) document.asObjectDocument().get(); + } + } finally { + value.close(); + } + } + } + + private static class MoshiRequestBodyConverter implements Converter { + + private final JsonAdapter> adapter; + private final Class rawType; + + MoshiRequestBodyConverter(JsonAdapter> adapter, Class rawType) { + this.adapter = adapter; + this.rawType = rawType; + } + + @Override + public RequestBody convert(T value) throws IOException { + Document document; + if (Document.class.isAssignableFrom(rawType)) { + document = (Document) value; + } else if (rawType.isArray()) { + ArrayDocument arrayDocument = new ArrayDocument(); + if (Array.getLength(value) > 0 && ((ResourceIdentifier) Array.get(value, 0)).getContext() != null) { + arrayDocument = ((ResourceIdentifier) Array.get(value, 0)).getContext().asArrayDocument(); + } + for (int i = 0; i != Array.getLength(value); i++) { + arrayDocument.add((ResourceIdentifier) Array.get(value, i)); + } + document = arrayDocument; + } else { + ResourceIdentifier data = ((ResourceIdentifier) value); + ObjectDocument objectDocument = new ObjectDocument(); + if (data.getContext() != null) { + objectDocument = data.getContext().asObjectDocument(); + } + objectDocument.set(data); + document = objectDocument; + } + Buffer buffer = new Buffer(); + adapter.toJson(buffer, document); + return RequestBody.create(MEDIA_TYPE, buffer.readByteString()); + } + } + +} diff --git a/JsonApiExample/app/src/main/java/com/flatstack/android/utils/recycler_view/BaseAdapter.java b/JsonApiExample/app/src/main/java/com/flatstack/android/utils/recycler_view/BaseAdapter.java new file mode 100755 index 0000000..22b78d7 --- /dev/null +++ b/JsonApiExample/app/src/main/java/com/flatstack/android/utils/recycler_view/BaseAdapter.java @@ -0,0 +1,60 @@ +package com.flatstack.android.utils.recycler_view; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.widget.RecyclerView; +import android.view.ViewGroup; + +import java.util.List; + +import rx.functions.Func1; + +/** + * Created by ereminilya on 14/12/16. + */ +public class BaseAdapter> extends RecyclerView.Adapter { + + @NonNull private final Func1 func0; + @NonNull private final List items; + + @Nullable private OnItemClickListener onItemClickListener; + + public BaseAdapter(@NonNull List items, @NonNull Func1 func0) { + this(items, func0, null); + } + + public BaseAdapter(@NonNull List items, @NonNull Func1 func0, + @Nullable OnItemClickListener onItemClickListener) { + this.items = items; + this.func0 = func0; + this.onItemClickListener = onItemClickListener; + } + + @Override public VH onCreateViewHolder(ViewGroup viewGroup, int holderType) { + VH holder = func0.call(viewGroup); + holder.itemView.setOnClickListener(v -> { + int position = holder.getAdapterPosition(); + if (position != RecyclerView.NO_POSITION && onItemClickListener != null) { + onItemClickListener.onItemClick(getData().get(position)); + } + }); + return holder; + } + + @Override public void onBindViewHolder(VH vh, int position) { + vh.bind(items.get(position)); + } + + @Override public int getItemCount() { + return items.size(); + } + + public List getData() { + return items; + } + + public void add(T item) { + this.items.add(item); + notifyItemInserted(items.size() - 1); + } +} \ No newline at end of file diff --git a/JsonApiExample/app/src/main/java/com/flatstack/android/utils/recycler_view/BaseHolder.java b/JsonApiExample/app/src/main/java/com/flatstack/android/utils/recycler_view/BaseHolder.java new file mode 100755 index 0000000..1b986b4 --- /dev/null +++ b/JsonApiExample/app/src/main/java/com/flatstack/android/utils/recycler_view/BaseHolder.java @@ -0,0 +1,16 @@ +package com.flatstack.android.utils.recycler_view; + +import android.support.v7.widget.RecyclerView; +import android.view.View; + +/** + * Created by ereminilya on 14/12/16. + */ +public abstract class BaseHolder extends RecyclerView.ViewHolder { + + public BaseHolder(View itemView) { + super(itemView); + } + + protected abstract void bind(T item); +} \ No newline at end of file diff --git a/JsonApiExample/app/src/main/java/com/flatstack/android/utils/recycler_view/OnItemClickListener.java b/JsonApiExample/app/src/main/java/com/flatstack/android/utils/recycler_view/OnItemClickListener.java new file mode 100755 index 0000000..79a8305 --- /dev/null +++ b/JsonApiExample/app/src/main/java/com/flatstack/android/utils/recycler_view/OnItemClickListener.java @@ -0,0 +1,11 @@ +package com.flatstack.android.utils.recycler_view; + +import android.support.annotation.NonNull; + +/** + * Created by ereminilya on 14/12/16. + */ +public interface OnItemClickListener { + + void onItemClick(@NonNull T item); +} diff --git a/JsonApiExample/app/src/main/java/com/flatstack/android/utils/storage/IStorage.java b/JsonApiExample/app/src/main/java/com/flatstack/android/utils/storage/IStorage.java new file mode 100755 index 0000000..9dfd443 --- /dev/null +++ b/JsonApiExample/app/src/main/java/com/flatstack/android/utils/storage/IStorage.java @@ -0,0 +1,43 @@ +package com.flatstack.android.utils.storage; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import java.lang.reflect.Type; +import java.util.List; + +/** + * Created by ereminilya on 8/1/17. + */ + +public interface IStorage { + + String KEY_TOKEN = "token"; + String KEY_EMAIL = "email"; + + @Nullable T get(@NonNull String key, @NonNull Type type); + + void put(@NonNull String key, @NonNull T item); + + void putString(@NonNull final String key, @NonNull String str); + + @Nullable String getString(@NonNull String key); + + void putLong(@NonNull String key, long number); + + long getLong(@NonNull String key, long defaultValue); + + void putInt(@NonNull String key, int number); + + int getInt(@NonNull String key, int defaultValue); + + void putBoolean(@NonNull String key, boolean value); + + boolean getBoolean(@NonNull String key, boolean defaultValue); + + void remove(@NonNull String key); + + void putCollection(@NonNull String key, @NonNull List items); + + @Nullable List getCollection(@NonNull String key, @NonNull Type type); +} diff --git a/JsonApiExample/app/src/main/java/com/flatstack/android/utils/storage/Storage.java b/JsonApiExample/app/src/main/java/com/flatstack/android/utils/storage/Storage.java new file mode 100755 index 0000000..b395c3f --- /dev/null +++ b/JsonApiExample/app/src/main/java/com/flatstack/android/utils/storage/Storage.java @@ -0,0 +1,83 @@ +package com.flatstack.android.utils.storage; + +import android.content.SharedPreferences; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import com.google.gson.Gson; + +import java.lang.reflect.Type; +import java.util.List; + +/** + * Created by ereminilya on 8/1/17. + */ + +public class Storage implements IStorage { + + private final SharedPreferences sp; + private final Gson gson; + + public Storage(SharedPreferences sp, Gson gson) { + this.sp = sp; + this.gson = gson; + } + + @Override + @Nullable public T get(@NonNull String key, @NonNull Type type) { + String json = sp.getString(key, ""); + if ("".equals(json)) return null; + else { + return gson.fromJson(json, type); + } + } + + @Override public void put(@NonNull String key, @NonNull Object items) { + sp.edit().putString(key, gson.toJson(items)).apply(); + } + + @Override public void putString(@NonNull String key, @NonNull String str) { + sp.edit().putString(key, str).apply(); + } + + @Nullable @Override public String getString(@NonNull String key) { + return sp.getString(key, null); + } + + @Override public void putLong(@NonNull String key, long number) { + sp.edit().putLong(key, number).apply(); + } + + @Override public long getLong(@NonNull String key, long defaultValue) { + return sp.getLong(key, defaultValue); + } + + @Override public void putInt(@NonNull String key, int number) { + sp.edit().putInt(key, number).apply(); + } + + @Override public int getInt(@NonNull String key, int defaultValue) { + return sp.getInt(key, defaultValue); + } + + @Override public void putBoolean(@NonNull String key, boolean value) { + sp.edit().putBoolean(key, value).apply(); + } + + @Override public boolean getBoolean(@NonNull String key, boolean defaultValue) { + return sp.getBoolean(key, defaultValue); + } + + @Override public void remove(@NonNull String key) { + sp.edit().remove(key).apply(); + } + + public void putCollection(@NonNull String key, @NonNull List items) { + put(key, items); + } + + @Override public List getCollection(@NonNull String key, @NonNull Type type) { + return get(key, type); + } + +} \ No newline at end of file diff --git a/JsonApiExample/app/src/main/java/com/flatstack/android/utils/ui/BaseActivity.java b/JsonApiExample/app/src/main/java/com/flatstack/android/utils/ui/BaseActivity.java new file mode 100755 index 0000000..5a543b8 --- /dev/null +++ b/JsonApiExample/app/src/main/java/com/flatstack/android/utils/ui/BaseActivity.java @@ -0,0 +1,82 @@ +package com.flatstack.android.utils.ui; + +import android.content.Context; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.Toolbar; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; + +import com.flatstack.android.R; +import com.flatstack.android.utils.Keyboard; + +import butterknife.Bind; +import butterknife.ButterKnife; + +public abstract class BaseActivity extends AppCompatActivity { + + @Nullable @Bind(R.id.toolbar) View toolbar; + + @NonNull public abstract UiInfo getUiInfo(); + + @Override protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(getUiInfo().getLayoutRes()); + ButterKnife.bind(this); + if (getIntent() != null && getIntent().getExtras() != null) { + parseArguments(getIntent().getExtras()); + } + if (toolbar != null) { + setSupportActionBar((Toolbar) toolbar); + if (getUiInfo().getTitleRes() != 0) { + setTitle(getUiInfo().getTitleRes()); + } else if (getUiInfo().getTitle() != null) { + setTitle(getUiInfo().getTitle()); + } + if (getUiInfo().isHasBackButton()) { + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + } + } + if (savedInstanceState != null) { + restoreState(savedInstanceState); + } + } + + @Override public void setContentView(int layoutResID) { + super.setContentView(layoutResID); + ButterKnife.bind(this); + } + + protected void restoreState(@NonNull Bundle savedState) { + } + + protected void parseArguments(@NonNull Bundle extras) { + throw new IllegalStateException("should be overridden"); + } + + @Override public boolean onCreateOptionsMenu(Menu menu) { + if (getUiInfo().getMenuRes() != 0) { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(getUiInfo().getMenuRes(), menu); + return true; + } + return super.onCreateOptionsMenu(menu); + } + + @Override public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == android.R.id.home && getUiInfo().isHasBackButton()) { + Keyboard.hide(this); + finish(); + return true; + } + return super.onOptionsItemSelected(item); + } + + public Context context() { + return this; + } +} diff --git a/JsonApiExample/app/src/main/java/com/flatstack/android/utils/ui/BaseDialogFragment.java b/JsonApiExample/app/src/main/java/com/flatstack/android/utils/ui/BaseDialogFragment.java new file mode 100755 index 0000000..d09d698 --- /dev/null +++ b/JsonApiExample/app/src/main/java/com/flatstack/android/utils/ui/BaseDialogFragment.java @@ -0,0 +1,84 @@ +package com.flatstack.android.utils.ui; + +import android.app.Dialog; +import android.content.Context; +import android.os.Bundle; +import android.support.annotation.LayoutRes; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.app.DialogFragment; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentActivity; +import android.support.v4.app.FragmentTransaction; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; + +import butterknife.ButterKnife; + +public abstract class BaseDialogFragment extends DialogFragment { + + @LayoutRes public abstract int getLayoutRes(); + + protected static T show(@NonNull T dialogFragment, + @NonNull FragmentActivity activity) { + FragmentTransaction ft = activity.getSupportFragmentManager().beginTransaction(); + Fragment prev = activity.getSupportFragmentManager() + .findFragmentByTag(dialogFragment.getClass().getName()); + if (prev != null) { + ft.remove(prev); + DialogFragment df = (DialogFragment) prev; + df.dismissAllowingStateLoss(); + } + ft.addToBackStack(null); + dialogFragment.show(ft, dialogFragment.getClass().getName()); + return dialogFragment; + } + + @Override public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (getArguments() != null) { + parseArguments(getArguments()); + } + if (savedInstanceState != null) { + restoreState(savedInstanceState); + } + } + + protected void restoreState(@NonNull Bundle savedState) { + } + + protected void parseArguments(@NonNull Bundle args) { + throw new IllegalStateException("should be overridden"); + } + + @NonNull @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + Dialog dialog = super.onCreateDialog(savedInstanceState); + dialog.getWindow().requestFeature(Window.FEATURE_NO_TITLE); + return dialog; + } + + @Override + public View onCreateView(LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + View v = inflater.inflate(getLayoutRes(), container, false); + ButterKnife.bind(this, v); + return v; + } + + @Override public void onDestroyView() { + ButterKnife.unbind(this); + super.onDestroyView(); + } + + public Context context() { + return getActivity(); + } + + public FragmentActivity activity() { + return getActivity(); + } +} diff --git a/JsonApiExample/app/src/main/java/com/flatstack/android/utils/ui/BaseFragment.java b/JsonApiExample/app/src/main/java/com/flatstack/android/utils/ui/BaseFragment.java new file mode 100755 index 0000000..2395d3a --- /dev/null +++ b/JsonApiExample/app/src/main/java/com/flatstack/android/utils/ui/BaseFragment.java @@ -0,0 +1,40 @@ +package com.flatstack.android.utils.ui; + +import android.content.Context; +import android.os.Bundle; +import android.support.annotation.LayoutRes; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import butterknife.ButterKnife; + +public abstract class BaseFragment extends Fragment { + + @LayoutRes public abstract int getLayoutRes(); + + @Override public void onCreate(@Nullable Bundle savedState) { + super.onCreate(savedState); + if (savedState != null) { + restoreState(savedState); + } + } + + public void restoreState(@NonNull Bundle savedState) { + } + + @Nullable @Override public View onCreateView(LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + View rootView = inflater.inflate(getLayoutRes(), container, false); + ButterKnife.bind(this, rootView); + return rootView; + } + + protected Context context() { + return getContext(); + } +} diff --git a/JsonApiExample/app/src/main/java/com/flatstack/android/utils/ui/UiInfo.java b/JsonApiExample/app/src/main/java/com/flatstack/android/utils/ui/UiInfo.java new file mode 100755 index 0000000..26e2574 --- /dev/null +++ b/JsonApiExample/app/src/main/java/com/flatstack/android/utils/ui/UiInfo.java @@ -0,0 +1,63 @@ +package com.flatstack.android.utils.ui; + +import android.support.annotation.LayoutRes; +import android.support.annotation.MenuRes; +import android.support.annotation.NonNull; +import android.support.annotation.StringRes; + +/** + * Created by almaziskhakov on 20/12/2016. + */ + +public class UiInfo { + + @LayoutRes private final int layoutRes; + @StringRes private int titleRes; + @MenuRes private int menuRes; + private String titleStr; + private boolean hasBackButton; + + public UiInfo(@LayoutRes int layoutRes) { + this.layoutRes = layoutRes; + } + + public int getMenuRes() { + return menuRes; + } + + public UiInfo setMenuRes(int menuRes) { + this.menuRes = menuRes; + return this; + } + + public int getLayoutRes() { + return layoutRes; + } + + public int getTitleRes() { + return titleRes; + } + + public UiInfo setTitleRes(int titleRes) { + this.titleRes = titleRes; + return this; + } + + public UiInfo setTitle(@NonNull String title) { + this.titleStr = title; + return this; + } + + public String getTitle() { + return titleStr; + } + + public boolean isHasBackButton() { + return hasBackButton; + } + + public UiInfo enableBackButton() { + this.hasBackButton = true; + return this; + } +} diff --git a/JsonApiExample/app/src/main/res/drawable-hdpi/ic_add.png b/JsonApiExample/app/src/main/res/drawable-hdpi/ic_add.png new file mode 100644 index 0000000000000000000000000000000000000000..1ae5b2dc41be9b20a87e14da5270f22ef5c1c66b GIT binary patch literal 223 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpUtWu7jMAr-gYPP67}b>Lyx`ss4V zz5gmo99(ASt=9?$ozU9B{qbQs+cKV%gsCc=Kn-sS&pnmiyj(ZFW3I)Dw~LihZU)Uc z5*e#p#KfV{zyKr;yx3;B*^Xm{(lg`NPygP2cBXLipI7=x*Z!4RRS1P=|9)~$=t8#3 zihn_do9C7~tP)&k7I$QYeA$bdgZWcH`hkY*le-XKcKeT_!rrU7%nS?#;bP~tVphA{ S`galN00vK2KbLh*2~7Y7N?0)f literal 0 HcmV?d00001 diff --git a/JsonApiExample/app/src/main/res/drawable-hdpi/ic_launcher.png b/JsonApiExample/app/src/main/res/drawable-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..39d5920b7f8c3782c43183b80e7b804c71c9d17e GIT binary patch literal 1804 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 WGQRZ_>g|OLK) z6iSZdoMRJDeEa?f&kvu^`!9IEUT>_utvQHKln(#^fGjQG*ZZ(UyXY>d%H+u}rCl)|r1|_0S z9E^cCZ!lml;mIbeZWOAAMKW@25>W=$=I7-NM#S@np*xQvWD=c&&M?GV7X&3S*VOe( zBpp<#9*gcyPV`PxYC}1ZXsEQ20<}W3LbTpTb^W^j+8s;I(!i`Ro*Hg@Nkn8rO)ZZdl4B!@s5x68MSx(V9=U7p+|aFV8HpGCN+q znssq=D=q{dw z??%am96B6)+1>%+Mn-&J#ku9byU9RZU_cqCDRitq)~=yMq;yrE0mCN}V8eQ&5*FiX zOgYAhPXy?ktdQ4mM*a#Xq~BI4_c@5t7O^D{{Y|agnppDH&?{AlW$ofROPK2u z1WU}c3aPcPzeh4iTaX5Bs^7zOiHXlX{4;D)(o|K=$|-*+ke-kXF}4&q>A~f1 z#Od$r%`f?mnkCVIsidehJfUAuP_DRd8uUg836kUuDB`VJ-n>r(%Gu+CKIN8BvJ;cP~c_sdIxsd}CpKcg!s z!CHfIM)+qrQdj$%8c(HMp&N+NLVbM~6CWNjtEJ@8_%sM3MkP$}8b!S10f(6t*w12J zKTkfS!X2V{Lb2EukUcv&Ir%H+c~X)sUXjnuBc<0#0_`$+RtfH>Ob>qTopJl0$tq58 z;a`sz@)y}3p%bp>--^ufQ6C%ywCRql>?wmDlZUTXj2+t8fHZ|HQ=~YeEv3=UF`8lA zdIiBc1Odrl^9j0FMRL)rNJGgjZaHo53RWElMp=XXM9D;20KxU6{5w3dQtnaT08#SY z8^zB%LQ9@}t>?{#l*s>P25}U!pNj`Fa^C~wD~ZonQoma)nCU>FUpDN^sy27S4wpfi zw4bGA8L>&EtxkNjts_EcIc>zJ$iS=b8XBk*0*EwXS0sy#CC28PWbJ)M?}fhi^XZ=f zQ-BP!4>f)blG}ms#nKPq+5T<5Q|q8iLVK_>ny2ecPAS+B_DJHyf@oQ zcTY{ z{e{OZpRRD<06n(&CjM{c6co0POeWf_k;_7j@X{xJEhZ-Qo&*jNw-nXi^2nM~llL1< zMcR0k7Co_UaPuqaBQ*hs@hCf5KsRm1wm4KccpHE6;OtUh@;=?+dP)naSiIlHhm;OE z)@Nw^>U$hUh~CiD5HFtahayFU_#$zv%`l;mug(RXpg|iv2v(}cO$ok=tAC)vu@8EZ zomCcOpHG?hkQS3M!vT`2TV(lg__fdG@f>a?1lE_W^q2MkB@Q-UajpZ9J)t?Rsp zyjveI*R@(c;LvAQ)%QzPb_iy?I-QYH&NbD261fTFQB4v5_2`y2%{ko~GwoRWl8OzI z-_R|m8f`GzzmA&(YmAz_$M8gYk@Y9&T#g)&Zxm}tY5MBfu-WGebwgFdI$M^}`IeJ` zPj6ev9372KNhM}X_qgZnOh%a_-(K$(eTqG-q%|75!~m+5M3`pYAL z-EGX}Gf?T+yt%@r{-*1JUE;+DV)F7`tEZ+`d%?gc++iGu6NlsVFhA~&R8Rl35~&9| zZfY7Kyy)qXdUm&7V)U3D(t4O+plj4j`W~ka0nQ?N%F-7XSvO*U{v#TaSW-r_4f8&u0sYQ4Hq9V zuefs8@x#r#F>^`+!@thWVv3ac#cV2ZyZAw#lcM~zuyvuVK;1y#aK+@T(iyEYyHclp zH1^YqZ?HOJcxGoRpJCJFQ{oB2&!nDRH8MPO%ac=qfr+Dmfk~l(fdhjXZ@*yQ-nkn$ za~HqnDB#?Dw&L%m?|p1<-uBF`IB>p?q5sRwnCtr0?=ueQKDxjBP4pa~Q9yH;I6mAy zej&JPLh?J?{)Oki%$xA~*GIwX&|lpVbN6vtrp;F>+1v5fC~p6}uksFmL=-_fz}oN- zlZ6sCG5-42ck15lnGOy6_Aah`{}$p=hMFp7pU@xsr+54m0Eu|I`njxgN@xNA2Qh_P literal 0 HcmV?d00001 diff --git a/JsonApiExample/app/src/main/res/drawable-xxhdpi/ic_launcher.png b/JsonApiExample/app/src/main/res/drawable-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..79c2539e6f28d873151d60792b674f4563b5a818 GIT binary patch literal 4854 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 NCPWuptNJGLe*g*{Q}_S? literal 0 HcmV?d00001 diff --git a/JsonApiExample/app/src/main/res/drawable-xxxhdpi/ic_launcher.png b/JsonApiExample/app/src/main/res/drawable-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..28740e1a0a4e40f257f7cc6d1d5017a8ff74ea96 GIT binary patch literal 5445 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 sT1`&KX6}wDChD9@qW`0RU + + + + + + + \ No newline at end of file diff --git a/JsonApiExample/app/src/main/res/layout/dialog_test.xml b/JsonApiExample/app/src/main/res/layout/dialog_test.xml new file mode 100755 index 0000000..81ff62d --- /dev/null +++ b/JsonApiExample/app/src/main/res/layout/dialog_test.xml @@ -0,0 +1,32 @@ + + + + + + + + + + \ No newline at end of file diff --git a/JsonApiExample/app/src/main/res/layout/fragment_main.xml b/JsonApiExample/app/src/main/res/layout/fragment_main.xml new file mode 100755 index 0000000..c781802 --- /dev/null +++ b/JsonApiExample/app/src/main/res/layout/fragment_main.xml @@ -0,0 +1,27 @@ + + + + + + + + + diff --git a/JsonApiExample/app/src/main/res/layout/item_todo.xml b/JsonApiExample/app/src/main/res/layout/item_todo.xml new file mode 100644 index 0000000..ba84112 --- /dev/null +++ b/JsonApiExample/app/src/main/res/layout/item_todo.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/JsonApiExample/app/src/main/res/layout/screen_registration.xml b/JsonApiExample/app/src/main/res/layout/screen_registration.xml new file mode 100644 index 0000000..c8a1bc4 --- /dev/null +++ b/JsonApiExample/app/src/main/res/layout/screen_registration.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + +