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 0000000..b21ba61 Binary files /dev/null and b/JsonApiExample/app/src/main/ic_launcher-web.png differ 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 0000000..1ae5b2d Binary files /dev/null and b/JsonApiExample/app/src/main/res/drawable-hdpi/ic_add.png differ 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 0000000..39d5920 Binary files /dev/null and b/JsonApiExample/app/src/main/res/drawable-hdpi/ic_launcher.png differ diff --git a/JsonApiExample/app/src/main/res/drawable-mdpi/ic_add.png b/JsonApiExample/app/src/main/res/drawable-mdpi/ic_add.png new file mode 100644 index 0000000..d51f0dd Binary files /dev/null and b/JsonApiExample/app/src/main/res/drawable-mdpi/ic_add.png differ diff --git a/JsonApiExample/app/src/main/res/drawable-mdpi/ic_launcher.png b/JsonApiExample/app/src/main/res/drawable-mdpi/ic_launcher.png new file mode 100644 index 0000000..fc36cdc Binary files /dev/null and b/JsonApiExample/app/src/main/res/drawable-mdpi/ic_launcher.png differ diff --git a/JsonApiExample/app/src/main/res/drawable-xhdpi/ic_add.png b/JsonApiExample/app/src/main/res/drawable-xhdpi/ic_add.png new file mode 100644 index 0000000..9ea0eeb Binary files /dev/null and b/JsonApiExample/app/src/main/res/drawable-xhdpi/ic_add.png differ diff --git a/JsonApiExample/app/src/main/res/drawable-xhdpi/ic_launcher.png b/JsonApiExample/app/src/main/res/drawable-xhdpi/ic_launcher.png new file mode 100644 index 0000000..f0fb706 Binary files /dev/null and b/JsonApiExample/app/src/main/res/drawable-xhdpi/ic_launcher.png differ diff --git a/JsonApiExample/app/src/main/res/drawable-xxhdpi/ic_add.png b/JsonApiExample/app/src/main/res/drawable-xxhdpi/ic_add.png new file mode 100644 index 0000000..75f192a Binary files /dev/null and b/JsonApiExample/app/src/main/res/drawable-xxhdpi/ic_add.png differ 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 0000000..79c2539 Binary files /dev/null and b/JsonApiExample/app/src/main/res/drawable-xxhdpi/ic_launcher.png differ 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 0000000..28740e1 Binary files /dev/null and b/JsonApiExample/app/src/main/res/drawable-xxxhdpi/ic_launcher.png differ diff --git a/JsonApiExample/app/src/main/res/layout/activity_main.xml b/JsonApiExample/app/src/main/res/layout/activity_main.xml new file mode 100755 index 0000000..788acd1 --- /dev/null +++ b/JsonApiExample/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,25 @@ + + + + + + + + \ 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 @@ + + + + + + + + + + + + + +