diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..39fb081 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures +.externalNativeBuild diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..dd9fbdd --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,122 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 27 + defaultConfig { + applicationId "github.shardul.cats" + + minSdkVersion 17 + + targetSdkVersion 27 + + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + vectorDrawables.useSupportLibrary = true + } + + dataBinding { + enabled = true + } + + buildTypes { + release { + + // Load signing config, we keep release signing credentials out of build file + Properties properties = new Properties() + properties.load(project.rootProject.file('local.properties').newDataInputStream()) + + def keystoreFile = properties.getProperty('keystore.file') + def keystorePassword = properties.getProperty('keystore.password') + def keystoreAlias = properties.getProperty('keystore.alias') + def validConfig = keystoreFile != null && keystorePassword != null && keystoreAlias != null; + + if (validConfig) { + System.out.println("Release signing configured with " + keystoreFile) + signingConfigs { + release { + storeFile project.rootProject.file(keystoreFile) + storePassword keystorePassword + keyAlias keystoreAlias + keyPassword keystorePassword + v1SigningEnabled true + v2SigningEnabled true + } + } + } else { + System.out.println("Specify keystore.file, keystore.alias and keystore.password in local.properties to enable release signing.") + } + + if (validConfig) { + signingConfig signingConfigs.release + } + + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + + flavorDimensions "all" + + productFlavors { + cats { + buildConfigField "String", "CAT_CATEGORY", "\"clothes\"" + dimension "all" + } + kittens { + buildConfigField "String", "CAT_CATEGORY", "\"caturday\"" + dimension "all" + applicationIdSuffix ".kittens" + } + } + + compileOptions { + targetCompatibility 1.8 + sourceCompatibility 1.8 + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + + // support libraries + implementation "com.android.support:appcompat-v7:$support_version" + implementation "com.android.support:recyclerview-v7:$support_version" + implementation "com.android.support:cardview-v7:$support_version" + implementation "com.android.support:design:$support_version" + + implementation "com.android.support.constraint:constraint-layout:$constraint_layout_version" + + // network + implementation "com.squareup.retrofit2:retrofit:$retrofit_version" + implementation "com.squareup.retrofit2:converter-gson:$retrofit_version" + implementation "com.squareup.okhttp3:logging-interceptor:$okhttp_version" + implementation "com.google.code.gson:gson:$google_gson_version" + implementation "com.github.bumptech.glide:glide:$glide_version" + implementation("com.squareup.retrofit2:converter-simplexml:$simple_xml_version"){ + exclude group: 'xpp3', module: 'xpp3' + exclude group: 'stax', module: 'stax-api' + exclude group: 'stax', module: 'stax' + } + + // di + implementation "com.google.dagger:dagger:$dagger_version" + annotationProcessor "com.google.dagger:dagger-compiler:$dagger_version" + + // Architecture components + implementation "android.arch.lifecycle:extensions:$arch_version" + implementation "android.arch.lifecycle:runtime:$arch_version" + implementation "android.arch.persistence.room:runtime:$room_version" + annotationProcessor "android.arch.persistence.room:compiler:$room_version" + + + testImplementation "junit:junit:$junit_version" + androidTestImplementation "com.android.support.test:runner:$runner_version" + androidTestImplementation "com.android.support.test.espresso:espresso-core:$espresso_version" + + // Misc + implementation "com.jakewharton.timber:timber:$timber_version" + + +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /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/github/shardul/cats/LoadCatsInstrumentedTest.java b/app/src/androidTest/java/github/shardul/cats/LoadCatsInstrumentedTest.java new file mode 100644 index 0000000..1baefed --- /dev/null +++ b/app/src/androidTest/java/github/shardul/cats/LoadCatsInstrumentedTest.java @@ -0,0 +1,84 @@ +package github.shardul.cats; + +import android.app.Activity; +import android.support.test.espresso.IdlingRegistry; +import android.support.test.espresso.IdlingResource; +import android.support.test.rule.ActivityTestRule; +import android.support.test.runner.AndroidJUnit4; +import android.view.View; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static android.support.test.espresso.Espresso.onView; +import static android.support.test.espresso.action.ViewActions.click; +import static android.support.test.espresso.assertion.ViewAssertions.matches; +import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; +import static android.support.test.espresso.matcher.ViewMatchers.withId; + + +@RunWith(AndroidJUnit4.class) +/** + * Checks when refresh is tapped progress view is shown, + * and when refresh is complete refresh button is visible again. + * */ +public class LoadCatsInstrumentedTest { + + @Rule + public ActivityTestRule mMainActivityRule + = new ActivityTestRule<>(MainActivity.class); + + @Test + public void loadCats() { + + final int progressId = R.id.cats_fragment_progress; + final int buttonId = R.id.cats_fragment_refresh; + + onView(withId(buttonId)).perform(click()); + + onView(withId(progressId)).check(matches(isDisplayed())); + + final VisibilityIdlingResource progressIdlingResource = new VisibilityIdlingResource( + mMainActivityRule.getActivity() , progressId); + + + IdlingRegistry.getInstance().register(progressIdlingResource); + + onView(withId(buttonId)).check(matches(isDisplayed())); + } + +} + +class VisibilityIdlingResource implements IdlingResource { + + final Activity mActivity; + final int mResource; + ResourceCallback mResourceCallback; + + public VisibilityIdlingResource(Activity activity, int resource) { + this.mActivity = activity; + this.mResource = resource; + } + + @Override + public String getName() { + return "loading progress"; + } + + @Override + public boolean isIdleNow() { + final boolean idleNow = mActivity.findViewById(mResource).getVisibility() == View.GONE; + if (idleNow) { + if (mResourceCallback != null) { + mResourceCallback.onTransitionToIdle(); + } + } + return idleNow; + } + + @Override + public void registerIdleTransitionCallback(ResourceCallback callback) { + mResourceCallback = callback; + } +} diff --git a/app/src/kittens/res/drawable/ic_launcher_background.xml b/app/src/kittens/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..9a806bd --- /dev/null +++ b/app/src/kittens/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/kittens/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/kittens/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..036d09b --- /dev/null +++ b/app/src/kittens/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/kittens/res/mipmap-hdpi/ic_launcher.png b/app/src/kittens/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..84f92ab Binary files /dev/null and b/app/src/kittens/res/mipmap-hdpi/ic_launcher.png differ diff --git a/app/src/kittens/res/mipmap-hdpi/ic_launcher_foreground.png b/app/src/kittens/res/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..e8d4b55 Binary files /dev/null and b/app/src/kittens/res/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/app/src/kittens/res/mipmap-hdpi/ic_launcher_round.png b/app/src/kittens/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000..93b489f Binary files /dev/null and b/app/src/kittens/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/app/src/kittens/res/mipmap-mdpi/ic_launcher.png b/app/src/kittens/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..49fd1ae Binary files /dev/null and b/app/src/kittens/res/mipmap-mdpi/ic_launcher.png differ diff --git a/app/src/kittens/res/mipmap-mdpi/ic_launcher_foreground.png b/app/src/kittens/res/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..5250ed6 Binary files /dev/null and b/app/src/kittens/res/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/app/src/kittens/res/mipmap-mdpi/ic_launcher_round.png b/app/src/kittens/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000..fa8b8b0 Binary files /dev/null and b/app/src/kittens/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/app/src/kittens/res/mipmap-xhdpi/ic_launcher.png b/app/src/kittens/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..5245063 Binary files /dev/null and b/app/src/kittens/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/app/src/kittens/res/mipmap-xhdpi/ic_launcher_foreground.png b/app/src/kittens/res/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..27b8b94 Binary files /dev/null and b/app/src/kittens/res/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/app/src/kittens/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/kittens/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000..06b626d Binary files /dev/null and b/app/src/kittens/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/app/src/kittens/res/mipmap-xxhdpi/ic_launcher.png b/app/src/kittens/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..d2ac522 Binary files /dev/null and b/app/src/kittens/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/app/src/kittens/res/mipmap-xxhdpi/ic_launcher_foreground.png b/app/src/kittens/res/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..c4852fe Binary files /dev/null and b/app/src/kittens/res/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/app/src/kittens/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/kittens/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..a8ac592 Binary files /dev/null and b/app/src/kittens/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/app/src/kittens/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/kittens/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..31f2995 Binary files /dev/null and b/app/src/kittens/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/app/src/kittens/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/app/src/kittens/res/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..8974d3f Binary files /dev/null and b/app/src/kittens/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/app/src/kittens/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/kittens/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..eca0a46 Binary files /dev/null and b/app/src/kittens/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/app/src/kittens/res/values/colors.xml b/app/src/kittens/res/values/colors.xml new file mode 100644 index 0000000..32dc189 --- /dev/null +++ b/app/src/kittens/res/values/colors.xml @@ -0,0 +1,9 @@ + + + #FFF9C4 + #FFEB3B + #FBC02D + #FF9E80 + #FF6E40 + #FF3D00 + diff --git a/app/src/kittens/res/values/strings.xml b/app/src/kittens/res/values/strings.xml new file mode 100644 index 0000000..7427a71 --- /dev/null +++ b/app/src/kittens/res/values/strings.xml @@ -0,0 +1,3 @@ + + Kittens + diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..2f7b4f0 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/ic_launcher-web.png b/app/src/main/ic_launcher-web.png new file mode 100644 index 0000000..1704bdd Binary files /dev/null and b/app/src/main/ic_launcher-web.png differ diff --git a/app/src/main/java/github/shardul/cats/CatsApplication.java b/app/src/main/java/github/shardul/cats/CatsApplication.java new file mode 100644 index 0000000..2e36b5a --- /dev/null +++ b/app/src/main/java/github/shardul/cats/CatsApplication.java @@ -0,0 +1,42 @@ +package github.shardul.cats; + +import android.app.Activity; +import android.app.Application; + +import github.shardul.cats.di.AppContextModule; +import github.shardul.cats.di.CatsAppComponent; +import github.shardul.cats.di.DaggerCatsAppComponent; +import timber.log.Timber; + +/** + * Created by Shardul on 29/03/18. + */ +public class CatsApplication extends Application { + + private CatsAppComponent mCatsAppComponent; + + public static CatsAppComponent get(Activity activity) { + return ((CatsApplication)activity.getApplication()).mCatsAppComponent; + } + + @Override + public void onCreate() { + super.onCreate(); + + if (BuildConfig.DEBUG) { + Timber.plant(new Timber.DebugTree()); + } + + mCatsAppComponent = DaggerCatsAppComponent.builder() + .appContextModule(new AppContextModule(getApplicationContext())).build(); + + } + + public CatsAppComponent getCatsAppComponent() { + return mCatsAppComponent; + } + + public CatsRepository getRepository() { + return mCatsAppComponent.repository(); + } +} diff --git a/app/src/main/java/github/shardul/cats/CatsRepository.java b/app/src/main/java/github/shardul/cats/CatsRepository.java new file mode 100644 index 0000000..3700004 --- /dev/null +++ b/app/src/main/java/github/shardul/cats/CatsRepository.java @@ -0,0 +1,90 @@ +package github.shardul.cats; + +import android.arch.lifecycle.LiveData; +import android.arch.lifecycle.MediatorLiveData; +import android.arch.lifecycle.Observer; +import android.support.annotation.Nullable; + +import java.util.List; + +import github.shardul.cats.core.AppExecutors; +import github.shardul.cats.db.CatsDatabase; +import github.shardul.cats.models.Cat; +import github.shardul.cats.network.CatsService; +import github.shardul.cats.network.FetchCatsTask; +import github.shardul.cats.network.FetchListener; + +/** + * Single access point to access cats + * Created by Shardul on 31/03/18. + */ +public class CatsRepository { + + final CatsDatabase mCatsDatabase; + final AppExecutors mAppExecutors; + final CatsService mCatsService; + + private MediatorLiveData> mObservableCats; + + private FetchCatsTask mFetchCatsTask; + private FetchListener> mListener; + + public CatsRepository(CatsDatabase catsDatabase, AppExecutors appExecutors, + CatsService catsService) { + mCatsDatabase = catsDatabase; + mAppExecutors = appExecutors; + mCatsService = catsService; + mObservableCats = new MediatorLiveData<>(); + + mObservableCats.addSource(mCatsDatabase.catsDao().getAll(), new Observer>() { + @Override + public void onChanged(@Nullable List cats) { + + if (cats.isEmpty()) { + fetchNewCats(); + } + + CatsRepository.this.mObservableCats.postValue(cats); + } + }); + + + } + + public LiveData> getObservableCats() { + return mObservableCats; + } + + public void fetchNewCats() { + + mAppExecutors.getDiskIO().execute(new Runnable() { + @Override + public void run() { + mCatsDatabase.catsDao().deleteAll(); + } + }); + + mListener = new FetchListener>() { + @Override + public void onFetchComplete(List complete) { + + CatsRepository.this.mListener = null; + + mAppExecutors.getDiskIO().execute(new Runnable() { + @Override + public void run() { + mCatsDatabase.catsDao().insertAll(complete.toArray(new Cat[complete.size()])); + } + }); + } + + @Override + public void onFetchFailed(Exception e) { + } + }; + mFetchCatsTask = new FetchCatsTask(mCatsService, mListener); + mAppExecutors.getNetworkIO().execute(mFetchCatsTask); + } + + +} diff --git a/app/src/main/java/github/shardul/cats/MainActivity.java b/app/src/main/java/github/shardul/cats/MainActivity.java new file mode 100644 index 0000000..6bf6a27 --- /dev/null +++ b/app/src/main/java/github/shardul/cats/MainActivity.java @@ -0,0 +1,31 @@ +package github.shardul.cats; + +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; + +import java.util.List; + +import javax.inject.Inject; + +import github.shardul.cats.db.CatsDatabase; +import github.shardul.cats.models.Cat; +import github.shardul.cats.network.CatsService; +import github.shardul.cats.network.FetchCatsTask; + +public class MainActivity extends AppCompatActivity { + + @Inject + CatsService mCatsService; + + @Inject + CatsDatabase mCatsDatabase; + + private FetchCatsTask mFetchCatsTask; + private List cats; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + } +} diff --git a/app/src/main/java/github/shardul/cats/binding/FragmentBindingComponent.java b/app/src/main/java/github/shardul/cats/binding/FragmentBindingComponent.java new file mode 100644 index 0000000..709b735 --- /dev/null +++ b/app/src/main/java/github/shardul/cats/binding/FragmentBindingComponent.java @@ -0,0 +1,22 @@ +package github.shardul.cats.binding; + +import android.databinding.DataBindingComponent; +import android.support.v4.app.Fragment; + +/** + * Series of data binding adapters to be use when data binding + * Created by Shardul on 31/03/18. + */ +public class FragmentBindingComponent implements DataBindingComponent { + + private final ImageBindingAdapter mImageBindingAdapter; + + public FragmentBindingComponent(Fragment fragment) { + mImageBindingAdapter = new ImageBindingAdapter(fragment); + } + + @Override + public ImageBindingAdapter getImageBindingAdapter() { + return mImageBindingAdapter; + } +} diff --git a/app/src/main/java/github/shardul/cats/binding/ImageBindingAdapter.java b/app/src/main/java/github/shardul/cats/binding/ImageBindingAdapter.java new file mode 100644 index 0000000..335eaa7 --- /dev/null +++ b/app/src/main/java/github/shardul/cats/binding/ImageBindingAdapter.java @@ -0,0 +1,25 @@ +package github.shardul.cats.binding; + +import android.databinding.BindingAdapter; +import android.support.v4.app.Fragment; +import android.widget.ImageView; + +import com.bumptech.glide.Glide; + +/** + * When bound, imageUrl = @{url} can be used in layout to download images directly. + * Created by Shardul on 31/03/18. + */ +public class ImageBindingAdapter { + + final Fragment mFragment; + + public ImageBindingAdapter(Fragment fragment) { + mFragment = fragment; + } + + @BindingAdapter("imageUrl") + public void setImage(ImageView image, String url) { + Glide.with(mFragment).load(url).into(image); + } +} diff --git a/app/src/main/java/github/shardul/cats/core/AppExecutors.java b/app/src/main/java/github/shardul/cats/core/AppExecutors.java new file mode 100644 index 0000000..30ab342 --- /dev/null +++ b/app/src/main/java/github/shardul/cats/core/AppExecutors.java @@ -0,0 +1,47 @@ +package github.shardul.cats.core; + +import android.os.Handler; +import android.os.Looper; +import android.support.annotation.NonNull; + +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; + +/** + * Provides executor instances to run on IO = single thread, UI = Main Thread & Network = 3 threads + * Created by Shardul on 31/03/18. + */ +public class AppExecutors { + + private final Executor mDiskIO; + private final Executor mNetworkIO; + private final Executor mMainThread; + + public AppExecutors() { + mDiskIO = Executors.newSingleThreadExecutor(); + mNetworkIO = Executors.newFixedThreadPool(3); + mMainThread = new MainExecutor(); + } + + public Executor getDiskIO() { + return mDiskIO; + } + + public Executor getNetworkIO() { + return mNetworkIO; + } + + public Executor getMainThread() { + return mMainThread; + } + + private class MainExecutor implements Executor { + + private Handler mainThreadHandler = new Handler(Looper.getMainLooper()); + + @Override + public void execute(@NonNull Runnable command) { + mainThreadHandler.post(command); + } + } +} diff --git a/app/src/main/java/github/shardul/cats/db/CatsDao.java b/app/src/main/java/github/shardul/cats/db/CatsDao.java new file mode 100644 index 0000000..893ddad --- /dev/null +++ b/app/src/main/java/github/shardul/cats/db/CatsDao.java @@ -0,0 +1,31 @@ +package github.shardul.cats.db; + +import android.arch.lifecycle.LiveData; +import android.arch.persistence.room.Dao; +import android.arch.persistence.room.Insert; +import android.arch.persistence.room.OnConflictStrategy; +import android.arch.persistence.room.Query; + +import java.util.List; + +import github.shardul.cats.models.Cat; + +/** + * Data access object for cat table + * Created by Shardul on 31/03/18. + */ +@Dao +public interface CatsDao { + + @Query("SELECT * FROM cat") + LiveData> getAll(); + + @Query("SELECT * FROM cat WHERE _id LIKE :catid") + public List catsWithId(String catid); + + @Insert(onConflict = OnConflictStrategy.REPLACE) + public void insertAll(Cat... cats); + + @Query("DELETE FROM cat") + public void deleteAll(); +} diff --git a/app/src/main/java/github/shardul/cats/db/CatsDatabase.java b/app/src/main/java/github/shardul/cats/db/CatsDatabase.java new file mode 100644 index 0000000..6882746 --- /dev/null +++ b/app/src/main/java/github/shardul/cats/db/CatsDatabase.java @@ -0,0 +1,15 @@ +package github.shardul.cats.db; + +import android.arch.persistence.room.Database; +import android.arch.persistence.room.RoomDatabase; + +import github.shardul.cats.models.Cat; + +/** + * Room Database + * Created by Shardul on 31/03/18. + */ +@Database(entities = {Cat.class}, version = 1, exportSchema = false) +public abstract class CatsDatabase extends RoomDatabase { + public abstract CatsDao catsDao(); +} diff --git a/app/src/main/java/github/shardul/cats/di/ActivityModule.java b/app/src/main/java/github/shardul/cats/di/ActivityModule.java new file mode 100644 index 0000000..e313c53 --- /dev/null +++ b/app/src/main/java/github/shardul/cats/di/ActivityModule.java @@ -0,0 +1,28 @@ +package github.shardul.cats.di; + +import android.app.Activity; +import android.content.Context; + +import javax.inject.Named; + +import dagger.Module; +import dagger.Provides; + +/** + * Created by Shardul on 29/03/18. + */ +@Module +public class ActivityModule { + + final Context mContext; + + public ActivityModule(Activity activity) { + mContext = activity; + } + + @Provides + @Named("activity_context") + public Context getmContext() { + return mContext; + } +} diff --git a/app/src/main/java/github/shardul/cats/di/AppContextModule.java b/app/src/main/java/github/shardul/cats/di/AppContextModule.java new file mode 100644 index 0000000..3087e43 --- /dev/null +++ b/app/src/main/java/github/shardul/cats/di/AppContextModule.java @@ -0,0 +1,29 @@ +package github.shardul.cats.di; + +import android.content.Context; + +import javax.inject.Named; + +import dagger.Module; +import dagger.Provides; + +/** + * Created by Shardul on 29/03/18. + */ + +@Module +public class AppContextModule { + + final Context mContext; + + public AppContextModule(Context context) { + mContext = context; + } + + @Provides + @CatsApplicationScope + @Named("application_context") + public Context context() { + return mContext; + } +} diff --git a/app/src/main/java/github/shardul/cats/di/CatsAppComponent.java b/app/src/main/java/github/shardul/cats/di/CatsAppComponent.java new file mode 100644 index 0000000..a71706d --- /dev/null +++ b/app/src/main/java/github/shardul/cats/di/CatsAppComponent.java @@ -0,0 +1,17 @@ +package github.shardul.cats.di; + +import dagger.Component; +import github.shardul.cats.CatsRepository; +import github.shardul.cats.network.CatsService; + +/** + * Created by Shardul on 29/03/18. + */ +@CatsApplicationScope +@Component(modules = {CatsServicesModule.class, CatsDataModule.class}) +public interface CatsAppComponent { + + CatsService catsService(); + + CatsRepository repository(); +} diff --git a/app/src/main/java/github/shardul/cats/di/CatsApplicationScope.java b/app/src/main/java/github/shardul/cats/di/CatsApplicationScope.java new file mode 100644 index 0000000..3947fb5 --- /dev/null +++ b/app/src/main/java/github/shardul/cats/di/CatsApplicationScope.java @@ -0,0 +1,15 @@ +package github.shardul.cats.di; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.inject.Scope; + +/** + * Created by Shardul on 29/03/18. + */ + +@Scope +@Retention(RetentionPolicy.CLASS) +public @interface CatsApplicationScope { +} diff --git a/app/src/main/java/github/shardul/cats/di/CatsDataModule.java b/app/src/main/java/github/shardul/cats/di/CatsDataModule.java new file mode 100644 index 0000000..aa781ac --- /dev/null +++ b/app/src/main/java/github/shardul/cats/di/CatsDataModule.java @@ -0,0 +1,34 @@ +package github.shardul.cats.di; + +import android.arch.persistence.room.Room; +import android.content.Context; + +import javax.inject.Named; + +import dagger.Module; +import dagger.Provides; +import github.shardul.cats.CatsRepository; +import github.shardul.cats.core.AppExecutors; +import github.shardul.cats.db.CatsDatabase; +import github.shardul.cats.network.CatsService; + +/** + * Created by Shardul on 31/03/18. + */ + +@Module(includes = AppContextModule.class) +public class CatsDataModule { + + @Provides + @CatsApplicationScope + public CatsDatabase database(@Named("application_context") Context context) { + return Room.databaseBuilder(context, CatsDatabase.class, "cats.sqlite3").build(); + } + + @Provides + @CatsApplicationScope + public CatsRepository repository(CatsDatabase catsDatabase, AppExecutors appExecutors, + CatsService catsService) { + return new CatsRepository(catsDatabase, appExecutors, catsService); + } +} diff --git a/app/src/main/java/github/shardul/cats/di/CatsServicesModule.java b/app/src/main/java/github/shardul/cats/di/CatsServicesModule.java new file mode 100644 index 0000000..f5064a3 --- /dev/null +++ b/app/src/main/java/github/shardul/cats/di/CatsServicesModule.java @@ -0,0 +1,50 @@ +package github.shardul.cats.di; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +import dagger.Module; +import dagger.Provides; +import github.shardul.cats.core.AppExecutors; +import github.shardul.cats.network.CatsService; +import okhttp3.OkHttpClient; +import retrofit2.Retrofit; +import retrofit2.SimpleXmlConverterFactory; + +/** + * Created by Shardul on 29/03/18. + * We only want these to be exposed to outside world + */ +@Module(includes = {NetworkModule.class}) +public class CatsServicesModule { + + @Provides + @CatsApplicationScope + public Gson gson(){ + + GsonBuilder builder = new GsonBuilder(); + return builder.create(); + } + + @Provides + @CatsApplicationScope + public CatsService catsService(Retrofit retrofit){ + return retrofit.create(CatsService.class); + } + + @Provides + @CatsApplicationScope + public Retrofit retrofit(OkHttpClient httpClient, Gson gson) { + return new Retrofit.Builder() + .baseUrl("http://thecatapi.com/") + .client(httpClient) + .addConverterFactory(SimpleXmlConverterFactory.create()) + .build(); + } + + @Provides + @CatsApplicationScope + public AppExecutors executor() { + return new AppExecutors(); + } +} diff --git a/app/src/main/java/github/shardul/cats/di/NetworkModule.java b/app/src/main/java/github/shardul/cats/di/NetworkModule.java new file mode 100644 index 0000000..23defab --- /dev/null +++ b/app/src/main/java/github/shardul/cats/di/NetworkModule.java @@ -0,0 +1,55 @@ +package github.shardul.cats.di; + +import android.content.Context; + +import java.io.File; + +import javax.inject.Named; + +import dagger.Module; +import dagger.Provides; +import okhttp3.Cache; +import okhttp3.OkHttpClient; +import okhttp3.logging.HttpLoggingInterceptor; +import timber.log.Timber; + +/** + * Created by Shardul on 29/03/18. + */ + +@Module(includes = AppContextModule.class) +public class NetworkModule { + + @Provides + @CatsApplicationScope + public OkHttpClient okHttpClient(Cache cache, HttpLoggingInterceptor loggingInterceptor) { + + return new OkHttpClient.Builder() + .addInterceptor(loggingInterceptor) + .cache(cache) + .build(); + } + + @Provides + @CatsApplicationScope + public HttpLoggingInterceptor loggingInterceptor() { + return new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() { + @Override + public void log(String message) { + Timber.i(message); + } + }); + } + + @Provides + @CatsApplicationScope + public File cacheFile(@Named("application_context") Context context) { + return new File(context.getCacheDir(), "ok_http_cache"); + } + + @Provides + @CatsApplicationScope + public Cache cache(File cacheFile) { + return new Cache(cacheFile, 10 * 1024 * 1024); + } +} diff --git a/app/src/main/java/github/shardul/cats/models/Cat.java b/app/src/main/java/github/shardul/cats/models/Cat.java new file mode 100644 index 0000000..ee9a304 --- /dev/null +++ b/app/src/main/java/github/shardul/cats/models/Cat.java @@ -0,0 +1,115 @@ +package github.shardul.cats.models; + +import android.arch.persistence.room.ColumnInfo; +import android.arch.persistence.room.Entity; +import android.arch.persistence.room.PrimaryKey; +import android.os.Parcel; +import android.os.Parcelable; +import android.support.annotation.NonNull; + +/** + * Created by Shardul on 30/03/18. + */ +@Entity +public class Cat implements Parcelable { + + @PrimaryKey + @ColumnInfo(name = "_id") + private @NonNull String id; + + @ColumnInfo(name = "url") + private String url; + + @ColumnInfo(name = "source_url") + private String sourceUrl; + + public Cat(String id, String url, String sourceUrl) { + this.id = id; + this.url = url; + this.sourceUrl = sourceUrl; + } + + + Cat(Parcel in) { + id = in.readString(); + url = in.readString(); + sourceUrl = in.readString(); + } + + @Override + public String toString() { + return "Cat{" + + "id='" + id + '\'' + + ", url='" + url + '\'' + + ", sourceUrl='" + sourceUrl + '\'' + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Cat cat = (Cat) o; + + if (!id.equals(cat.id)) return false; + if (url != null ? !url.equals(cat.url) : cat.url != null) return false; + return sourceUrl != null ? sourceUrl.equals(cat.sourceUrl) : cat.sourceUrl == null; + } + + @Override + public int hashCode() { + int result = id.hashCode(); + result = 31 * result + (url != null ? url.hashCode() : 0); + result = 31 * result + (sourceUrl != null ? sourceUrl.hashCode() : 0); + return result; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getSourceUrl() { + return sourceUrl; + } + + public void setSourceUrl(String sourceUrl) { + this.sourceUrl = sourceUrl; + } + + public static final Creator CREATOR = new Creator() { + @Override + public Cat createFromParcel(Parcel in) { + return new Cat(in); + } + + @Override + public Cat[] newArray(int size) { + return new Cat[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(id); + dest.writeString(url); + dest.writeString(sourceUrl); + } +} diff --git a/app/src/main/java/github/shardul/cats/network/CatsResponse.java b/app/src/main/java/github/shardul/cats/network/CatsResponse.java new file mode 100644 index 0000000..8d8e2de --- /dev/null +++ b/app/src/main/java/github/shardul/cats/network/CatsResponse.java @@ -0,0 +1,40 @@ +package github.shardul.cats.network; + +import org.simpleframework.xml.Element; +import org.simpleframework.xml.ElementList; +import org.simpleframework.xml.Root; + +import java.util.List; + +/** + * Created by Shardul on 29/03/18. + */ + +@Root(name="Response", strict = false) +public class CatsResponse { + @Element + CatsData data; +} + +@Root(name = "data") +class CatsData { + @Element + Images images; +} + +@Root(name = "images") +class Images { + @ElementList(inline = true) + List imageItems; + +} + +@Root(name = "image") +class Image { + @Element(name = "url") + String url; + @Element(name = "id") + String id; + @Element(name = "source_url") + String sourceUrl; +} diff --git a/app/src/main/java/github/shardul/cats/network/CatsService.java b/app/src/main/java/github/shardul/cats/network/CatsService.java new file mode 100644 index 0000000..54919a6 --- /dev/null +++ b/app/src/main/java/github/shardul/cats/network/CatsService.java @@ -0,0 +1,15 @@ +package github.shardul.cats.network; + +import retrofit2.Call; +import retrofit2.http.GET; +import retrofit2.http.Query; + +/** + * Created by Shardul on 29/03/18. + */ + +public interface CatsService { + + @GET("api/images/get?format=xml&results_per_page=20") + Call getTwentyCatsOfCategory(@Query("category") String category); +} diff --git a/app/src/main/java/github/shardul/cats/network/FetchCatsTask.java b/app/src/main/java/github/shardul/cats/network/FetchCatsTask.java new file mode 100644 index 0000000..1a71101 --- /dev/null +++ b/app/src/main/java/github/shardul/cats/network/FetchCatsTask.java @@ -0,0 +1,60 @@ +package github.shardul.cats.network; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.List; + +import github.shardul.cats.BuildConfig; +import github.shardul.cats.models.Cat; +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + +/** + * Makes API call to cat api using CatService, and parses them to send it back to mCall + * Created by Shardul on 30/03/18. + */ +public class FetchCatsTask extends FetchTask> implements Callback { + + private CatsService mService; + + public FetchCatsTask(CatsService service, FetchListener> listener) { + mService = service; + mListener = new WeakReference>>(listener); + } + + @Override + public void run() { + Call catsCall = mService.getTwentyCatsOfCategory(BuildConfig.CAT_CATEGORY); + catsCall.enqueue(this); + + mCall = catsCall; + } + + @Override + public void onResponse(Call call, Response response) { + + if (response.isSuccessful()) { + if (response.body() != null) { + if (response.body().data.images.imageItems.isEmpty()) { + this.fetchFailed(new Exception("no cats live here, try dogs may be ?")); + } else { + ArrayList cats = new ArrayList(); + + for(Image imageItem: response.body().data.images.imageItems) { + cats.add(new Cat(imageItem.id, imageItem.url, imageItem.sourceUrl)); + } + + this.fetchComplete(cats); + } + } + } else { + this.fetchFailed(new Exception("we are not on earth anymore ?")); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + this.fetchFailed(new Exception(t.getMessage())); + } +} diff --git a/app/src/main/java/github/shardul/cats/network/FetchListener.java b/app/src/main/java/github/shardul/cats/network/FetchListener.java new file mode 100644 index 0000000..4bd9684 --- /dev/null +++ b/app/src/main/java/github/shardul/cats/network/FetchListener.java @@ -0,0 +1,11 @@ +package github.shardul.cats.network; + +/** + * Base fetch mListener implementation to use with fetch tasks + * Created by Shardul on 30/03/18. + */ +public interface FetchListener { + + public void onFetchComplete(T complete); + public void onFetchFailed(Exception e); +} diff --git a/app/src/main/java/github/shardul/cats/network/FetchTask.java b/app/src/main/java/github/shardul/cats/network/FetchTask.java new file mode 100644 index 0000000..43d3e08 --- /dev/null +++ b/app/src/main/java/github/shardul/cats/network/FetchTask.java @@ -0,0 +1,30 @@ +package github.shardul.cats.network; + +import java.lang.ref.WeakReference; + +import retrofit2.Call; + +/** + * Provides base implementation for fetch tasks + * Created by Shardul on 30/03/18. + */ +public abstract class FetchTask implements Runnable { + + protected WeakReference> mListener; + protected Call mCall; + + public void fetchComplete(T fetched) { + if (mListener.get() != null) { + mListener.get().onFetchComplete(fetched); + } + } + public void fetchFailed(Exception e) { + if (mListener != null) { + mListener.get().onFetchFailed(e); + } + } + + public void cancel() { + mCall.cancel(); + } +} diff --git a/app/src/main/java/github/shardul/cats/ui/cats/CatViewHolder.java b/app/src/main/java/github/shardul/cats/ui/cats/CatViewHolder.java new file mode 100644 index 0000000..a0be293 --- /dev/null +++ b/app/src/main/java/github/shardul/cats/ui/cats/CatViewHolder.java @@ -0,0 +1,24 @@ +package github.shardul.cats.ui.cats; + +import android.support.v7.widget.RecyclerView; + +import github.shardul.cats.databinding.CatsItemBinding; +import github.shardul.cats.models.Cat; + +/** + * Created by Shardul on 02/04/18. + */ + +public class CatViewHolder extends RecyclerView.ViewHolder { + + CatsItemBinding mBinding; + + public CatViewHolder(CatsItemBinding binding) { + super(binding.getRoot()); + mBinding = binding; + } + + public void setCat(Cat cat) { + mBinding.setCat(cat); + } +} diff --git a/app/src/main/java/github/shardul/cats/ui/cats/CatsAdapter.java b/app/src/main/java/github/shardul/cats/ui/cats/CatsAdapter.java new file mode 100644 index 0000000..bea4674 --- /dev/null +++ b/app/src/main/java/github/shardul/cats/ui/cats/CatsAdapter.java @@ -0,0 +1,57 @@ +package github.shardul.cats.ui.cats; + +import android.content.Context; +import android.databinding.DataBindingUtil; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import java.util.List; + +import github.shardul.cats.R; +import github.shardul.cats.binding.FragmentBindingComponent; +import github.shardul.cats.databinding.CatsItemBinding; +import github.shardul.cats.models.Cat; + +/** + * Created by Shardul on 02/04/18. + */ + +public class CatsAdapter extends RecyclerView.Adapter { + + private List mCats; + private FragmentBindingComponent mBindingComponent; + + public CatsAdapter(FragmentBindingComponent imageBindingComponent) { + + mBindingComponent = imageBindingComponent; + } + + @Override + public CatViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + LayoutInflater inflater = (LayoutInflater) parent.getContext() + .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + + CatsItemBinding binding = DataBindingUtil.inflate(inflater, R.layout.cats_item, parent,false, + mBindingComponent); + + CatViewHolder holder = new CatViewHolder(binding); + return holder; + } + + @Override + public void onBindViewHolder(CatViewHolder holder, int position) { + Cat cat = mCats.get(position); + holder.setCat(cat); + } + + @Override + public int getItemCount() { + return mCats == null ? 0 : mCats.size(); + } + + public void setCats(List cats) { + mCats = cats; + notifyDataSetChanged(); + } +} diff --git a/app/src/main/java/github/shardul/cats/ui/cats/CatsComponent.java b/app/src/main/java/github/shardul/cats/ui/cats/CatsComponent.java new file mode 100644 index 0000000..1e05880 --- /dev/null +++ b/app/src/main/java/github/shardul/cats/ui/cats/CatsComponent.java @@ -0,0 +1,14 @@ +package github.shardul.cats.ui.cats; + +import dagger.Component; +import github.shardul.cats.di.CatsAppComponent; + +/** + * Created by Shardul on 31/03/18. + */ +@CatsFragmentScope +@Component(dependencies = {CatsAppComponent.class}) +public interface CatsComponent { + + public void inject(CatsFragment fragment); +} diff --git a/app/src/main/java/github/shardul/cats/ui/cats/CatsFragment.java b/app/src/main/java/github/shardul/cats/ui/cats/CatsFragment.java new file mode 100644 index 0000000..577fb53 --- /dev/null +++ b/app/src/main/java/github/shardul/cats/ui/cats/CatsFragment.java @@ -0,0 +1,110 @@ +package github.shardul.cats.ui.cats; + +import android.arch.lifecycle.Observer; +import android.arch.lifecycle.ViewModelProviders; +import android.databinding.DataBindingUtil; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.design.widget.Snackbar; +import android.support.v4.app.Fragment; +import android.support.v7.widget.GridLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import java.util.List; + +import github.shardul.cats.R; +import github.shardul.cats.binding.FragmentBindingComponent; +import github.shardul.cats.databinding.CatsFragmentBinding; +import github.shardul.cats.models.Cat; +import github.shardul.cats.util.AutoClearedValue; + +/** + * Created by Shardul on 31/03/18. + */ + +public class CatsFragment extends Fragment { + + private final static int MAX_COLUMNS = 2; + + private AutoClearedValue mBinding; + private AutoClearedValue mAdapter; + private CatsViewModel mCatsViewModel; + + private List mCats; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mCatsViewModel = ViewModelProviders.of(this).get(CatsViewModel.class); + } + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + CatsFragmentBinding binding = DataBindingUtil.inflate(inflater, R.layout.cats_fragment, + container,false); + binding.setFragment(this); + + mBinding = new AutoClearedValue(this, binding); + mAdapter = new AutoClearedValue(this, new CatsAdapter( + new FragmentBindingComponent(this))); + + RecyclerView recyclerView = mBinding.getValue().catsFragmentRecyclerView; + recyclerView.setHasFixedSize(true); + + GridLayoutManager layoutManager = new GridLayoutManager(getContext(), MAX_COLUMNS); + recyclerView.setLayoutManager(layoutManager); + + recyclerView.setAdapter(mAdapter.getValue()); + + return binding.getRoot(); + } + + @Override + public void onActivityCreated(@Nullable Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + subscribe(); + } + + public void refreshClicked(){ + mCatsViewModel.fetchNewCats(); + } + + private void subscribe() { + + mCatsViewModel.getCats().observe(this, new Observer>() { + @Override + public void onChanged(@Nullable List cats) { + if (cats == null || cats.isEmpty()) { + loading(true); + } else { + mAdapter.getValue().setCats(cats); + loading(false); + } + + mCats = cats; + } + }); + } + + private void loading(boolean isLoading) { + if(isLoading) { + + if (mCats != null) { + Snackbar.make(mBinding.getValue().catsFragmentRefresh, + R.string.cats_fragment_loading_message, Snackbar.LENGTH_SHORT).show(); + } + + mBinding.getValue().catsFragmentProgress.setVisibility(View.VISIBLE); + mBinding.getValue().catsFragmentRefresh.setVisibility(View.GONE); + } else { + mBinding.getValue().catsFragmentProgress.setVisibility(View.GONE); + mBinding.getValue().catsFragmentRefresh.setVisibility(View.VISIBLE); + } + } +} diff --git a/app/src/main/java/github/shardul/cats/ui/cats/CatsFragmentScope.java b/app/src/main/java/github/shardul/cats/ui/cats/CatsFragmentScope.java new file mode 100644 index 0000000..40dd23a --- /dev/null +++ b/app/src/main/java/github/shardul/cats/ui/cats/CatsFragmentScope.java @@ -0,0 +1,11 @@ +package github.shardul.cats.ui.cats; + +import javax.inject.Scope; + +/** + * Created by Shardul on 31/03/18. + */ + +@Scope +public @interface CatsFragmentScope { +} diff --git a/app/src/main/java/github/shardul/cats/ui/cats/CatsViewModel.java b/app/src/main/java/github/shardul/cats/ui/cats/CatsViewModel.java new file mode 100644 index 0000000..7e26458 --- /dev/null +++ b/app/src/main/java/github/shardul/cats/ui/cats/CatsViewModel.java @@ -0,0 +1,45 @@ +package github.shardul.cats.ui.cats; + + +import android.app.Application; +import android.arch.lifecycle.AndroidViewModel; +import android.arch.lifecycle.LiveData; +import android.arch.lifecycle.MediatorLiveData; +import android.support.annotation.NonNull; + +import java.util.List; + +import github.shardul.cats.CatsApplication; +import github.shardul.cats.CatsRepository; +import github.shardul.cats.models.Cat; + +/** + * Created by Shardul on 31/03/18. + */ + +public class CatsViewModel extends AndroidViewModel { + + private final MediatorLiveData> mObservableCats; + private final CatsRepository catsRepository; + + public CatsViewModel(@NonNull Application application) { + super(application); + + mObservableCats = new MediatorLiveData<>(); + mObservableCats.setValue(null); + + catsRepository = ((CatsApplication) application).getRepository(); + LiveData> cats = catsRepository.getObservableCats(); + + mObservableCats.addSource(cats, mObservableCats::setValue); + } + + public LiveData> getCats() { + return mObservableCats; + } + + public void fetchNewCats() { + catsRepository.fetchNewCats(); + } + +} diff --git a/app/src/main/java/github/shardul/cats/util/AutoClearedValue.java b/app/src/main/java/github/shardul/cats/util/AutoClearedValue.java new file mode 100644 index 0000000..b775997 --- /dev/null +++ b/app/src/main/java/github/shardul/cats/util/AutoClearedValue.java @@ -0,0 +1,36 @@ +package github.shardul.cats.util; + +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; + +/** + * Registers with fragment lifecycle callbacks to release resources when fragment is destroyed. + * Created by Shardul on 02/04/18. + */ +public class AutoClearedValue { + + T mValue; + + public AutoClearedValue(Fragment fragment, T value) { + final FragmentManager fragmentManager = fragment.getFragmentManager(); + + fragmentManager.registerFragmentLifecycleCallbacks(new FragmentManager.FragmentLifecycleCallbacks() { + @Override + public void onFragmentViewDestroyed(FragmentManager fm, Fragment f) { + if (f == fragment) { + AutoClearedValue.this.mValue = null; + fragmentManager.unregisterFragmentLifecycleCallbacks(this); + } else { + super.onFragmentViewDestroyed(fm, f); + } + } + }, false); + + mValue = value; + + } + + public T getValue() { + return mValue; + } +} diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..c7bd21d --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..d5fccc5 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_refresh_white_24px.xml b/app/src/main/res/drawable/ic_refresh_white_24px.xml new file mode 100644 index 0000000..25451e4 --- /dev/null +++ b/app/src/main/res/drawable/ic_refresh_white_24px.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..b5a1ea9 --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,21 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/cats_fragment.xml b/app/src/main/res/layout/cats_fragment.xml new file mode 100644 index 0000000..73cf965 --- /dev/null +++ b/app/src/main/res/layout/cats_fragment.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/cats_item.xml b/app/src/main/res/layout/cats_item.xml new file mode 100644 index 0000000..c1627f5 --- /dev/null +++ b/app/src/main/res/layout/cats_item.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..036d09b --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..036d09b --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..cab66ff Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..9cc3809 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000..c40e224 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..74a7697 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..f250001 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000..334d5e5 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..14573e9 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..ceb53f9 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000..19d774b Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..ef90835 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..5a75e47 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..0a1400a Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..75ee4fd Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..bd28c72 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..85e6557 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..3f365ab --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,9 @@ + + + #BBDEFB + #2196F3 + #1976D2 + #EA80FC + #ff4081 + #D500F9 + diff --git a/app/src/main/res/values/ic_launcher_background.xml b/app/src/main/res/values/ic_launcher_background.xml new file mode 100644 index 0000000..2740bca --- /dev/null +++ b/app/src/main/res/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + @color/colorPrimary + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..fec2871 --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,4 @@ + + Cats + Finding world\'s finest cats for you! + diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..b4390a2 --- /dev/null +++ b/app/src/main/res/values/styles.xml @@ -0,0 +1,9 @@ + + + + + diff --git a/app/src/test/java/github/shardul/cats/ExampleUnitTest.java b/app/src/test/java/github/shardul/cats/ExampleUnitTest.java new file mode 100644 index 0000000..a50d20b --- /dev/null +++ b/app/src/test/java/github/shardul/cats/ExampleUnitTest.java @@ -0,0 +1,17 @@ +package github.shardul.cats; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Example local unit test, which will execute on the development machine (host). + * + * @see Testing documentation + */ +public class ExampleUnitTest { + @Test + public void addition_isCorrect() throws Exception { + assertEquals(4, 2 + 2); + } +} \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..b281080 --- /dev/null +++ b/build.gradle @@ -0,0 +1,44 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + + repositories { + google() + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:3.1.0' + + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + google() + jcenter() + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} + +ext.support_version = "27.1.0" +ext.constraint_layout_version = "1.0.2" +ext.retrofit_version = "2.3.0" +ext.okhttp_version = "3.4.1" +ext.google_gson_version = "2.8.1" +ext.glide_version = "3.8.0" +ext.simple_xml_version = "2.0.0-beta3" +ext.dagger_version = "2.11" +ext.timber_version = "4.5.1" + +ext.arch_version = "1.1.1" +ext.room_version = "1.0.0" + +ext.junit_version = "4.12" +ext.runner_version = "1.0.1" +ext.espresso_version = "3.0.1" \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..aac7c9b --- /dev/null +++ b/gradle.properties @@ -0,0 +1,17 @@ +# Project-wide Gradle settings. + +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. + +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html + +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx1536m + +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..13372ae Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..306d92c --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon Apr 02 22:21:59 IST 2018 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..9d82f78 --- /dev/null +++ b/gradlew @@ -0,0 +1,160 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..aec9973 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..e7b4def --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +include ':app'