diff --git a/.idea/dictionaries/dan.xml b/.idea/dictionaries/dan.xml
new file mode 100644
index 00000000..125b1b49
--- /dev/null
+++ b/.idea/dictionaries/dan.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/.idea/encodings.xml b/.idea/encodings.xml
new file mode 100644
index 00000000..15a15b21
--- /dev/null
+++ b/.idea/encodings.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
new file mode 100644
index 00000000..7ac24c77
--- /dev/null
+++ b/.idea/gradle.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 00000000..c7c27cbb
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml
new file mode 100644
index 00000000..7f68460d
--- /dev/null
+++ b/.idea/runConfigurations.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 00000000..35eb1ddf
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index 29c5e4c6..288ac30d 100644
--- a/README.md
+++ b/README.md
@@ -1,24 +1,35 @@
-# Mobile Developer Coding Challenge
-
-This is a coding challenge for prospective mobile developer applicants applying through http://work.traderev.com/
-
-## Goal:
-
-#### Build simple app that allows viewing and interacting with a grid of curated photos from Unsplash
-
-- [ ] Fork this repo. Keep it public until we have been able to review it.
-- [ ] Android: _Java_ or _Kotlin_ | iOS: _Swift 4_
-- [ ] Unsplash API docs are here: https://unsplash.com/documentation.
-- [ ] Grid of photos should preserve the aspect ratio of the photos it's displaying, meaning you shouldn't crop the image in any way.
-- [ ] App should work in both portrait and landscape orientations of the device.
-- [ ] Grid should support pagination, i.e. you can scroll on grid of photos infinitely.
-- [ ] When user taps on a photo on the grid it should show only the tapped photo in full screen with more information about the photo.
-- [ ] When user swipes on a photo in full screen, it should show the the next photo and preserve current photo's location on the grid, so when she dismisses the full screen, grid of photos should contain the last photo she saw in photo details.
-
-### Evaluation:
-- [ ] Solution compiles. If there are necessary steps required to get it to compile, those should be covered in README.md.
-- [ ] No crashes, bugs, compiler warnings
-- [ ] App operates as intended
-- [ ] Conforms to SOLID principles
-- [ ] Code is easily understood and communicative
-- [ ] Commit history is consistent, easy to follow and understand
+# Development environment
+
+* Android Studio version - 3.4
+
+* Minimum SDK - 21, Target SDK - 28
+
+* androidx
+
+# Major dependencies being used
+
+* ViewModel & LiveData API, MVVM Architecture
+
+* Retrofit
+
+* RXJava/Android
+
+* Glide
+
+* GSON converter for retrofit
+
+* Toasty
+
+* DataBind
+
+* espresso
+
+# Testing tool
+* espresso framework
+* Testing file for running in espresso: src/androidTest/java/com.dan.traderevmobilechallenge/ExampleInstrumentedTest.java
+* How to use:
+1. For performance, if you test the app with an emulator turn off all drawing options in setting on your emulator.
+ Go to setting-> Developer options-> find "Window animation scale", "Transition animation scale" and "Animator duration scale" then they should be turned **off** for running the app in espresso more smoothly.
+
+2. Open file "ExampleInstrumentedTest.java" and you can run it for testing the app. I created the testing methods in the class with the comments.
+
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 00000000..c2636165
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1,14 @@
+/build
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
diff --git a/app/app.iml b/app/app.iml
new file mode 100644
index 00000000..7ad75ecf
--- /dev/null
+++ b/app/app.iml
@@ -0,0 +1,205 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ generateDebugSources
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 00000000..2a5bc285
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,90 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 28
+ defaultConfig {
+ applicationId "com.dan.traderevmobilechallenge"
+ minSdkVersion 21
+ targetSdkVersion 28
+ versionCode 1
+ versionName "1.0"
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+ buildTypes {
+
+ debug {
+ buildConfigField("String", "clientId", clientId)
+ }
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+
+ dataBinding {
+ enabled = true
+ }
+ compileOptions {
+ sourceCompatibility = '1.8'
+ targetCompatibility = '1.8'
+ }
+
+ repositories {
+
+ mavenCentral()
+ maven {
+ url 'https://jitpack.io'
+ }
+ google()
+ jcenter()
+ }
+
+}
+
+dependencies {
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
+ implementation 'androidx.appcompat:appcompat:1.0.2'
+ implementation 'androidx.cardview:cardview:1.0.0'
+ implementation 'androidx.recyclerview:recyclerview:1.0.0'
+ implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
+ implementation 'com.google.android.material:material:1.0.0-rc01'
+ implementation 'androidx.legacy:legacy-support-v4:1.0.0'
+ implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'
+ testImplementation 'junit:junit:4.12'
+ androidTestImplementation 'androidx.test:runner:1.1.1'
+ androidTestImplementation 'androidx.test:rules:1.2.0-alpha04'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
+
+ androidTestImplementation ('com.android.support.test.espresso:espresso-contrib:2.2.1') {
+ exclude group: 'com.android.support', module: 'appcompat'
+ exclude group: 'com.android.support', module: 'support-v4'
+ exclude module: 'recyclerview-v7'
+ exclude module: 'support-annotations'
+ }
+
+ //RX Java
+ implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'
+ implementation 'io.reactivex.rxjava2:rxjava:2.2.6'
+ implementation 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0'
+ // Retrofit
+ implementation 'com.squareup.retrofit2:retrofit:2.1.0'
+ implementation 'com.google.code.gson:gson:2.6.2'
+ implementation 'com.squareup.retrofit2:converter-gson:2.1.0'
+
+ def lifecycle_version = "2.0.0"
+ // ViewModel and LiveData
+ implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
+ // Picasso
+ implementation 'com.squareup.picasso:picasso:2.71828'
+ implementation 'androidx.viewpager:viewpager:1.0.0'
+ //animation
+ implementation 'com.daimajia.easing:library:2.0@aar'
+ implementation 'com.daimajia.androidanimations:library:2.3@aar'
+ //Glide
+ implementation 'com.github.bumptech.glide:glide:4.9.0'
+ annotationProcessor 'com.github.bumptech.glide:compiler:4.9.0'
+ //Toasty
+ implementation 'com.github.GrenderG:Toasty:1.4.2'
+
+
+}
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 00000000..f1b42451
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/app/src/androidTest/java/com/dan/traderevmobilechallenge/ExampleInstrumentedTest.java b/app/src/androidTest/java/com/dan/traderevmobilechallenge/ExampleInstrumentedTest.java
new file mode 100644
index 00000000..0540d19f
--- /dev/null
+++ b/app/src/androidTest/java/com/dan/traderevmobilechallenge/ExampleInstrumentedTest.java
@@ -0,0 +1,218 @@
+package com.dan.traderevmobilechallenge;
+
+import android.content.Context;
+import android.view.View;
+import android.widget.FrameLayout;
+
+import androidx.fragment.app.Fragment;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.espresso.Espresso;
+import androidx.test.espresso.action.ViewActions;
+import androidx.test.espresso.assertion.ViewAssertions;
+import androidx.test.espresso.contrib.RecyclerViewActions;
+import androidx.test.espresso.matcher.ViewMatchers;
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.dan.traderevmobilechallenge.view.MainActivity;
+import com.dan.traderevmobilechallenge.view.fragments.GridViewFragment;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Random;
+
+import static androidx.test.espresso.Espresso.onView;
+import static androidx.test.espresso.action.ViewActions.scrollTo;
+import static androidx.test.espresso.action.ViewActions.swipeLeft;
+import static androidx.test.espresso.action.ViewActions.swipeRight;
+import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static androidx.test.espresso.matcher.ViewMatchers.withId;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * To run test, you can click errow indicator aligned on each test method for testing separately
+ * or click the indicator aligned on class name for all in one test
+ *
+ * OR click on Run/debug button on main menu with selecting testing app
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+
+ @Before
+ public void setUp() {
+
+ mainActivity = activityActivityTestRule.getActivity();
+ }
+
+ @Rule
+ public ActivityTestRule activityActivityTestRule = new ActivityTestRule<>(MainActivity.class);
+ private MainActivity mainActivity;
+ private Random random = new Random();
+
+ @Test
+ public void useAppContext() {
+ // Context of the app under test.
+ Context appContext = InstrumentationRegistry.getTargetContext();
+
+ assertEquals("com.dan.traderevmobilechallenge", appContext.getPackageName());
+ }
+
+ /**
+ * Test for launch
+ */
+ @Test
+ public void launchTest() {
+
+ // check if container for loading Grid View fragment is null or not
+ FrameLayout frameLayout = mainActivity.findViewById(R.id.fragment_container);
+ assertNotNull(frameLayout);
+
+ // add GridView fragment
+ GridViewFragment gridViewFragment = new GridViewFragment();
+
+ mainActivity.getSupportFragmentManager()
+ .beginTransaction()
+ .add(frameLayout.getId(), gridViewFragment)
+ .commitAllowingStateLoss();
+ // give a little of time for loading fragment
+ getInstrumentation().waitForIdleSync();
+ // check if recyclerView in the fragment is null or not
+ View view = gridViewFragment.getView().findViewById(R.id.recycler_view);
+ assertNotNull(view);
+ }
+
+ /**
+ * Test for scrolling to end of page and back to start
+ */
+ @Test
+ public void scrollRecyclerView() {
+
+ performIdle(5000);
+ // Check if recycler view is displayed
+ Espresso.onView(withId(R.id.recycler_view)).check(ViewAssertions.matches(isDisplayed()));
+
+ // To get Adaptor to get number of items presented in recycler view
+ Fragment fragment = mainActivity.getSupportFragmentManager().findFragmentByTag(GridViewFragment.class.getSimpleName());
+ GridViewFragment gridViewFragment = null;
+ if (fragment instanceof GridViewFragment) {
+
+ gridViewFragment = (GridViewFragment) fragment;
+ }
+ View view = gridViewFragment.getView();
+ RecyclerView recyclerView = view.findViewById(R.id.recycler_view);
+ // scroll to end
+ Espresso.onView(withId(R.id.recycler_view)).perform(RecyclerViewActions
+ .scrollToPosition(recyclerView.getAdapter().getItemCount()-1));
+ performIdle(500);
+ //scroll to start
+ Espresso.onView(withId(R.id.recycler_view)).perform(RecyclerViewActions.scrollToPosition(0));
+ performIdle(1000);
+
+ }
+
+ /**
+ * Test for scrolling to a given random position among items loaded and click it to full screen
+ */
+ @Test
+ public void scrollToPositionInRecyclerViewAndClickTheItem() {
+
+ performIdle(5000);
+ // Check if recycler view is displayed
+ Espresso.onView(withId(R.id.recycler_view)).check(ViewAssertions.matches(isDisplayed()));
+
+ // To get Adaptor to get number of items presented in recycler view
+ Fragment fragment = mainActivity.getSupportFragmentManager().findFragmentByTag(GridViewFragment.class.getSimpleName());
+ GridViewFragment gridViewFragment = null;
+ if (fragment instanceof GridViewFragment) {
+
+ gridViewFragment = (GridViewFragment) fragment;
+ }
+ View view = gridViewFragment.getView();
+ RecyclerView recyclerView = view.findViewById(R.id.recycler_view);
+ // Get random position
+ int numOfItems = recyclerView.getAdapter().getItemCount();
+ int randomPosition = (int) (Math.random() * ( numOfItems - 1 ));
+ // check if recycler view is displayed
+ Espresso.onView(ViewMatchers.withId(R.id.recycler_view)).check(ViewAssertions.matches(isDisplayed()));
+ // click the item at the given position and click and see if the full screen presents
+ Espresso.onView(ViewMatchers.withId(R.id.recycler_view))
+ .perform(RecyclerViewActions.actionOnItemAtPosition(randomPosition-1, ViewActions.click()));
+ performIdle(3000);
+ }
+
+ /**
+ * Test for swapping photos in full screen and clicking button to
+ * check if photo info is hide /show on toggle action
+ */
+ @Test
+ public void swappingPhotosInFullScreen() {
+
+ // go 100th position
+ int targetToGo = 100;
+ performIdle(5000);
+ onView(ViewMatchers.withId(R.id.recycler_view))
+ .perform(RecyclerViewActions.actionOnItemAtPosition(targetToGo, scrollTo()));
+
+ //open the item at 100th position
+ onView(ViewMatchers.withId(R.id.recycler_view)).perform(RecyclerViewActions.actionOnItemAtPosition(targetToGo, ViewActions.click()));
+ // check viewPager is displayed
+ onView(ViewMatchers.withId(R.id.view_pager)).check(ViewAssertions.matches(isDisplayed()));
+
+ //swap to right
+ int untilWhichPosition = 130;
+ for (int i = targetToGo; i < untilWhichPosition; i++) {
+
+ onView(withId(R.id.view_pager)).perform(swipeRight());
+ // check if Floating button and TextField are displayed
+ onView(withId(R.id.fb_btn)).check(ViewAssertions.matches(isDisplayed()));
+ onView(withId(R.id.tv_photo_info)).check(ViewAssertions.matches(isDisplayed()));
+ // click floating button
+ onView(withId(R.id.fb_btn)).perform(ViewActions.click());
+ performIdle(50);
+ // click floating button
+ onView(withId(R.id.fb_btn)).perform(ViewActions.click());
+ // turn it on again. TextView should be displayed
+ onView(withId(R.id.tv_photo_info)).check(ViewAssertions.matches(isDisplayed()));
+ performIdle(400);
+ }
+ //swap to left
+ for (int i = untilWhichPosition - 1; i < 145; i++) {
+
+ onView(withId(R.id.view_pager)).perform(swipeLeft());
+ onView(withId(R.id.fb_btn)).check(ViewAssertions.matches(isDisplayed()));
+ onView(withId(R.id.tv_photo_info)).check(ViewAssertions.matches(isDisplayed()));
+
+ onView(withId(R.id.fb_btn)).perform(ViewActions.click());
+ performIdle(50);
+
+ onView(withId(R.id.fb_btn)).perform(ViewActions.click());
+ // turn it on again
+ onView(withId(R.id.tv_photo_info)).check(ViewAssertions.matches(isDisplayed()));
+
+ performIdle(400);
+ }
+ }
+
+ private void performIdle(long time) {
+
+ try {
+ Thread.sleep(time);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+
+ @After
+ public void tearDown() {
+ mainActivity = null;
+ }
+}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..2f333db4
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/dan/traderevmobilechallenge/adapters/GridAdapter.java b/app/src/main/java/com/dan/traderevmobilechallenge/adapters/GridAdapter.java
new file mode 100644
index 00000000..135386da
--- /dev/null
+++ b/app/src/main/java/com/dan/traderevmobilechallenge/adapters/GridAdapter.java
@@ -0,0 +1,208 @@
+package com.dan.traderevmobilechallenge.adapters;
+
+import android.graphics.drawable.Drawable;
+import android.transition.TransitionSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+import androidx.recyclerview.widget.DiffUtil;
+import androidx.recyclerview.widget.ListAdapter;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.RequestManager;
+import com.bumptech.glide.load.DataSource;
+import com.bumptech.glide.load.engine.GlideException;
+import com.bumptech.glide.request.RequestListener;
+import com.bumptech.glide.request.target.Target;
+import com.dan.traderevmobilechallenge.R;
+import com.dan.traderevmobilechallenge.application.CustomApp;
+import com.dan.traderevmobilechallenge.databinding.ImageCardBinding;
+import com.dan.traderevmobilechallenge.model.Photo;
+import com.dan.traderevmobilechallenge.view.MainActivity;
+import com.dan.traderevmobilechallenge.view.fragments.FullImagePagerFragment;
+
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * This is adapter associated with RecyclerView
+ * It shows photo data in Grid style
+ *
+ * Created by Dan Kim on 2019-04-30
+ */
+public class GridAdapter extends ListAdapter {
+
+ private final RequestManager requestManager;
+ private final ViewHolderListener viewHolderListener;
+ private LayoutInflater layoutInflater;
+
+ /**
+ * A listener that is attached to all ViewHolders to handle photo loading events and clicks.
+ */
+ private interface ViewHolderListener {
+
+ void onLoadCompleted(int adapterPosition);
+ void onItemClicked(View view, int adapterPosition);
+ }
+
+ private static final DiffUtil.ItemCallback DIFF_CALLBACK = new DiffUtil.ItemCallback() {
+ @Override
+ public boolean areItemsTheSame(@NonNull Photo oldItem, @NonNull Photo newItem) {
+ return oldItem.id.equals(newItem.id);
+ }
+
+ @Override
+ public boolean areContentsTheSame(@NonNull Photo oldItem, @NonNull Photo newItem) {
+ return oldItem.id.equals(newItem.id) && oldItem.user.name.equals(newItem.user.name)
+ && oldItem.createdAt.equals(newItem.createdAt);
+ }
+ };
+
+ /**
+ * Constructs a new grid adapter for the given {@link com.dan.traderevmobilechallenge.view.fragments.GridViewFragment}.
+ */
+ public GridAdapter(Fragment fragment) {
+ super(DIFF_CALLBACK);
+ this.requestManager = Glide.with(fragment);
+ this.viewHolderListener = new ViewHolderListenerImpl(fragment);
+ }
+
+ @NonNull
+ @Override
+ public ImageViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+
+ if (layoutInflater == null) {
+ layoutInflater = LayoutInflater.from(parent.getContext());
+ }
+ // Inflate views being used by DataBinding object
+ ImageCardBinding imageCardBinding = ImageCardBinding.inflate(layoutInflater, parent, false);
+
+ return new ImageViewHolder(imageCardBinding, requestManager, viewHolderListener);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull ImageViewHolder holder, int position) {
+ holder.onBind(getItem(position));
+ }
+
+ /**
+ * {@link ViewHolderListener} implementation
+ */
+ private static class ViewHolderListenerImpl implements ViewHolderListener {
+
+ private final Fragment fragment;
+ private final AtomicBoolean enterTransitionStarted;
+
+ /**
+ * Construct with passing a given Fragment
+ * @param fragment {@link com.dan.traderevmobilechallenge.view.fragments.GridViewFragment}
+ */
+ ViewHolderListenerImpl(Fragment fragment) {
+ this.fragment = fragment;
+ this.enterTransitionStarted = new AtomicBoolean();
+ }
+
+ @Override
+ public void onLoadCompleted(int position) {
+ // Call startPostponedEnterTransition only when the 'selected' image loading is completed.
+ if (MainActivity.currentPosition != position) {
+ return;
+ }
+ if (enterTransitionStarted.getAndSet(true)) {
+ return;
+ }
+ fragment.startPostponedEnterTransition();
+ }
+
+
+ @Override
+ public void onItemClicked(View view, int position) {
+ // Update the position.
+ MainActivity.currentPosition = position;
+
+ // Exclude the clicked card from the exit transition (e.g. the card will disappear immediately
+ // instead of fading out with the rest to prevent an overlapping animation of fade and move).
+ ((TransitionSet) Objects.requireNonNull(fragment.getExitTransition())).excludeTarget(view, true);
+
+ // Shared element transition starts
+ ImageView transitioningView = view.findViewById(R.id.iv_photo);
+ Objects.requireNonNull(fragment.getFragmentManager())
+ .beginTransaction()
+ .setReorderingAllowed(true) // Optimize for shared element transition
+ .addSharedElement(transitioningView, transitioningView.getTransitionName())
+ .replace(R.id.fragment_container, new FullImagePagerFragment(), FullImagePagerFragment.class
+ .getSimpleName())
+ .addToBackStack(null)
+ .commit();
+ }
+ }
+
+ /**
+ * ViewHolder for the grid's images.
+ */
+ static class ImageViewHolder extends RecyclerView.ViewHolder implements
+ View.OnClickListener {
+
+ private final RequestManager requestManager;
+ private final ViewHolderListener viewHolderListener;
+ private final ImageCardBinding imageCardBinding;
+
+ ImageViewHolder(ImageCardBinding imageCardBinding, RequestManager requestManager,
+ ViewHolderListener viewHolderListener) {
+ super(imageCardBinding.getRoot());
+
+ this.requestManager = requestManager;
+ this.viewHolderListener = viewHolderListener;
+ this.imageCardBinding = imageCardBinding;
+ imageCardBinding.cardView.setOnClickListener(this);
+ }
+
+
+ /**
+ * Binds this view holder to the given adapter position.
+ *
+ * The binding will load the photoImage into the photoImage view, as well as set its transition name for
+ * later.
+ */
+ void onBind(Photo photo) {
+
+ setImage(photo);
+ //data bind to xml
+ imageCardBinding.setPhoto(photo);
+ imageCardBinding.ivPhoto.setTransitionName(CustomApp.getContext().getString(R.string.photo) + photo.id);
+ }
+
+ void setImage(Photo photo) {
+ // Load the photoImage with Glide
+ requestManager
+ .load(photo.urls.small)
+ .listener(new RequestListener() {
+ @Override
+ public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, boolean isFirstResource) {
+ viewHolderListener.onLoadCompleted(getAdapterPosition());
+ return false;
+ }
+
+ @Override
+ public boolean onResourceReady(Drawable resource, Object model, Target target, DataSource dataSource, boolean isFirstResource) {
+ viewHolderListener.onLoadCompleted(getAdapterPosition());
+ return false;
+ }
+ })
+ .into(imageCardBinding.ivPhoto);
+
+ }
+
+ @Override
+ public void onClick(View view) {
+ // Let the listener start the FullImagePagerFragment.
+ viewHolderListener.onItemClicked(view, getAdapterPosition());
+ }
+ }
+}
diff --git a/app/src/main/java/com/dan/traderevmobilechallenge/adapters/ImagePagerAdapter.java b/app/src/main/java/com/dan/traderevmobilechallenge/adapters/ImagePagerAdapter.java
new file mode 100644
index 00000000..38d77455
--- /dev/null
+++ b/app/src/main/java/com/dan/traderevmobilechallenge/adapters/ImagePagerAdapter.java
@@ -0,0 +1,34 @@
+package com.dan.traderevmobilechallenge.adapters;
+
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentStatePagerAdapter;
+
+import com.dan.traderevmobilechallenge.model.Photo;
+import com.dan.traderevmobilechallenge.view.fragments.ImageFragment;
+
+import java.util.ArrayList;
+
+/**
+ *
+ * Adapter to handle swapping photos in full screen
+ * {@link com.dan.traderevmobilechallenge.view.fragments.FullImagePagerFragment}
+ * Created by Dan Kim on 2019-04-30
+ */
+public class ImagePagerAdapter extends FragmentStatePagerAdapter {
+ private final ArrayList photos;
+
+ public ImagePagerAdapter(Fragment fragment, ArrayList photos) {
+ super(fragment.getChildFragmentManager());
+ this.photos = photos;
+ }
+
+ @Override
+ public Fragment getItem(int position) {
+ return ImageFragment.newInstance(photos.get(position));
+ }
+
+ @Override
+ public int getCount() {
+ return photos.size();
+ }
+}
diff --git a/app/src/main/java/com/dan/traderevmobilechallenge/application/CustomApp.java b/app/src/main/java/com/dan/traderevmobilechallenge/application/CustomApp.java
new file mode 100644
index 00000000..3262ade9
--- /dev/null
+++ b/app/src/main/java/com/dan/traderevmobilechallenge/application/CustomApp.java
@@ -0,0 +1,34 @@
+package com.dan.traderevmobilechallenge.application;
+
+import android.app.Application;
+import android.content.Context;
+import android.content.IntentFilter;
+
+import com.dan.traderevmobilechallenge.components.NetworkStateChangeReceiver;
+
+import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
+
+/**
+ * Created by Dan Kim on 2019-04-28
+ */
+public class CustomApp extends Application {
+
+ private static Context applicationContext;
+ private static final String WIFI_STATE_CHANGE_ACTION = "android.net.wifi.WIFI_STATE_CHANGED";
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ applicationContext = getApplicationContext();
+ registerForNetworkChangeEvents(this);
+ }
+
+ public static Context getContext(){
+ return applicationContext;
+ }
+ private static void registerForNetworkChangeEvents(final Context context) {
+ NetworkStateChangeReceiver networkStateChangeReceiver = new NetworkStateChangeReceiver();
+ context.registerReceiver(networkStateChangeReceiver, new IntentFilter(CONNECTIVITY_ACTION));
+ context.registerReceiver(networkStateChangeReceiver, new IntentFilter(WIFI_STATE_CHANGE_ACTION));
+ }
+}
diff --git a/app/src/main/java/com/dan/traderevmobilechallenge/components/NetworkStateChangeReceiver.java b/app/src/main/java/com/dan/traderevmobilechallenge/components/NetworkStateChangeReceiver.java
new file mode 100644
index 00000000..ab19c33e
--- /dev/null
+++ b/app/src/main/java/com/dan/traderevmobilechallenge/components/NetworkStateChangeReceiver.java
@@ -0,0 +1,50 @@
+package com.dan.traderevmobilechallenge.components;
+
+import android.annotation.SuppressLint;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+/**
+ * This is designed to broadcast a change of network status to MainActivity's ViewModel
+ * {@link com.dan.traderevmobilechallenge.viewmodel.MainActivityViewModel}
+ * so MainActivity will show Toasty message accordingly
+ *
+ * Created by Dan Kim on 2019-05-01
+ */
+public class NetworkStateChangeReceiver extends BroadcastReceiver {
+
+ public static final String NETWORK_AVAILABLE_ACTION = "com.dan.traderevmobilechallenge.components.NETWORK_AVAILABLE_ACTION";
+ public static final String IS_NETWORK_AVAILABLE = "isNetworkAvailable";
+
+ @SuppressLint("UnsafeProtectedBroadcastReceiver")
+ @Override
+ public void onReceive(Context context, Intent intent) {
+
+ Intent networkStateIntent = new Intent(NETWORK_AVAILABLE_ACTION);
+ networkStateIntent.putExtra(IS_NETWORK_AVAILABLE, isConnect(context));
+ LocalBroadcastManager.getInstance(context).sendBroadcast(networkStateIntent);
+ }
+
+ private boolean isConnect(Context context) {
+
+ try {
+ if (context != null) {
+
+ ConnectivityManager connectivityManager = (ConnectivityManager) context
+ .getSystemService(Context.CONNECTIVITY_SERVICE);
+
+ NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
+ return networkInfo != null && networkInfo.isConnected();
+ }
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return false;
+ }
+}
diff --git a/app/src/main/java/com/dan/traderevmobilechallenge/model/Photo.java b/app/src/main/java/com/dan/traderevmobilechallenge/model/Photo.java
new file mode 100644
index 00000000..fc44085e
--- /dev/null
+++ b/app/src/main/java/com/dan/traderevmobilechallenge/model/Photo.java
@@ -0,0 +1,81 @@
+package com.dan.traderevmobilechallenge.model;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.dan.traderevmobilechallenge.model.user.User;
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * This Data map class for photo info received from server
+ *
+ * Created by Dan Kim on 2019-04-26
+ */
+@SuppressWarnings({"WeakerAccess", "CanBeFinal"})
+public class Photo implements Parcelable {
+
+ @SerializedName("id")
+ public String id;
+ @SerializedName("created_at")
+ public String createdAt;
+ @SerializedName("updated_at")
+ public String updatedAt;
+ @SerializedName("width")
+ public int width;
+ @SerializedName("height")
+ public int height;
+ @SerializedName("color")
+ public String color;
+ @SerializedName("description")
+ public String description;
+ @SerializedName("alt_description")
+ public String alt_description;
+ @SerializedName("urls")
+ public Urls urls;
+ @SerializedName("user")
+ public User user;
+
+ protected Photo(Parcel in) {
+ id = in.readString();
+ createdAt = in.readString();
+ updatedAt = in.readString();
+ width = in.readInt();
+ height = in.readInt();
+ color = in.readString();
+ description = in.readString();
+ alt_description = in.readString();
+ urls = in.readParcelable(Urls.class.getClassLoader());
+ user = in.readParcelable(User.class.getClassLoader());
+ }
+
+ public static final Creator CREATOR = new Creator() {
+ @Override
+ public Photo createFromParcel(Parcel in) {
+ return new Photo(in);
+ }
+
+ @Override
+ public Photo[] newArray(int size) {
+ return new Photo[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(id);
+ dest.writeString(createdAt);
+ dest.writeString(updatedAt);
+ dest.writeInt(width);
+ dest.writeInt(height);
+ dest.writeString(color);
+ dest.writeString(description);
+ dest.writeString(alt_description);
+ dest.writeParcelable(urls, flags);
+ dest.writeParcelable(user, flags);
+ }
+}
diff --git a/app/src/main/java/com/dan/traderevmobilechallenge/model/Urls.java b/app/src/main/java/com/dan/traderevmobilechallenge/model/Urls.java
new file mode 100644
index 00000000..bf64d6c7
--- /dev/null
+++ b/app/src/main/java/com/dan/traderevmobilechallenge/model/Urls.java
@@ -0,0 +1,60 @@
+package com.dan.traderevmobilechallenge.model;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * Data Map for JSON object for Urls
+ *
+ * Created by Dan Kim on 2019-04-26
+ */
+@SuppressWarnings({"WeakerAccess", "CanBeFinal"})
+public class Urls implements Parcelable {
+
+ @SerializedName("raw")
+ public String raw;
+ @SerializedName("full")
+ public String full;
+ @SerializedName("regular")
+ public String regular;
+ @SerializedName("small")
+ public String small;
+ @SerializedName("thumb")
+ public String thumb;
+
+ protected Urls(Parcel in) {
+ raw = in.readString();
+ full = in.readString();
+ regular = in.readString();
+ small = in.readString();
+ thumb = in.readString();
+ }
+
+ public static final Creator CREATOR = new Creator() {
+ @Override
+ public Urls createFromParcel(Parcel in) {
+ return new Urls(in);
+ }
+
+ @Override
+ public Urls[] newArray(int size) {
+ return new Urls[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(raw);
+ dest.writeString(full);
+ dest.writeString(regular);
+ dest.writeString(small);
+ dest.writeString(thumb);
+ }
+}
diff --git a/app/src/main/java/com/dan/traderevmobilechallenge/model/datasource/UnSplashApiDataSource.java b/app/src/main/java/com/dan/traderevmobilechallenge/model/datasource/UnSplashApiDataSource.java
new file mode 100644
index 00000000..1fae4a5e
--- /dev/null
+++ b/app/src/main/java/com/dan/traderevmobilechallenge/model/datasource/UnSplashApiDataSource.java
@@ -0,0 +1,35 @@
+package com.dan.traderevmobilechallenge.model.datasource;
+
+import com.dan.traderevmobilechallenge.model.Photo;
+import com.dan.traderevmobilechallenge.remote.RetrofitClient;
+
+import java.util.ArrayList;
+
+import io.reactivex.Observable;
+
+/**
+ * Data source to handle for calling to get data from Unsplash api server
+ *
+ * Created by Dan Kim on 2019-04-26
+ */
+@SuppressWarnings("SpellCheckingInspection")
+public class UnSplashApiDataSource {
+ private static final UnSplashApiDataSource ourInstance = new UnSplashApiDataSource();
+
+ public static UnSplashApiDataSource getInstance() {
+ return ourInstance;
+ }
+
+ private UnSplashApiDataSource() { }
+
+ /**
+ * Method to get photos from Unsplash api server
+ *
+ * @param clientId client ID as parameter required by unsplash APi call
+ * @return Observable object that holds list of photos. It is observed by
+ * {@link com.dan.traderevmobilechallenge.viewmodel.MainActivityViewModel}
+ */
+ public Observable> getAllPhotos(final String clientId, int pageNo, int maxNumDataOnPage){
+ return RetrofitClient.getInstance().getServiceApi().getAllPhotos(clientId,pageNo,maxNumDataOnPage);
+ }
+}
diff --git a/app/src/main/java/com/dan/traderevmobilechallenge/model/user/ProfileImage.java b/app/src/main/java/com/dan/traderevmobilechallenge/model/user/ProfileImage.java
new file mode 100644
index 00000000..05e97d7d
--- /dev/null
+++ b/app/src/main/java/com/dan/traderevmobilechallenge/model/user/ProfileImage.java
@@ -0,0 +1,52 @@
+package com.dan.traderevmobilechallenge.model.user;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * Data Map for JSON object for Profile
+ *
+ * Created by Dan Kim on 2019-04-26
+ */
+@SuppressWarnings({"WeakerAccess", "CanBeFinal"})
+public class ProfileImage implements Parcelable {
+
+ @SerializedName("small")
+ public String small;
+ @SerializedName("medium")
+ public String medium;
+ @SerializedName("large")
+ public String large;
+
+ protected ProfileImage(Parcel in) {
+ small = in.readString();
+ medium = in.readString();
+ large = in.readString();
+ }
+
+ public static final Creator CREATOR = new Creator() {
+ @Override
+ public ProfileImage createFromParcel(Parcel in) {
+ return new ProfileImage(in);
+ }
+
+ @Override
+ public ProfileImage[] newArray(int size) {
+ return new ProfileImage[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(small);
+ dest.writeString(medium);
+ dest.writeString(large);
+ }
+}
diff --git a/app/src/main/java/com/dan/traderevmobilechallenge/model/user/User.java b/app/src/main/java/com/dan/traderevmobilechallenge/model/user/User.java
new file mode 100644
index 00000000..f01f0671
--- /dev/null
+++ b/app/src/main/java/com/dan/traderevmobilechallenge/model/user/User.java
@@ -0,0 +1,89 @@
+package com.dan.traderevmobilechallenge.model.user;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * Data Map for JSON object for User
+ *
+ * Created by Dan Kim on 2019-04-26
+ */
+@SuppressWarnings("ALL")
+public class User implements Parcelable {
+
+ @SerializedName("id")
+ public String id;
+ @SerializedName("updated_at")
+ public String updatedAt;
+ @SerializedName("username")
+ public String userName;
+ @SerializedName("name")
+ public String name;
+ @SerializedName("first_name")
+ public String firstName;
+ @SerializedName("last_name")
+ public String lastName;
+ @SerializedName("location")
+ public String location;
+ @SerializedName("instagram_username")
+ public String instagramUserName;
+ @SerializedName("totalCollections")
+ int totalCollections;
+ @SerializedName("total_likes")
+ int totalLikes;
+ @SerializedName("total_photos")
+ int totalPhotos;
+ @SerializedName("accepted_tos")
+ boolean acceptTos;
+
+
+ protected User(Parcel in) {
+ id = in.readString();
+ updatedAt = in.readString();
+ userName = in.readString();
+ name = in.readString();
+ firstName = in.readString();
+ lastName = in.readString();
+ location = in.readString();
+ instagramUserName = in.readString();
+ totalCollections = in.readInt();
+ totalLikes = in.readInt();
+ totalPhotos = in.readInt();
+ acceptTos = in.readByte() != 0;
+ }
+
+ public static final Creator CREATOR = new Creator() {
+ @Override
+ public User createFromParcel(Parcel in) {
+ return new User(in);
+ }
+
+ @Override
+ public User[] newArray(int size) {
+ return new User[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(id);
+ dest.writeString(updatedAt);
+ dest.writeString(userName);
+ dest.writeString(name);
+ dest.writeString(firstName);
+ dest.writeString(lastName);
+ dest.writeString(location);
+ dest.writeString(instagramUserName);
+ dest.writeInt(totalCollections);
+ dest.writeInt(totalLikes);
+ dest.writeInt(totalPhotos);
+ dest.writeByte((byte) (acceptTos ? 1 : 0));
+ }
+}
diff --git a/app/src/main/java/com/dan/traderevmobilechallenge/remote/RetrofitClient.java b/app/src/main/java/com/dan/traderevmobilechallenge/remote/RetrofitClient.java
new file mode 100644
index 00000000..e1b9edd5
--- /dev/null
+++ b/app/src/main/java/com/dan/traderevmobilechallenge/remote/RetrofitClient.java
@@ -0,0 +1,41 @@
+package com.dan.traderevmobilechallenge.remote;
+
+import com.jakewharton.retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
+
+import retrofit2.Retrofit;
+import retrofit2.converter.gson.GsonConverterFactory;
+
+/**
+ * Http client to communicate with unsplash api server
+ *
+ * Created by Dan Kim on 2019-04-26
+ */
+public class RetrofitClient {
+
+ private static final String BASE_URL = "https://api.unsplash.com/";
+ private static RetrofitClient instance;
+ private final Retrofit retrofit;
+
+
+ private RetrofitClient() {
+
+ retrofit = new Retrofit.Builder()
+ .baseUrl(BASE_URL)
+ .addConverterFactory(GsonConverterFactory.create())
+ .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
+ .build();
+ }
+
+ public static RetrofitClient getInstance() {
+
+ if (instance == null) {
+ instance = new RetrofitClient();
+ }
+ return instance;
+ }
+
+ public ServiceApi getServiceApi() {
+ return retrofit.create(ServiceApi.class);
+ }
+
+}
diff --git a/app/src/main/java/com/dan/traderevmobilechallenge/remote/ServiceApi.java b/app/src/main/java/com/dan/traderevmobilechallenge/remote/ServiceApi.java
new file mode 100644
index 00000000..9665c06b
--- /dev/null
+++ b/app/src/main/java/com/dan/traderevmobilechallenge/remote/ServiceApi.java
@@ -0,0 +1,24 @@
+package com.dan.traderevmobilechallenge.remote;
+
+import com.dan.traderevmobilechallenge.model.Photo;
+
+import java.util.ArrayList;
+
+import io.reactivex.Observable;
+import retrofit2.http.GET;
+import retrofit2.http.Query;
+
+/**
+ * Client API to get data from unsplash api server
+ *
+ * Created by Dan Kim on 2019-04-26
+ */
+public interface ServiceApi {
+
+ @GET("photos")
+ Observable> getAllPhotos(
+ @Query("client_id") String clientId,
+ @Query("page") int page,
+ @Query("per_page") int perPage);
+
+}
diff --git a/app/src/main/java/com/dan/traderevmobilechallenge/repository/RemoteRepository.java b/app/src/main/java/com/dan/traderevmobilechallenge/repository/RemoteRepository.java
new file mode 100644
index 00000000..4b07caac
--- /dev/null
+++ b/app/src/main/java/com/dan/traderevmobilechallenge/repository/RemoteRepository.java
@@ -0,0 +1,30 @@
+package com.dan.traderevmobilechallenge.repository;
+
+import com.dan.traderevmobilechallenge.BuildConfig;
+import com.dan.traderevmobilechallenge.model.datasource.UnSplashApiDataSource;
+import com.dan.traderevmobilechallenge.model.Photo;
+
+import java.util.ArrayList;
+
+import io.reactivex.Observable;
+
+/**
+ * Repository layer class to manage data source
+ *
+ * Created by Dan Kim on 2019-04-26
+ */
+public class RemoteRepository {
+
+ public RemoteRepository() {
+ }
+
+ /**
+ * Method to get photos
+ * @return Observable that holds list of photos from unsplash API server
+ */
+ public Observable> getAllPhotosFromUnsplashApi(int pageNo, int maxNumDataOnPage) {
+ // Client ID is defined in BuildConfig for security purpose
+ // it's stored in the BuildConfig while compilation time
+ return UnSplashApiDataSource.getInstance().getAllPhotos(BuildConfig.clientId,pageNo,maxNumDataOnPage);
+ }
+}
diff --git a/app/src/main/java/com/dan/traderevmobilechallenge/utils/StringUtil.java b/app/src/main/java/com/dan/traderevmobilechallenge/utils/StringUtil.java
new file mode 100644
index 00000000..554dd9ec
--- /dev/null
+++ b/app/src/main/java/com/dan/traderevmobilechallenge/utils/StringUtil.java
@@ -0,0 +1,63 @@
+package com.dan.traderevmobilechallenge.utils;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+
+import com.dan.traderevmobilechallenge.R;
+import com.dan.traderevmobilechallenge.application.CustomApp;
+import com.dan.traderevmobilechallenge.model.Photo;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+
+/**
+ * This is util class to do string-related tasks
+ *
+ * Created by Dan Kim on 2019-04-28
+ */
+public class StringUtil {
+
+ @SuppressLint("SimpleDateFormat")
+ private static final SimpleDateFormat formatter1 = new SimpleDateFormat("yyyy-MM-dd");
+ @SuppressLint("SimpleDateFormat")
+ private static final SimpleDateFormat formatter2 = new SimpleDateFormat("E, MMM d");
+
+ /**
+ * Method to format photo info showing in full screen
+ * @param photo current photo data
+ * @return formatted photo info to be shown in fullscreen
+ */
+ public static String formatPhotoData(Photo photo) {
+
+ Context context = CustomApp.getContext();
+
+ String author = photo.user.name != null ? photo.user.name : context.getString(R.string.unknown);
+
+ String createdAt = photo.createdAt != null ? photo.createdAt : context.getString(R.string.unknown);
+ String formattedCreatedAt = " \u25BA";
+ if (!createdAt.equals(context.getString(R.string.unknown))) {
+ String[] temp = createdAt.split("T");
+ try {
+ formattedCreatedAt += formatter2.format(formatter1.parse(temp[0]));
+ } catch (ParseException e) {
+ // If formatting occurs , just return parameter
+ formattedCreatedAt += createdAt;
+ }
+ } else {
+ formattedCreatedAt = createdAt;
+ }
+ // parse description. possible to get null
+ String description = photo.description != null ? photo.description : context.getString(R.string.unknown);
+ // If description is null try getting alt-description. if it's also null, then showing "unknown"
+ if (description.equals(context.getString(R.string.unknown))){
+
+ if (photo.alt_description != null ){
+ description = photo.alt_description;
+ }
+ }
+
+ String location = photo.user.location != null ? photo.user.location : context.getString(R.string.unknown);
+
+ return String.format(context.getString(R.string.photo_info), author,formattedCreatedAt,description,location);
+ }
+}
diff --git a/app/src/main/java/com/dan/traderevmobilechallenge/view/LoadingDialog.java b/app/src/main/java/com/dan/traderevmobilechallenge/view/LoadingDialog.java
new file mode 100644
index 00000000..c2568d4c
--- /dev/null
+++ b/app/src/main/java/com/dan/traderevmobilechallenge/view/LoadingDialog.java
@@ -0,0 +1,47 @@
+package com.dan.traderevmobilechallenge.view;
+
+import android.annotation.SuppressLint;
+import android.app.Dialog;
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AlertDialog;
+import androidx.appcompat.app.AppCompatDialogFragment;
+
+import com.dan.traderevmobilechallenge.R;
+
+import java.util.Objects;
+
+/**
+ * Loading Dialog is used to be shown while fetching data
+ *
+ * Created by Dan Kim on 2019-05-01
+ */
+public class LoadingDialog extends AppCompatDialogFragment {
+
+ @NonNull
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(Objects.requireNonNull(getActivity()));
+ LayoutInflater inflater = getActivity().getLayoutInflater();
+ @SuppressLint("InflateParams") View view = inflater.inflate(R.layout.loading_dialog, null);
+ builder.setView(view);
+
+ return builder.create();
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+ Objects.requireNonNull(getDialog().getWindow()).setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
+
+ return super.onCreateView(inflater, container, savedInstanceState);
+ }
+}
diff --git a/app/src/main/java/com/dan/traderevmobilechallenge/view/MainActivity.java b/app/src/main/java/com/dan/traderevmobilechallenge/view/MainActivity.java
new file mode 100644
index 00000000..2e295c44
--- /dev/null
+++ b/app/src/main/java/com/dan/traderevmobilechallenge/view/MainActivity.java
@@ -0,0 +1,62 @@
+package com.dan.traderevmobilechallenge.view;
+
+import android.os.Bundle;
+
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.databinding.DataBindingUtil;
+import androidx.lifecycle.ViewModelProviders;
+
+import com.dan.traderevmobilechallenge.R;
+import com.dan.traderevmobilechallenge.databinding.ActivityMainBinding;
+import com.dan.traderevmobilechallenge.view.fragments.GridViewFragment;
+import com.dan.traderevmobilechallenge.viewmodel.MainActivityViewModel;
+
+import es.dmoral.toasty.Toasty;
+
+public class MainActivity extends AppCompatActivity {
+
+ public static int currentPosition;
+ private static final String KEY_CURRENT_POSITION = "key_current_position";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ // Data binding object created by compiler
+ ActivityMainBinding activityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
+
+ //ViewModel
+ MainActivityViewModel mainActivityViewModel = ViewModelProviders.of(this).get(MainActivityViewModel.class);
+ mainActivityViewModel.getNetworkStateLiveData().observe(this, newWorkOk -> {
+ // Network problem occurs
+ if (!newWorkOk){
+ Toasty.error(MainActivity.this,getString(R.string.no_internet), Toasty.LENGTH_SHORT).show();
+ }
+
+ });
+
+ //Toolbar
+ setSupportActionBar(activityMainBinding.toolBar);
+ if (savedInstanceState != null) {
+ currentPosition = savedInstanceState.getInt(KEY_CURRENT_POSITION, 0);
+ return;
+ }
+
+ loadGridViewFragment();
+ }
+
+ /**
+ * Method to load grid view for showing photos
+ */
+ private void loadGridViewFragment() {
+
+ getSupportFragmentManager().beginTransaction()
+ .add(R.id.fragment_container, new GridViewFragment(), GridViewFragment.class.getSimpleName())
+ .commit();
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putInt(KEY_CURRENT_POSITION, currentPosition);
+ }
+}
diff --git a/app/src/main/java/com/dan/traderevmobilechallenge/view/fragments/FullImagePagerFragment.java b/app/src/main/java/com/dan/traderevmobilechallenge/view/fragments/FullImagePagerFragment.java
new file mode 100644
index 00000000..dc4cb37b
--- /dev/null
+++ b/app/src/main/java/com/dan/traderevmobilechallenge/view/fragments/FullImagePagerFragment.java
@@ -0,0 +1,163 @@
+package com.dan.traderevmobilechallenge.view.fragments;
+
+import android.os.Bundle;
+import android.transition.Transition;
+import android.transition.TransitionInflater;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.core.app.SharedElementCallback;
+import androidx.fragment.app.Fragment;
+import androidx.lifecycle.ViewModelProviders;
+import androidx.viewpager.widget.ViewPager;
+
+import com.daimajia.androidanimations.library.Techniques;
+import com.daimajia.androidanimations.library.YoYo;
+import com.dan.traderevmobilechallenge.R;
+import com.dan.traderevmobilechallenge.adapters.ImagePagerAdapter;
+import com.dan.traderevmobilechallenge.databinding.FragmentPagerBinding;
+import com.dan.traderevmobilechallenge.model.Photo;
+import com.dan.traderevmobilechallenge.utils.StringUtil;
+import com.dan.traderevmobilechallenge.view.MainActivity;
+import com.dan.traderevmobilechallenge.viewmodel.MainActivityViewModel;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * This fragment utilizes full screen view to show selected photo in grid view page{@link GridViewFragment}
+ */
+@SuppressWarnings("ConstantConditions")
+public class FullImagePagerFragment extends Fragment {
+
+ private FragmentPagerBinding fragmentPagerBinding;
+ private boolean toggleClick = false;
+ private ArrayList photos;
+
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+
+ //MainActivity's ViewModel
+ MainActivityViewModel mainActivityViewModel = ViewModelProviders.of(Objects.requireNonNull(getActivity())).get(MainActivityViewModel.class);
+ // Get photo list data from ViewModel
+ photos = mainActivityViewModel.getPhotosLiveData().getValue();
+ // Inflate by DataBinding object
+ fragmentPagerBinding = FragmentPagerBinding.inflate(inflater);
+
+ fragmentPagerBinding.viewPager.setAdapter(new ImagePagerAdapter(this, photos));
+ // Set the current position and add a listener that will update the selection coordinator when
+ // paging the images.
+ fragmentPagerBinding.viewPager.setCurrentItem(MainActivity.currentPosition);
+ fragmentPagerBinding.viewPager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
+ @Override
+ public void onPageSelected(int position) {
+ MainActivity.currentPosition = position;
+ }
+ });
+
+ prepareSharedElementTransition();
+
+ // Avoid a postponeEnterTransition on orientation change, and postpone only of first creation.
+ if (savedInstanceState == null) {
+ postponeEnterTransition();
+ }
+ initFloatButton();
+ showPhotoInfo();
+ return fragmentPagerBinding.getRoot();
+ }
+
+ /**
+ * Method to show photo info for current page
+ */
+ private void showPhotoInfo() {
+
+ fragmentPagerBinding.viewPager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener(){
+ @Override
+ public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
+ // set photo info showing in full screen page
+ fragmentPagerBinding.tvPhotoInfo.setText(StringUtil.formatPhotoData(photos.get(position)));
+ }
+ });
+ }
+
+ /**
+ * Initialize floating button that controls showing/hiding photo info
+ */
+ private void initFloatButton() {
+
+ //Animation with TextField for showing photo info
+ Objects.requireNonNull(getActivity()).runOnUiThread(() -> YoYo.with(Techniques.Tada)
+ .duration(700)
+ .repeat(1)
+ .playOn(fragmentPagerBinding.tvPhotoInfo));
+ // when floating button clicks on toggle , show on/off the photo info
+ fragmentPagerBinding.fbBtn.setOnClickListener(v -> getActivity().runOnUiThread(() -> {
+
+ toggleClick = !toggleClick;
+
+ if (toggleClick) {
+ fragmentPagerBinding.fbBtn.setImageResource(R.drawable.eye_outline);
+ fragmentPagerBinding.tvPhotoInfo.setVisibility(View.INVISIBLE);
+ } else {
+ fragmentPagerBinding.tvPhotoInfo.setVisibility(View.VISIBLE);
+ fragmentPagerBinding.fbBtn.setImageResource(R.drawable.eye_off_outline);
+ }
+ }));
+ }
+
+ /**
+ * Method to prepare shared elements transition from the one with same transition name in
+ * GridView{@link GridViewFragment}
+ */
+ private void prepareSharedElementTransition() {
+ Transition transition =
+ TransitionInflater.from(getContext())
+ .inflateTransition(R.transition.image_shared_element_transition);
+ setSharedElementEnterTransition(transition);
+
+ // A similar mapping is set at the GridFragment with a setExitSharedElementCallback.
+ setEnterSharedElementCallback(
+ new SharedElementCallback() {
+ @Override
+ public void onMapSharedElements(List names, Map sharedElements) {
+ // Locate the image view at the primary fragment (the ImageFragment that is currently
+ // visible). To locate the fragment, call instantiateItem with the selection position.
+ // At this stage, the method will simply return the fragment at the position and will
+ // not create a new one.
+ Fragment currentFragment = (Fragment) Objects.requireNonNull(fragmentPagerBinding.viewPager.getAdapter())
+ .instantiateItem(fragmentPagerBinding.viewPager, MainActivity.currentPosition);
+ View view = currentFragment.getView();
+ if (view == null) {
+ return;
+ }
+
+ // Map the first shared element name to the child ImageView.
+ sharedElements.put(names.get(0), view.findViewById(R.id.image));
+ }
+ });
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ // Hide toolbar
+ if (getActivity() != null) {
+ ((AppCompatActivity) getActivity()).getSupportActionBar().hide();
+ }
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ // show toolbar for Activity
+ if (getActivity() != null) {
+ ((AppCompatActivity) getActivity()).getSupportActionBar().show();
+ }
+ }
+}
diff --git a/app/src/main/java/com/dan/traderevmobilechallenge/view/fragments/GridViewFragment.java b/app/src/main/java/com/dan/traderevmobilechallenge/view/fragments/GridViewFragment.java
new file mode 100644
index 00000000..863d3f58
--- /dev/null
+++ b/app/src/main/java/com/dan/traderevmobilechallenge/view/fragments/GridViewFragment.java
@@ -0,0 +1,149 @@
+package com.dan.traderevmobilechallenge.view.fragments;
+
+import android.os.Bundle;
+import android.transition.TransitionInflater;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.app.SharedElementCallback;
+import androidx.fragment.app.Fragment;
+import androidx.lifecycle.ViewModelProviders;
+import androidx.recyclerview.widget.RecyclerView;
+
+
+import com.dan.traderevmobilechallenge.R;
+import com.dan.traderevmobilechallenge.adapters.GridAdapter;
+import com.dan.traderevmobilechallenge.databinding.FragmentGridBinding;
+import com.dan.traderevmobilechallenge.view.LoadingDialog;
+import com.dan.traderevmobilechallenge.view.MainActivity;
+import com.dan.traderevmobilechallenge.viewmodel.MainActivityViewModel;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * This fragment class to utilize Grid view for showing photos
+ * using Grid layout
+ */
+public class GridViewFragment extends Fragment {
+
+ private GridAdapter gridAdapter;
+ private FragmentGridBinding fragmentGridBinding;
+ private LoadingDialog loadingDialog;
+
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+
+ fragmentGridBinding = FragmentGridBinding.inflate(inflater);
+
+ gridAdapter = new GridAdapter(this);
+ fragmentGridBinding.recyclerView.setAdapter(gridAdapter);
+
+ showLoadingDialog(savedInstanceState);
+ //Get ViewModel
+ MainActivityViewModel viewModel = ViewModelProviders.of(Objects.requireNonNull(getActivity())).get(MainActivityViewModel.class);
+
+ // Observe Data from ViewModel
+ viewModel.getPhotosLiveData().observe(getViewLifecycleOwner(), photos -> {
+ // show data
+ gridAdapter.submitList(photos);
+ //loading dialog will be dismissing
+ if (loadingDialog != null){
+ loadingDialog.dismiss();
+ loadingDialog = null;
+ }else{
+ loadingDialog = (LoadingDialog) getChildFragmentManager().findFragmentByTag(LoadingDialog.class.getSimpleName());
+ if (loadingDialog != null) {
+ loadingDialog.dismiss();
+ loadingDialog = null;
+ }
+ }
+ });
+
+ prepareTransitions();
+ postponeEnterTransition();
+
+ return fragmentGridBinding.getRoot();
+ }
+
+ /**
+ * Show Loading Dialog while fetching data
+ * @param savedInstanceState Bundle sent by Android system
+ */
+ private void showLoadingDialog(Bundle savedInstanceState) {
+ if (savedInstanceState == null) {
+ loadingDialog = new LoadingDialog();
+ loadingDialog.setCancelable(false);
+ loadingDialog.show(getChildFragmentManager(), LoadingDialog.class.getSimpleName());
+ }
+ }
+
+ @Override
+ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ scrollToPosition();
+ }
+
+ /**
+ * To Scroll to position the view for the current position is null (not currently part of
+ * layout manager children), or it's not completely visible.
+ */
+ private void scrollToPosition() {
+ fragmentGridBinding.recyclerView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
+ @Override
+ public void onLayoutChange(View v,
+ int left,
+ int top,
+ int right,
+ int bottom,
+ int oldLeft,
+ int oldTop,
+ int oldRight,
+ int oldBottom) {
+ fragmentGridBinding.recyclerView.removeOnLayoutChangeListener(this);
+
+ final RecyclerView.LayoutManager layoutManager = fragmentGridBinding.recyclerView.getLayoutManager();
+ View viewAtPosition = Objects.requireNonNull(layoutManager).findViewByPosition(MainActivity.currentPosition);
+ // Scroll to position if the view for the current position is null (not currently part of
+ // layout manager children), or it's not completely visible.
+ if (viewAtPosition == null || layoutManager
+ .isViewPartiallyVisible(viewAtPosition, false, true)) {
+ fragmentGridBinding.recyclerView.post(() -> layoutManager.scrollToPosition(MainActivity.currentPosition));
+ }
+ }
+ });
+ }
+
+ /**
+ * To Prepare shared elements transition
+ */
+ private void prepareTransitions() {
+ setExitTransition(TransitionInflater.from(getContext())
+ .inflateTransition(R.transition.grid_exit_transition));
+
+ // A similar mapping is set at the FullImagePagerFragment with a setEnterSharedElementCallback.
+ setExitSharedElementCallback(
+ new SharedElementCallback() {
+ @Override
+ public void onMapSharedElements(List names, Map sharedElements) {
+ // Locate the ViewHolder for the clicked position.
+
+ RecyclerView.ViewHolder selectedViewHolder = fragmentGridBinding.recyclerView
+ .findViewHolderForAdapterPosition(MainActivity.currentPosition);
+ if (selectedViewHolder == null) {
+ return;
+ }
+
+ // Map the first shared element name to the child ImageView.
+ sharedElements
+ .put(names.get(0), selectedViewHolder.itemView.findViewById(R.id.iv_photo));
+
+ }
+ });
+ }
+}
diff --git a/app/src/main/java/com/dan/traderevmobilechallenge/view/fragments/ImageFragment.java b/app/src/main/java/com/dan/traderevmobilechallenge/view/fragments/ImageFragment.java
new file mode 100644
index 00000000..41db3870
--- /dev/null
+++ b/app/src/main/java/com/dan/traderevmobilechallenge/view/fragments/ImageFragment.java
@@ -0,0 +1,71 @@
+package com.dan.traderevmobilechallenge.view.fragments;
+
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.load.DataSource;
+import com.bumptech.glide.load.engine.GlideException;
+import com.bumptech.glide.request.RequestListener;
+import com.bumptech.glide.request.target.Target;
+import com.dan.traderevmobilechallenge.R;
+import com.dan.traderevmobilechallenge.application.CustomApp;
+import com.dan.traderevmobilechallenge.model.Photo;
+
+/**
+ * A Fragment that instantiates a full screen page showing each photo
+ * Calls from {@link com.dan.traderevmobilechallenge.adapters.ImagePagerAdapter}
+ */
+@SuppressWarnings("ALL")
+public class ImageFragment extends Fragment {
+
+ private static final String KEY_PHOTO = "com.dan.traderevmobilechallenge.view.fragments.key.photo";
+
+ public static ImageFragment newInstance(Photo photo) {
+ ImageFragment fragment = new ImageFragment();
+ Bundle argument = new Bundle();
+ argument.putParcelable(KEY_PHOTO, photo);
+ fragment.setArguments(argument);
+ return fragment;
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+
+ final View view = inflater.inflate(R.layout.fragment_image, container, false);
+ // Parse data sent from ImagePagerAdapter
+ Bundle arguments = getArguments();
+ Photo photo = arguments.getParcelable(KEY_PHOTO);
+ // Set transition name set in GridAdapter
+ view.findViewById(R.id.image).setTransitionName(CustomApp.getContext().getString(R.string.photo) + photo.id);
+ // load photo
+ Glide.with(this)
+ .load(photo.urls.regular)
+ .listener(new RequestListener() {
+ @SuppressWarnings("ConstantConditions")
+ @Override
+ public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, boolean isFirstResource) {
+ getParentFragment().startPostponedEnterTransition();
+ return false;
+ }
+
+ @Override
+ public boolean onResourceReady(Drawable resource, Object model, Target target, DataSource dataSource, boolean isFirstResource) {
+ getParentFragment().startPostponedEnterTransition();
+ return false;
+ }
+ }).into((ImageView) view.findViewById(R.id.image));
+
+ return view;
+ }
+}
diff --git a/app/src/main/java/com/dan/traderevmobilechallenge/viewmodel/MainActivityViewModel.java b/app/src/main/java/com/dan/traderevmobilechallenge/viewmodel/MainActivityViewModel.java
new file mode 100644
index 00000000..76605cea
--- /dev/null
+++ b/app/src/main/java/com/dan/traderevmobilechallenge/viewmodel/MainActivityViewModel.java
@@ -0,0 +1,160 @@
+package com.dan.traderevmobilechallenge.viewmodel;
+
+import android.app.Application;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.AndroidViewModel;
+import androidx.lifecycle.MutableLiveData;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import com.dan.traderevmobilechallenge.components.NetworkStateChangeReceiver;
+import com.dan.traderevmobilechallenge.model.Photo;
+import com.dan.traderevmobilechallenge.repository.RemoteRepository;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import io.reactivex.Observable;
+import io.reactivex.Observer;
+import io.reactivex.android.schedulers.AndroidSchedulers;
+import io.reactivex.disposables.CompositeDisposable;
+import io.reactivex.disposables.Disposable;
+import io.reactivex.functions.Function;
+import io.reactivex.schedulers.Schedulers;
+import static com.dan.traderevmobilechallenge.components.NetworkStateChangeReceiver.IS_NETWORK_AVAILABLE;
+
+/**
+ * ViewModel of {@link com.dan.traderevmobilechallenge.view.MainActivity}
+ * It's being used by {@link com.dan.traderevmobilechallenge.view.fragments.GridViewFragment}
+ * that is attached to the MainActivity.
+ *
+ * It gets data sent by unsplash API server and make it as observable live data, then
+ * GridViewFragment will be observing it
+ *
+ * Created by Dan Kim on 2019-04-26
+ */
+public class MainActivityViewModel extends AndroidViewModel {
+
+ private static final int PARAM_NUMBER_OF_PAGE = 10;
+ private static final int PARAM_MAX_NUMBER_OF_PHOTOS_IN_A_PAGE = 30;
+ private static final String TAG = "myDebug";
+ private final RemoteRepository remoteRepository;
+ private final CompositeDisposable compositeDisposable = new CompositeDisposable();
+ private final MutableLiveData> photosLiveData = new MutableLiveData<>();
+ private final MutableLiveData networkStateLiveData = new MutableLiveData<>();
+
+ public MainActivityViewModel(@NonNull Application application) {
+ super(application);
+
+ initNetWork();
+ //Instantiate Repository
+ remoteRepository = new RemoteRepository();
+ getData();
+ }
+
+ /**
+ * Method to initialize to receive broadcast message from application -level
+ */
+ private void initNetWork() {
+
+ IntentFilter intentFilter = new IntentFilter(NetworkStateChangeReceiver.NETWORK_AVAILABLE_ACTION);
+ LocalBroadcastManager.getInstance(getApplication()).registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ boolean isNetworkAvailable = intent.getBooleanExtra(IS_NETWORK_AVAILABLE, false);
+ // if receiving network issue
+ if (!isNetworkAvailable){
+ Log.d(TAG, "No NET WORK: ");
+ //Because of this set, MainActivity will be notifying the issue
+ networkStateLiveData.setValue(false);
+ }else{
+
+ networkStateLiveData.setValue(true);
+ // Now network is backed up, check if no photos have not received yet
+ // call api to get them
+ if(photosLiveData.getValue() == null){
+ getData();
+ }
+ }
+
+ }
+ }, intentFilter);
+ }
+
+ /**
+ * Method to get Data from unsplash API server
+ */
+ private void getData() {
+ // Add Observables that holds data from unsplash API on each page
+ // Apparently up to 10 pages . I set to receive 30 photos on each page
+ // By my observation, there is no single api to get all pages
+ // Also, I checked manually , how many pages are available for me and up to 59 pages
+ // are available and tested and ok, but wasteful, so I set only up tp 10 page I want to get
+ List>> photoObservableList = new ArrayList<>();
+ for (int i = 1; i <= PARAM_NUMBER_OF_PAGE; i++) {
+
+ photoObservableList.add(getObservablePhotos(i));
+ }
+ Observable.zip(photoObservableList, (Function