├── .gitignore
├── .travis.yml
├── README.md
├── app
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── news
│ │ └── agoda
│ │ └── com
│ │ └── sample
│ │ └── ApplicationTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── news
│ │ │ └── agoda
│ │ │ └── com
│ │ │ └── sample
│ │ │ ├── NewsApp.java
│ │ │ ├── api
│ │ │ ├── ApiConstants.java
│ │ │ ├── ApiEndPoint.java
│ │ │ ├── NewsApiClient.java
│ │ │ ├── NewsViewModelFactory.java
│ │ │ ├── RxSingleSchedulers.java
│ │ │ └── model
│ │ │ │ ├── MediaEntity.java
│ │ │ │ ├── NewsEntity.java
│ │ │ │ └── NewsList.java
│ │ │ ├── base
│ │ │ ├── BaseActivity.java
│ │ │ └── BaseViewState.java
│ │ │ ├── di
│ │ │ ├── component
│ │ │ │ └── ApplicationComponent.java
│ │ │ ├── module
│ │ │ │ ├── ActivityBindingModule.java
│ │ │ │ ├── ApiModule.java
│ │ │ │ ├── ApplicationModule.java
│ │ │ │ ├── RxModule.java
│ │ │ │ └── ViewModelModule.java
│ │ │ └── scope
│ │ │ │ ├── AppScope.java
│ │ │ │ └── ViewModelKey.java
│ │ │ ├── ui
│ │ │ ├── DetailViewActivity.java
│ │ │ ├── MainActivity.java
│ │ │ ├── NewsAdapter.java
│ │ │ ├── callback
│ │ │ │ └── IItemClick.java
│ │ │ └── viewmodel
│ │ │ │ ├── NewsListViewState.java
│ │ │ │ └── NewsViewModel.java
│ │ │ └── util
│ │ │ ├── AppConstants.java
│ │ │ ├── MediaDeserializer.java
│ │ │ └── Utilities.java
│ └── res
│ │ ├── drawable
│ │ └── place_holder.png
│ │ ├── layout
│ │ ├── activity_detail.xml
│ │ ├── activity_main.xml
│ │ └── list_item_news.xml
│ │ ├── menu
│ │ └── menu_main.xml
│ │ ├── mipmap-hdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-mdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xhdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xxhdpi
│ │ └── ic_launcher.png
│ │ ├── values-sw600dp
│ │ └── dimens.xml
│ │ ├── values-w820dp
│ │ └── dimens.xml
│ │ └── values
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── news
│ └── agoda
│ └── com
│ └── sample
│ ├── APIAvailabilityTest.java
│ ├── api
│ └── model
│ │ ├── MediaEntityTest.java
│ │ └── NewsEntityTest.java
│ └── ui
│ └── viewmodel
│ └── NewsViewModelTest.java
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle
2 | /local.properties
3 | /.idea
4 | .DS_Store
5 | /build
6 | /captures
7 | **/*.iml
8 | **/build
9 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: android
2 | jdk:
3 | - oraclejdk7
4 |
5 | android:
6 | components:
7 | - tools
8 | - platform-tools
9 | - build-tools-23.0.3
10 | - extra-android-m2repository
11 | - android-23
12 |
13 | script: ./gradlew clean :app:testDebugUnitTest
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | This is a sample News Reader app that is supposed to display news list and the details.
2 | The first page displays news list, when one of the items is clicked, it is supposed to show the detail of the selected news.
3 | Unfortunately, the app is full of bugs and it crashes as soon as it is launched.
4 | Also, the code is not properly written and there are no unit tests.
5 | Can you help to fix all the problems?
6 |
7 | ## Before you start
8 | This project requires the following
9 |
10 | 1. Android Studio 2.1
11 | 2. Android SDK 23 or above.
12 | 3. Android SDK build tools 23.0.3 or above.
13 |
14 | ## Screenshots
15 | The screenshot below shows how the app looks like when it is done.
16 |
17 | 
18 | 
19 |
20 | ## About the project
21 | All the data is coming from the web endpoint.
22 | The response contains a list of news items as well as URLs to the pictures associated with each story.
23 |
24 | ## Fix crashes in News List page
25 | Can you help fix all bugs so that it can display news list properly?
26 |
27 | ## Fix crashes in News Detail page
28 | Can you help fix all bugs so that the app can show news detail properly? Also, clicking on "Full Story" button, it should open a browser and display full story in the browser.
29 |
30 | ## Basic unit test
31 | Can you help to write unit tests for MediaEntity and NewsEntity?
32 |
33 | ## Improvements
34 | 1. The main logic is written in MainActivity, which is not a very clean way to construct an app. Can you help to improve it?
35 |
36 | 2. The way of fetching and parsing JSON data is not very nice. For example, if one of the name/value is missing, it
37 | can cause the app to crash.
38 |
39 | 3. The layout is only suitable for phones. Can you create an immersive tablet experience?
40 |
41 | Can you help to make it better?
42 |
43 | ## Notes
44 | 1. It is possible that some of the stories do not have images.
45 | 2. It is possible that the link to the full story might not work as it is controlled by New York Times.
46 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 28
5 |
6 | defaultConfig {
7 | applicationId "news.agoda.com.technewssample"
8 | minSdkVersion 21
9 | targetSdkVersion 28
10 | versionCode 1
11 | versionName "1.0"
12 | }
13 | compileOptions {
14 | sourceCompatibility JavaVersion.VERSION_1_8
15 | targetCompatibility JavaVersion.VERSION_1_8
16 | }
17 | dataBinding {
18 | enabled = true
19 | }
20 | testOptions {
21 | unitTests.returnDefaultValues = true
22 | }
23 |
24 | buildTypes {
25 | release {
26 | minifyEnabled false
27 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
28 | }
29 | }
30 | }
31 |
32 | def supportVersion = '28.0.0'
33 | def retrofitVersion = '2.3.0'
34 | def rxJavaVersion = '2.1.0'
35 | def daggerVersion = '2.19'
36 | def mockitoVersion = '2.19.0'
37 | def gsonVersion = '2.6.2'
38 | def frescoVersion = '1.11.0'
39 | def hamcrestVersion = '2.1.0'
40 |
41 | dependencies {
42 | implementation fileTree(dir: 'libs', include: ['*.jar'])
43 | implementation "com.android.support:appcompat-v7:$supportVersion"
44 | implementation "com.android.support:support-compat:$supportVersion"
45 | implementation "com.android.support:support-v4:$supportVersion"
46 | implementation "com.android.support:design:$supportVersion"
47 | implementation "com.android.support:support-core-utils:$supportVersion"
48 | implementation "com.android.support:support-core-ui:$supportVersion"
49 | implementation "com.android.support:support-fragment:$supportVersion"
50 | implementation "com.android.support:support-v13:$supportVersion"
51 | implementation "com.android.support:cardview-v7:$supportVersion"
52 | implementation "com.android.support:recyclerview-v7:$supportVersion"
53 | implementation "com.android.support:support-annotations:$supportVersion"
54 |
55 | implementation "android.arch.lifecycle:extensions:1.1.1"
56 | annotationProcessor "android.arch.lifecycle:compiler:1.1.1"
57 | testImplementation "android.arch.core:core-testing:1.1.1"
58 |
59 | implementation "com.squareup.retrofit2:retrofit:$retrofitVersion"
60 | implementation "com.squareup.retrofit2:converter-gson:$retrofitVersion"
61 | implementation "com.squareup.retrofit2:adapter-rxjava2:$retrofitVersion"
62 |
63 | implementation "io.reactivex.rxjava2:rxandroid:$rxJavaVersion"
64 | implementation "io.reactivex.rxjava2:rxjava:$rxJavaVersion"
65 |
66 | implementation "com.google.dagger:dagger:$daggerVersion"
67 | annotationProcessor "com.google.dagger:dagger-compiler:$daggerVersion"
68 | implementation "com.google.dagger:dagger-android-support:$daggerVersion"
69 | annotationProcessor "com.google.dagger:dagger-android-processor:$daggerVersion"
70 |
71 | implementation "com.google.code.gson:gson:$gsonVersion"
72 | implementation "com.facebook.fresco:fresco:$frescoVersion"
73 |
74 | testImplementation 'junit:junit:4.12'
75 | testImplementation "org.mockito:mockito-core:$mockitoVersion"
76 | testImplementation 'org.hamcrest:hamcrest-library:1.3'
77 | androidTestImplementation 'com.android.support.test:runner:1.0.2'
78 | }
79 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Volumes/Data/adt-mac/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/news/agoda/com/sample/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package news.agoda.com.sample;
2 |
3 | import android.app.Application;
4 | import android.test.ApplicationTestCase;
5 |
6 | /**
7 | * Testing Fundamentals
8 | */
9 | public class ApplicationTest extends ApplicationTestCase {
10 | public ApplicationTest() {
11 | super(Application.class);
12 | }
13 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
14 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/app/src/main/java/news/agoda/com/sample/NewsApp.java:
--------------------------------------------------------------------------------
1 | package news.agoda.com.sample;
2 |
3 | import android.app.Activity;
4 | import android.app.Application;
5 |
6 | import javax.inject.Inject;
7 |
8 | import dagger.android.AndroidInjector;
9 | import dagger.android.DispatchingAndroidInjector;
10 | import dagger.android.HasActivityInjector;
11 | import news.agoda.com.sample.di.component.DaggerApplicationComponent;
12 |
13 | public class NewsApp extends Application implements HasActivityInjector {
14 |
15 | @Inject
16 | DispatchingAndroidInjector activityDispatchingAndroidInjector;
17 |
18 | @Override
19 | public void onCreate() {
20 | super.onCreate();
21 | DaggerApplicationComponent
22 | .builder()
23 | .application(this)
24 | .build()
25 | .inject(this);
26 | }
27 |
28 | @Override
29 | public AndroidInjector activityInjector() {
30 | return activityDispatchingAndroidInjector;
31 | }
32 | }
--------------------------------------------------------------------------------
/app/src/main/java/news/agoda/com/sample/api/ApiConstants.java:
--------------------------------------------------------------------------------
1 | package news.agoda.com.sample.api;
2 |
3 |
4 | public class ApiConstants {
5 |
6 |
7 | public static final String BASE_URL = "https://api.myjson.com/";
8 | public static final String NEWS_URL = "bins/nl6jh";
9 |
10 | }
11 |
--------------------------------------------------------------------------------
/app/src/main/java/news/agoda/com/sample/api/ApiEndPoint.java:
--------------------------------------------------------------------------------
1 | package news.agoda.com.sample.api;
2 |
3 | import io.reactivex.Single;
4 | import news.agoda.com.sample.api.model.NewsList;
5 | import retrofit2.http.GET;
6 |
7 | public interface ApiEndPoint {
8 |
9 | @GET(ApiConstants.NEWS_URL)
10 | Single fetchNewsList();
11 | }
12 |
--------------------------------------------------------------------------------
/app/src/main/java/news/agoda/com/sample/api/NewsApiClient.java:
--------------------------------------------------------------------------------
1 | package news.agoda.com.sample.api;
2 |
3 |
4 | import javax.inject.Inject;
5 |
6 | import io.reactivex.Single;
7 | import news.agoda.com.sample.api.model.NewsList;
8 |
9 | public class NewsApiClient {
10 |
11 | private final ApiEndPoint api;
12 |
13 | @Inject
14 | public NewsApiClient(ApiEndPoint api) {
15 | this.api = api;
16 | }
17 |
18 | public Single fetchNews() {
19 | return api.fetchNewsList();
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/app/src/main/java/news/agoda/com/sample/api/NewsViewModelFactory.java:
--------------------------------------------------------------------------------
1 | package news.agoda.com.sample.api;
2 |
3 | import android.arch.lifecycle.ViewModel;
4 | import android.arch.lifecycle.ViewModelProvider;
5 |
6 | import java.util.Map;
7 |
8 | import javax.inject.Inject;
9 | import javax.inject.Provider;
10 |
11 | import news.agoda.com.sample.di.scope.AppScope;
12 |
13 | @AppScope
14 | public class NewsViewModelFactory implements ViewModelProvider.Factory {
15 | private final Map, Provider> creators;
16 |
17 | @Inject
18 | NewsViewModelFactory(final Map, Provider> creators) {
19 | this.creators = creators;
20 | }
21 |
22 | @SuppressWarnings("unchecked")
23 | @Override
24 | public T create(final Class modelClass) {
25 | Provider extends ViewModel> creator = creators.get(modelClass);
26 | if (creator == null) {
27 | for (final Map.Entry, Provider> entry : creators.entrySet()) {
28 | if (modelClass.isAssignableFrom(entry.getKey())) {
29 | creator = entry.getValue();
30 | break;
31 | }
32 | }
33 | }
34 | if (creator == null) {
35 | throw new IllegalArgumentException("unknown model class " + modelClass);
36 | }
37 | try {
38 | return (T) creator.get();
39 | } catch (final Exception e) {
40 | throw new RuntimeException(e);
41 | }
42 | }
43 | }
--------------------------------------------------------------------------------
/app/src/main/java/news/agoda/com/sample/api/RxSingleSchedulers.java:
--------------------------------------------------------------------------------
1 | package news.agoda.com.sample.api;
2 |
3 | import io.reactivex.SingleTransformer;
4 | import io.reactivex.android.schedulers.AndroidSchedulers;
5 | import io.reactivex.schedulers.Schedulers;
6 |
7 | public interface RxSingleSchedulers {
8 | RxSingleSchedulers DEFAULT = new RxSingleSchedulers() {
9 | @Override
10 | public SingleTransformer applySchedulers() {
11 | return single -> single
12 | .subscribeOn(Schedulers.io())
13 | .observeOn(AndroidSchedulers.mainThread());
14 | }
15 | };
16 |
17 | RxSingleSchedulers TEST_SCHEDULER = new RxSingleSchedulers() {
18 | @Override
19 | public SingleTransformer applySchedulers() {
20 | return single -> single
21 | .subscribeOn(Schedulers.trampoline())
22 | .observeOn(Schedulers.trampoline());
23 | }
24 | };
25 |
26 | SingleTransformer applySchedulers();
27 | }
28 |
--------------------------------------------------------------------------------
/app/src/main/java/news/agoda/com/sample/api/model/MediaEntity.java:
--------------------------------------------------------------------------------
1 | package news.agoda.com.sample.api.model;
2 |
3 | import com.google.gson.annotations.SerializedName;
4 |
5 | /**
6 | * This class represents a media item
7 | */
8 | public class MediaEntity {
9 |
10 | public MediaEntity(String url, String format, int height, int width, String type, String subType, String caption, String copyright) {
11 | this.url = url;
12 | this.format = format;
13 | this.height = height;
14 | this.width = width;
15 | this.type = type;
16 | this.subType = subType;
17 | this.caption = caption;
18 | this.copyright = copyright;
19 | }
20 |
21 | @SerializedName("url")
22 | private String url;
23 |
24 | @SerializedName("format")
25 | private String format;
26 |
27 | @SerializedName("height")
28 | private int height;
29 |
30 | @SerializedName("width")
31 | private int width;
32 |
33 | @SerializedName("type")
34 | private String type;
35 |
36 | @SerializedName("subtype")
37 | private String subType;
38 |
39 | @SerializedName("caption")
40 | private String caption;
41 |
42 | @SerializedName("copyright")
43 | private String copyright;
44 |
45 | public String getUrl() {
46 | return url;
47 | }
48 |
49 | public String getFormat() {
50 | return format;
51 | }
52 |
53 | public int getHeight() {
54 | return height;
55 | }
56 |
57 | public int getWidth() {
58 | return width;
59 | }
60 |
61 | public String getType() {
62 | return type;
63 | }
64 |
65 | public String getSubType() {
66 | return subType;
67 | }
68 |
69 | public String getCaption() {
70 | return caption;
71 | }
72 |
73 | public String getCopyright() {
74 | return copyright;
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/app/src/main/java/news/agoda/com/sample/api/model/NewsEntity.java:
--------------------------------------------------------------------------------
1 | package news.agoda.com.sample.api.model;
2 |
3 | import com.google.gson.annotations.SerializedName;
4 |
5 | import java.util.List;
6 |
7 | /**
8 | * This represents a news item
9 | */
10 | public class NewsEntity {
11 |
12 |
13 | @SerializedName("title")
14 | private String title;
15 |
16 | @SerializedName("abstract")
17 | private String summary;
18 |
19 | @SerializedName("url")
20 | private String articleUrl;
21 |
22 | @SerializedName("byline")
23 | private String byline;
24 |
25 | @SerializedName("published_date")
26 | private String publishedDate;
27 |
28 | @SerializedName("multimedia")
29 | private List mediaEntityList;
30 |
31 | public String getTitle() {
32 | return title;
33 | }
34 |
35 | public String getSummary() {
36 | return summary;
37 | }
38 |
39 | public String getArticleUrl() {
40 | return articleUrl;
41 | }
42 |
43 | public String getByline() {
44 | return byline;
45 | }
46 |
47 | public String getPublishedDate() {
48 | return publishedDate;
49 | }
50 |
51 | public List getMediaEntityList() {
52 | return mediaEntityList;
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/app/src/main/java/news/agoda/com/sample/api/model/NewsList.java:
--------------------------------------------------------------------------------
1 | package news.agoda.com.sample.api.model;
2 |
3 | import com.google.gson.annotations.SerializedName;
4 |
5 | import java.util.List;
6 |
7 | public class NewsList {
8 |
9 | @SerializedName("section")
10 | private String section;
11 |
12 | @SerializedName("num_results")
13 | private String totalResults;
14 |
15 | @SerializedName("results")
16 | private List results;
17 |
18 |
19 | public String getSection() {
20 | return section;
21 | }
22 |
23 | public String getTotalResults() {
24 | return totalResults;
25 | }
26 |
27 | public List getResults() {
28 | return results;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/app/src/main/java/news/agoda/com/sample/base/BaseActivity.java:
--------------------------------------------------------------------------------
1 | package news.agoda.com.sample.base;
2 |
3 | import android.databinding.DataBindingUtil;
4 | import android.databinding.ViewDataBinding;
5 | import android.os.Bundle;
6 | import android.support.annotation.Nullable;
7 | import android.support.v7.app.AppCompatActivity;
8 | import android.widget.Toast;
9 |
10 | import dagger.android.AndroidInjection;
11 |
12 | public abstract class BaseActivity extends AppCompatActivity {
13 |
14 | protected B binding;
15 |
16 | @Override
17 | protected void onCreate(@Nullable Bundle savedInstanceState) {
18 | AndroidInjection.inject(this);
19 | binding = DataBindingUtil.setContentView(this, getLayout());
20 | super.onCreate(savedInstanceState);
21 | }
22 |
23 | public void showToast(String message) {
24 | Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
25 | }
26 |
27 | public abstract int getLayout();
28 | }
29 |
--------------------------------------------------------------------------------
/app/src/main/java/news/agoda/com/sample/base/BaseViewState.java:
--------------------------------------------------------------------------------
1 | package news.agoda.com.sample.base;
2 |
3 | public class BaseViewState {
4 | public T getData() {
5 | return data;
6 | }
7 |
8 | public void setData(T data) {
9 | this.data = data;
10 | }
11 |
12 | public Throwable getError() {
13 | return error;
14 | }
15 |
16 | public void setError(Throwable error) {
17 | this.error = error;
18 | }
19 |
20 | public int getCurrentState() {
21 | return currentState;
22 | }
23 |
24 | public void setCurrentState(int currentState) {
25 | this.currentState = currentState;
26 | }
27 |
28 | protected T data;
29 | protected Throwable error;
30 | protected int currentState;
31 |
32 | public enum State{
33 | LOADING(0), SUCCESS(1),FAILED(-1);
34 | public int value;
35 | State(int val) {
36 | value = val;
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/app/src/main/java/news/agoda/com/sample/di/component/ApplicationComponent.java:
--------------------------------------------------------------------------------
1 | package news.agoda.com.sample.di.component;
2 |
3 | import dagger.BindsInstance;
4 | import dagger.Component;
5 | import dagger.android.AndroidInjector;
6 | import dagger.android.support.AndroidSupportInjectionModule;
7 | import news.agoda.com.sample.NewsApp;
8 | import news.agoda.com.sample.di.module.ActivityBindingModule;
9 | import news.agoda.com.sample.di.module.ApiModule;
10 | import news.agoda.com.sample.di.module.ApplicationModule;
11 | import news.agoda.com.sample.di.module.RxModule;
12 | import news.agoda.com.sample.di.scope.AppScope;
13 |
14 | @AppScope
15 | @Component(modules = {ApplicationModule.class,
16 | AndroidSupportInjectionModule.class,
17 | ActivityBindingModule.class,
18 | ApiModule.class, RxModule.class})
19 | public interface ApplicationComponent extends AndroidInjector {
20 |
21 | void inject(NewsApp application);
22 |
23 | @Component.Builder
24 | interface Builder {
25 | @BindsInstance
26 | Builder application(NewsApp application);
27 | ApplicationComponent build();
28 | }
29 | }
--------------------------------------------------------------------------------
/app/src/main/java/news/agoda/com/sample/di/module/ActivityBindingModule.java:
--------------------------------------------------------------------------------
1 | package news.agoda.com.sample.di.module;
2 |
3 |
4 | import dagger.Module;
5 | import dagger.android.ContributesAndroidInjector;
6 | import news.agoda.com.sample.ui.DetailViewActivity;
7 | import news.agoda.com.sample.ui.MainActivity;
8 |
9 | @Module
10 | public abstract class ActivityBindingModule {
11 |
12 | @ContributesAndroidInjector()
13 | abstract MainActivity bindMainActivity();
14 |
15 | @ContributesAndroidInjector()
16 | abstract DetailViewActivity bindDetailActivity();
17 | }
18 |
--------------------------------------------------------------------------------
/app/src/main/java/news/agoda/com/sample/di/module/ApiModule.java:
--------------------------------------------------------------------------------
1 | package news.agoda.com.sample.di.module;
2 |
3 | import com.google.gson.Gson;
4 | import com.google.gson.GsonBuilder;
5 | import com.google.gson.reflect.TypeToken;
6 |
7 | import java.util.List;
8 |
9 | import dagger.Module;
10 | import dagger.Provides;
11 | import news.agoda.com.sample.api.ApiConstants;
12 | import news.agoda.com.sample.api.ApiEndPoint;
13 | import news.agoda.com.sample.api.model.MediaEntity;
14 | import news.agoda.com.sample.di.scope.AppScope;
15 | import news.agoda.com.sample.util.MediaDeserializer;
16 | import retrofit2.Retrofit;
17 | import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
18 | import retrofit2.converter.gson.GsonConverterFactory;
19 |
20 | @Module
21 | public class ApiModule {
22 |
23 | @AppScope
24 | @Provides
25 | Retrofit provideRetrofit(Gson gson) {
26 | return new Retrofit.Builder().baseUrl(ApiConstants.BASE_URL)
27 | .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
28 | .addConverterFactory(GsonConverterFactory.create(gson))
29 | .build();
30 | }
31 |
32 | @AppScope
33 | @Provides
34 | Gson provideGson() {
35 | return new GsonBuilder().registerTypeAdapter(new TypeToken>(){}.getType(), new MediaDeserializer()).create();
36 | }
37 |
38 |
39 | @AppScope
40 | @Provides
41 | ApiEndPoint provideNewsApi(Retrofit retrofit) {
42 | return retrofit.create(ApiEndPoint.class);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/app/src/main/java/news/agoda/com/sample/di/module/ApplicationModule.java:
--------------------------------------------------------------------------------
1 | package news.agoda.com.sample.di.module;
2 |
3 | import android.app.Application;
4 | import android.content.Context;
5 |
6 | import dagger.Binds;
7 | import dagger.Module;
8 |
9 |
10 | @Module(includes = ViewModelModule.class)
11 | abstract public class ApplicationModule {
12 |
13 | @Binds
14 | abstract Context provideContext(Application application);
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/app/src/main/java/news/agoda/com/sample/di/module/RxModule.java:
--------------------------------------------------------------------------------
1 | package news.agoda.com.sample.di.module;
2 |
3 | import dagger.Module;
4 | import dagger.Provides;
5 | import news.agoda.com.sample.api.RxSingleSchedulers;
6 | import news.agoda.com.sample.di.scope.AppScope;
7 |
8 | @Module
9 | public class RxModule {
10 | @AppScope
11 | @Provides
12 | public RxSingleSchedulers providesScheduler() {
13 | return RxSingleSchedulers.DEFAULT;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/app/src/main/java/news/agoda/com/sample/di/module/ViewModelModule.java:
--------------------------------------------------------------------------------
1 | package news.agoda.com.sample.di.module;
2 |
3 | import android.arch.lifecycle.ViewModel;
4 | import android.arch.lifecycle.ViewModelProvider;
5 |
6 | import dagger.Binds;
7 | import dagger.Module;
8 | import dagger.multibindings.IntoMap;
9 | import news.agoda.com.sample.api.NewsViewModelFactory;
10 | import news.agoda.com.sample.di.scope.ViewModelKey;
11 | import news.agoda.com.sample.ui.viewmodel.NewsViewModel;
12 |
13 |
14 | @Module
15 | public abstract class ViewModelModule {
16 |
17 | @Binds
18 | @IntoMap
19 | @ViewModelKey(NewsViewModel.class)
20 | abstract ViewModel bindNewsViewModel(NewsViewModel searchViewModel);
21 |
22 |
23 | @Binds
24 | abstract ViewModelProvider.Factory bindNewsViewModelFactory(NewsViewModelFactory factory);
25 | }
26 |
--------------------------------------------------------------------------------
/app/src/main/java/news/agoda/com/sample/di/scope/AppScope.java:
--------------------------------------------------------------------------------
1 | package news.agoda.com.sample.di.scope;
2 |
3 | import java.lang.annotation.Retention;
4 | import java.lang.annotation.RetentionPolicy;
5 |
6 | import javax.inject.Scope;
7 |
8 | @Scope
9 | @Retention(RetentionPolicy.RUNTIME)
10 | public @interface AppScope {
11 | }
12 |
--------------------------------------------------------------------------------
/app/src/main/java/news/agoda/com/sample/di/scope/ViewModelKey.java:
--------------------------------------------------------------------------------
1 | package news.agoda.com.sample.di.scope;
2 |
3 |
4 | import android.arch.lifecycle.ViewModel;
5 |
6 | import java.lang.annotation.Documented;
7 | import java.lang.annotation.ElementType;
8 | import java.lang.annotation.Retention;
9 | import java.lang.annotation.RetentionPolicy;
10 | import java.lang.annotation.Target;
11 |
12 | import dagger.MapKey;
13 |
14 | @Documented
15 | @MapKey
16 | @Target(ElementType.METHOD)
17 | @Retention(RetentionPolicy.RUNTIME)
18 | public @interface ViewModelKey {
19 |
20 | Class extends ViewModel> value();
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/app/src/main/java/news/agoda/com/sample/ui/DetailViewActivity.java:
--------------------------------------------------------------------------------
1 | package news.agoda.com.sample.ui;
2 |
3 | import android.content.Context;
4 | import android.content.Intent;
5 | import android.net.Uri;
6 | import android.os.Bundle;
7 |
8 | import com.facebook.drawee.backends.pipeline.Fresco;
9 | import com.facebook.drawee.interfaces.DraweeController;
10 | import com.facebook.imagepipeline.request.ImageRequest;
11 |
12 | import news.agoda.com.sample.R;
13 | import news.agoda.com.sample.base.BaseActivity;
14 | import news.agoda.com.sample.databinding.ActivityDetailBinding;
15 | import news.agoda.com.sample.util.AppConstants;
16 | import news.agoda.com.sample.util.Utilities;
17 |
18 | /**
19 | * News detail view
20 | */
21 | public class DetailViewActivity extends BaseActivity {
22 | private String storyURL = "";
23 |
24 | public static void openDetailViewActivity(Context context,Bundle bundle ){
25 | Intent intent = new Intent(context, DetailViewActivity.class);
26 | intent.putExtras(bundle);
27 | context.startActivity(intent);
28 | }
29 |
30 | @Override
31 | protected void onCreate(Bundle savedInstanceState) {
32 | super.onCreate(savedInstanceState);
33 | Bundle extras = getIntent().getExtras();
34 | storyURL = extras.getString(AppConstants.URL);
35 | String title = extras.getString(AppConstants.TITLE);
36 | String summary = extras.getString(AppConstants.SUMMARY);
37 | String imageURL = "";
38 | if (extras.containsKey(AppConstants.IMAGEURL))
39 | imageURL = extras.getString(AppConstants.IMAGEURL);
40 | binding.setTitle(title);
41 | binding.setSummary(summary);
42 | DraweeController draweeController = Fresco.newDraweeControllerBuilder()
43 | .setImageRequest(ImageRequest.fromUri(Uri.parse(imageURL)))
44 | .setOldController(binding.newsImage.getController()).build();
45 | binding.newsImage.setController(draweeController);
46 | binding.setStoryClick(click -> {
47 | if (Utilities.isNetworkConnected(this))
48 | openCompleteNews();
49 | else
50 | showToast(getString(R.string.internet_error));
51 | });
52 | }
53 |
54 | @Override
55 | public int getLayout() {
56 | return R.layout.activity_detail;
57 | }
58 |
59 | private void openCompleteNews() {
60 | Intent intent = new Intent(Intent.ACTION_VIEW);
61 | intent.setData(Uri.parse(storyURL));
62 | startActivity(intent);
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/app/src/main/java/news/agoda/com/sample/ui/MainActivity.java:
--------------------------------------------------------------------------------
1 | package news.agoda.com.sample.ui;
2 |
3 | import android.arch.lifecycle.ViewModelProviders;
4 | import android.os.Bundle;
5 | import android.support.v7.widget.LinearLayoutManager;
6 |
7 | import com.facebook.drawee.backends.pipeline.Fresco;
8 |
9 | import java.util.ArrayList;
10 |
11 | import javax.inject.Inject;
12 |
13 | import news.agoda.com.sample.R;
14 | import news.agoda.com.sample.api.NewsViewModelFactory;
15 | import news.agoda.com.sample.api.model.NewsEntity;
16 | import news.agoda.com.sample.api.model.NewsList;
17 | import news.agoda.com.sample.base.BaseActivity;
18 | import news.agoda.com.sample.databinding.ActivityMainBinding;
19 | import news.agoda.com.sample.ui.callback.IItemClick;
20 | import news.agoda.com.sample.ui.viewmodel.NewsViewModel;
21 | import news.agoda.com.sample.util.AppConstants;
22 |
23 | public class MainActivity extends BaseActivity implements IItemClick {
24 | private NewsAdapter newsAdapter;
25 | private NewsViewModel newsViewModel;
26 |
27 | @Inject
28 | NewsViewModelFactory newsViewModelFactory;
29 |
30 | @Override
31 | protected void onCreate(Bundle savedInstanceState) {
32 | super.onCreate(savedInstanceState);
33 | Fresco.initialize(this);
34 | newsViewModel = ViewModelProviders.of(this, newsViewModelFactory).get(NewsViewModel.class);
35 | initNewsDataAdapter();
36 | observeDataChange();
37 | newsViewModel.fetchNews();
38 | }
39 |
40 | private void loadNewsData(NewsList data) {
41 | newsAdapter.addNewsList(data.getResults());
42 | }
43 |
44 | @Override
45 | public int getLayout() {
46 | return R.layout.activity_main;
47 | }
48 |
49 | private void initNewsDataAdapter() {
50 | newsAdapter = new NewsAdapter(this, new ArrayList<>());
51 | binding.rvNewsList.setLayoutManager(new LinearLayoutManager(this));
52 | binding.rvNewsList.setAdapter(newsAdapter);
53 | newsAdapter.addListener(this);
54 | }
55 |
56 | private void observeDataChange() {
57 | newsViewModel.getNewsListState().observe(this, newsListViewState -> {
58 | switch (newsListViewState.getCurrentState()) {
59 | case 0:
60 | binding.setShowLoading(true);
61 | break;
62 | case 1:
63 | binding.setShowLoading(false);
64 | loadNewsData(newsListViewState.getData());
65 | break;
66 | case -1: // show error
67 | binding.setShowLoading(false);
68 | break;
69 | }
70 | });
71 | }
72 |
73 | @Override
74 | public void onItemClick(NewsEntity newsEntity) {
75 | Bundle bundle = new Bundle();
76 | bundle.putString(AppConstants.TITLE, newsEntity.getTitle());
77 | bundle.putString(AppConstants.URL, newsEntity.getArticleUrl());
78 | bundle.putString(AppConstants.SUMMARY, newsEntity.getSummary());
79 | if (!newsEntity.getMediaEntityList().isEmpty())
80 | bundle.putString(AppConstants.IMAGEURL, newsEntity.getMediaEntityList().get(0).getUrl());
81 |
82 | DetailViewActivity.openDetailViewActivity(this, bundle);
83 | }
84 |
85 | @Override
86 | protected void onDestroy() {
87 | super.onDestroy();
88 | if (newsAdapter != null) {
89 | newsAdapter.removeListener();
90 | }
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/app/src/main/java/news/agoda/com/sample/ui/NewsAdapter.java:
--------------------------------------------------------------------------------
1 | package news.agoda.com.sample.ui;
2 |
3 | import android.content.Context;
4 | import android.net.Uri;
5 | import android.support.annotation.NonNull;
6 | import android.support.v7.widget.RecyclerView;
7 | import android.view.LayoutInflater;
8 | import android.view.ViewGroup;
9 |
10 | import com.facebook.drawee.backends.pipeline.Fresco;
11 | import com.facebook.drawee.interfaces.DraweeController;
12 | import com.facebook.imagepipeline.request.ImageRequest;
13 |
14 | import java.util.List;
15 |
16 | import news.agoda.com.sample.R;
17 | import news.agoda.com.sample.api.model.MediaEntity;
18 | import news.agoda.com.sample.api.model.NewsEntity;
19 | import news.agoda.com.sample.databinding.ListItemNewsBinding;
20 | import news.agoda.com.sample.ui.callback.IItemClick;
21 |
22 | public class NewsAdapter extends RecyclerView.Adapter {
23 | private List newsList;
24 | private LayoutInflater layoutInflater;
25 | private IItemClick listener;
26 |
27 | public NewsAdapter(Context context, List newsList) {
28 | this.newsList = newsList;
29 | layoutInflater = LayoutInflater.from(context);
30 | }
31 |
32 | public void addListener(IItemClick itemClick) {
33 | this.listener = itemClick;
34 | }
35 |
36 | public void removeListener() {
37 | listener = null;
38 | }
39 |
40 | public void addNewsList(List newsList) {
41 | if (!this.newsList.isEmpty()) {
42 | this.newsList.clear();
43 | }
44 | this.newsList.addAll(newsList);
45 | notifyDataSetChanged();
46 | }
47 |
48 | @NonNull
49 | @Override
50 | public ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
51 | return new ViewHolder(ListItemNewsBinding.inflate(layoutInflater, viewGroup, false));
52 | }
53 |
54 | @Override
55 | public void onBindViewHolder(@NonNull ViewHolder viewHolder, int i) {
56 | final NewsEntity newsEntity = newsList.get(i);
57 | viewHolder.binding.setNews(newsEntity);
58 | List mediaEntityList = newsEntity.getMediaEntityList();
59 | if (!mediaEntityList.isEmpty()) {
60 | MediaEntity mediaEntity = mediaEntityList.get(0);
61 | String thumbnailURL = mediaEntity.getUrl();
62 |
63 | DraweeController draweeController = Fresco.newDraweeControllerBuilder().setImageRequest(ImageRequest.fromUri(Uri.parse(thumbnailURL))).setOldController(viewHolder.binding.newsItemImage.getController()).build();
64 | viewHolder.binding.newsItemImage.setController(draweeController);
65 | } else {
66 | viewHolder.binding.newsItemImage.setImageResource(R.mipmap.ic_launcher);
67 | }
68 | viewHolder.binding.setItemClickListener(click -> listener.onItemClick(newsEntity));
69 | }
70 |
71 | @Override
72 | public int getItemCount() {
73 | return newsList.size();
74 | }
75 |
76 | static final class ViewHolder extends RecyclerView.ViewHolder {
77 | private final ListItemNewsBinding binding;
78 |
79 | public ViewHolder(ListItemNewsBinding binding) {
80 | super(binding.getRoot());
81 | this.binding = binding;
82 | }
83 | }
84 | }
85 |
86 |
--------------------------------------------------------------------------------
/app/src/main/java/news/agoda/com/sample/ui/callback/IItemClick.java:
--------------------------------------------------------------------------------
1 | package news.agoda.com.sample.ui.callback;
2 |
3 | public interface IItemClick {
4 | void onItemClick(T item);
5 | }
6 |
--------------------------------------------------------------------------------
/app/src/main/java/news/agoda/com/sample/ui/viewmodel/NewsListViewState.java:
--------------------------------------------------------------------------------
1 | package news.agoda.com.sample.ui.viewmodel;
2 |
3 | import news.agoda.com.sample.api.model.NewsList;
4 | import news.agoda.com.sample.base.BaseViewState;
5 |
6 | public class NewsListViewState extends BaseViewState {
7 | private NewsListViewState(NewsList data, int currentState, Throwable error) {
8 | this.data = data;
9 | this.error = error;
10 | this.currentState = currentState;
11 | }
12 |
13 | public static NewsListViewState ERROR_STATE = new NewsListViewState(null, State.FAILED.value, new Throwable());
14 | public static NewsListViewState LOADING_STATE = new NewsListViewState(null, State.LOADING.value, null);
15 | public static NewsListViewState SUCCESS_STATE = new NewsListViewState(new NewsList(), State.SUCCESS.value, null);
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/app/src/main/java/news/agoda/com/sample/ui/viewmodel/NewsViewModel.java:
--------------------------------------------------------------------------------
1 | package news.agoda.com.sample.ui.viewmodel;
2 |
3 | import android.arch.lifecycle.MutableLiveData;
4 | import android.arch.lifecycle.ViewModel;
5 |
6 | import javax.inject.Inject;
7 |
8 | import io.reactivex.disposables.CompositeDisposable;
9 | import news.agoda.com.sample.api.NewsApiClient;
10 | import news.agoda.com.sample.api.RxSingleSchedulers;
11 | import news.agoda.com.sample.api.model.NewsList;
12 |
13 | public class NewsViewModel extends ViewModel {
14 |
15 | private CompositeDisposable disposable;
16 | private final NewsApiClient apiClient;
17 | private final RxSingleSchedulers rxSingleSchedulers;
18 | private final MutableLiveData newsListState = new MutableLiveData<>();
19 |
20 | public MutableLiveData getNewsListState() {
21 | return newsListState;
22 | }
23 |
24 | @Inject
25 | public NewsViewModel(NewsApiClient apiClient, RxSingleSchedulers rxSingleSchedulers) {
26 | this.apiClient = apiClient;
27 | this.rxSingleSchedulers = rxSingleSchedulers;
28 | disposable = new CompositeDisposable();
29 | }
30 |
31 | public void fetchNews() {
32 | disposable.add(apiClient.fetchNews()
33 | .doOnEvent((newsList, throwable) -> onLoading())
34 | .compose(rxSingleSchedulers.applySchedulers())
35 | .subscribe(this::onSuccess,
36 | this::onError));
37 | }
38 |
39 | private void onSuccess(NewsList newsList) {
40 | NewsListViewState.SUCCESS_STATE.setData(newsList);
41 | newsListState.postValue(NewsListViewState.SUCCESS_STATE);
42 | }
43 |
44 | private void onError(Throwable error) {
45 | NewsListViewState.ERROR_STATE.setError(error);
46 | newsListState.postValue(NewsListViewState.ERROR_STATE);
47 | }
48 |
49 | private void onLoading() {
50 | newsListState.postValue(NewsListViewState.LOADING_STATE);
51 | }
52 |
53 | @Override
54 | protected void onCleared() {
55 | super.onCleared();
56 | if (disposable != null) {
57 | disposable.clear();
58 | disposable = null;
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/app/src/main/java/news/agoda/com/sample/util/AppConstants.java:
--------------------------------------------------------------------------------
1 | package news.agoda.com.sample.util;
2 |
3 | public final class AppConstants {
4 |
5 | public static final String TITLE = "title";
6 | public static final String URL = "url";
7 | public static final String SUMMARY = "summary";
8 | public static final String IMAGEURL = "imageURL";
9 | }
10 |
--------------------------------------------------------------------------------
/app/src/main/java/news/agoda/com/sample/util/MediaDeserializer.java:
--------------------------------------------------------------------------------
1 | package news.agoda.com.sample.util;
2 |
3 | import com.google.gson.TypeAdapter;
4 | import com.google.gson.stream.JsonReader;
5 | import com.google.gson.stream.JsonToken;
6 | import com.google.gson.stream.JsonWriter;
7 |
8 | import java.io.IOException;
9 | import java.util.ArrayList;
10 | import java.util.List;
11 |
12 | import news.agoda.com.sample.api.model.MediaEntity;
13 |
14 | public class MediaDeserializer extends TypeAdapter> {
15 |
16 | @Override
17 | public void write(JsonWriter out, List value) throws IOException {
18 |
19 | }
20 |
21 | @Override
22 | public List read(JsonReader reader) throws IOException {
23 | List mediaEntities = new ArrayList<>();
24 | try {
25 | if (reader.peek() == JsonToken.BEGIN_ARRAY) {
26 | reader.beginArray();
27 | while (reader.hasNext()) {
28 | mediaEntities.add(readMessage(reader));
29 | }
30 | reader.endArray();
31 | } else {
32 | reader.skipValue();
33 | return mediaEntities;
34 | }
35 | } catch (Exception e) {
36 | e.printStackTrace();
37 | }
38 |
39 | return mediaEntities;
40 | }
41 |
42 |
43 | private MediaEntity readMessage(JsonReader reader) throws IOException {
44 |
45 | String url = null;
46 | String format = null;
47 | int height = 0;
48 | int width = 0;
49 | String type = null;
50 | String subType = null;
51 | String caption = null;
52 | String copyright = null;
53 |
54 | reader.beginObject();
55 | while (reader.hasNext()) {
56 | String name = reader.nextName();
57 | switch (name) {
58 | case "url":
59 | url = reader.nextString();
60 | break;
61 | case "format":
62 | format = reader.nextString();
63 | break;
64 | case "height":
65 | height = reader.nextInt();
66 | break;
67 | case "width":
68 | width = reader.nextInt();
69 | break;
70 | case "type":
71 | type = reader.nextString();
72 | break;
73 | case "subtype":
74 | subType = reader.nextString();
75 | break;
76 | case "caption":
77 | caption = reader.nextString();
78 | break;
79 | case "copyright":
80 | copyright = reader.nextString();
81 | break;
82 | default:
83 | reader.skipValue();
84 | break;
85 | }
86 |
87 | }
88 | reader.endObject();
89 | return new MediaEntity(url, format, height, width, type, subType, caption, copyright);
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/app/src/main/java/news/agoda/com/sample/util/Utilities.java:
--------------------------------------------------------------------------------
1 | package news.agoda.com.sample.util;
2 |
3 | import android.content.Context;
4 | import android.net.ConnectivityManager;
5 | import android.net.NetworkInfo;
6 |
7 | import io.reactivex.Observable;
8 |
9 | public final class Utilities {
10 |
11 | private Utilities() {
12 | }
13 |
14 | public static boolean isNetworkConnected(Context context) {
15 | ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
16 | NetworkInfo info = cm.getActiveNetworkInfo();
17 | return info != null && info.isConnected();
18 |
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/place_holder.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droiddevgeeks/NewsApp/06861a8726aaa82d2657f022bab304f109b36df8/app/src/main/res/drawable/place_holder.png
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_detail.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
9 |
10 |
13 |
14 |
17 |
18 |
19 |
20 |
24 |
25 |
34 |
35 |
45 |
46 |
54 |
55 |
56 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
10 |
11 |
12 |
20 |
21 |
25 |
26 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/list_item_news.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
10 |
11 |
14 |
15 |
16 |
17 |
23 |
24 |
32 |
33 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_main.xml:
--------------------------------------------------------------------------------
1 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droiddevgeeks/NewsApp/06861a8726aaa82d2657f022bab304f109b36df8/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droiddevgeeks/NewsApp/06861a8726aaa82d2657f022bab304f109b36df8/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droiddevgeeks/NewsApp/06861a8726aaa82d2657f022bab304f109b36df8/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droiddevgeeks/NewsApp/06861a8726aaa82d2657f022bab304f109b36df8/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/values-sw600dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 64dp
4 | 10dp
5 | 10dp
6 | 210dp
7 | 140dp
8 | 75dp
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 | 10dp
7 | 10dp
8 | 210dp
9 | 140dp
10 | 75dp
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #008577
4 | #00574B
5 | #D81B60
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 | 8dp
6 | 10dp
7 | 10dp
8 | 18sp
9 | 210dp
10 | 140dp
11 | 75dp
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | TechNewsSample
3 |
4 | Hello world!
5 | Settings
6 |
7 | Please check your internet connection
8 | Something wend wrong
9 | Full Story
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/test/java/news/agoda/com/sample/APIAvailabilityTest.java:
--------------------------------------------------------------------------------
1 | package news.agoda.com.sample;
2 |
3 | import org.junit.Test;
4 |
5 | import java.io.BufferedReader;
6 | import java.io.InputStream;
7 | import java.io.InputStreamReader;
8 | import java.net.URL;
9 | import java.net.URLConnection;
10 | import java.nio.charset.Charset;
11 |
12 | public class APIAvailabilityTest {
13 |
14 | @Test public void testAvailability() throws Exception {
15 | URLConnection connection = new URL("http://www.mocky.io/v2/573c89f31100004a1daa8adb").openConnection();
16 | InputStream response = connection.getInputStream();
17 |
18 | StringBuffer buffer = new StringBuffer();
19 | try (BufferedReader reader = new BufferedReader(new InputStreamReader(response, Charset.defaultCharset()))) {
20 | for (String line; (line = reader.readLine()) != null; ) {
21 | buffer.append(line);
22 | }
23 | }
24 |
25 | assert buffer.length() > 0;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/app/src/test/java/news/agoda/com/sample/api/model/MediaEntityTest.java:
--------------------------------------------------------------------------------
1 | package news.agoda.com.sample.api.model;
2 |
3 | import org.junit.After;
4 | import org.junit.Assert;
5 | import org.junit.Before;
6 | import org.junit.Test;
7 | import org.mockito.Mock;
8 | import org.mockito.Mockito;
9 | import org.mockito.MockitoAnnotations;
10 |
11 | public class MediaEntityTest {
12 |
13 | private final String url = "www.test.com";
14 | private final String format = "testing format";
15 | private int height = 2;
16 | private int width = 1;
17 | private final String type = "testing type";
18 | private final String subType = "testing subtype";
19 | private final String caption = "testing caption";
20 | private final String copyright = "testing copyright";
21 |
22 | @Mock
23 | MediaEntity mediaEntity;
24 |
25 | @Before
26 | public void setUp() throws Exception {
27 | MockitoAnnotations.initMocks(this);
28 | Mockito.when(mediaEntity.getUrl()).thenReturn(url);
29 | Mockito.when(mediaEntity.getFormat()).thenReturn(format);
30 | Mockito.when(mediaEntity.getHeight()).thenReturn(height);
31 | Mockito.when(mediaEntity.getWidth()).thenReturn(width);
32 | Mockito.when(mediaEntity.getType()).thenReturn(type);
33 | Mockito.when(mediaEntity.getSubType()).thenReturn(subType);
34 | Mockito.when(mediaEntity.getCaption()).thenReturn(caption);
35 | Mockito.when(mediaEntity.getCaption()).thenReturn(copyright);
36 | }
37 |
38 | @Test
39 | public void testMediaUrl() {
40 | Mockito.when(mediaEntity.getUrl()).thenReturn(url);
41 | Assert.assertEquals("www.test.com", mediaEntity.getUrl());
42 | }
43 |
44 | @Test
45 | public void testMediaFormat() {
46 | Mockito.when(mediaEntity.getFormat()).thenReturn(format);
47 | Assert.assertEquals("testing format", mediaEntity.getFormat());
48 | }
49 |
50 | @Test
51 | public void testMediaHeight() {
52 | Mockito.when(mediaEntity.getHeight()).thenReturn(height);
53 | Assert.assertEquals(2, mediaEntity.getHeight());
54 | }
55 |
56 | @Test
57 | public void testMediaWidth() {
58 | Mockito.when(mediaEntity.getWidth()).thenReturn(width);
59 | Assert.assertEquals(1, mediaEntity.getWidth());
60 | }
61 |
62 | @Test
63 | public void testMediaType() {
64 | Mockito.when(mediaEntity.getType()).thenReturn(type);
65 | Assert.assertEquals("testing type", mediaEntity.getType());
66 | }
67 |
68 | @Test
69 | public void testMediaSubType() {
70 | Mockito.when(mediaEntity.getSubType()).thenReturn(subType);
71 | Assert.assertEquals("testing subtype", mediaEntity.getSubType());
72 | }
73 |
74 | @Test
75 | public void testMediaCaption() {
76 | Mockito.when(mediaEntity.getCaption()).thenReturn(caption);
77 | Assert.assertEquals("testing caption", mediaEntity.getCaption());
78 | }
79 |
80 | @Test
81 | public void testMediaCopyright() {
82 | Mockito.when(mediaEntity.getCopyright()).thenReturn(copyright);
83 | Assert.assertEquals("testing copyright", mediaEntity.getCopyright());
84 | }
85 |
86 |
87 | @Test
88 | public void testMediaUrlIncorrect() {
89 | Mockito.when(mediaEntity.getUrl()).thenReturn(url);
90 | Assert.assertNotEquals(" testing www.test.com", mediaEntity.getUrl());
91 | }
92 |
93 | @Test
94 | public void testMediaFormatIncorrect() {
95 | Mockito.when(mediaEntity.getFormat()).thenReturn(format);
96 | Assert.assertNotEquals("format", mediaEntity.getFormat());
97 | }
98 |
99 | @Test
100 | public void testMediaHeightIncorrect() {
101 | Mockito.when(mediaEntity.getHeight()).thenReturn(height);
102 | Assert.assertNotEquals(12, mediaEntity.getHeight());
103 | }
104 |
105 | @Test
106 | public void testMediaWidthIncorrect() {
107 | Mockito.when(mediaEntity.getWidth()).thenReturn(width);
108 | Assert.assertNotEquals(11, mediaEntity.getWidth());
109 | }
110 |
111 | @Test
112 | public void testMediaTypeIncorrect() {
113 | Mockito.when(mediaEntity.getType()).thenReturn(type);
114 | Assert.assertNotEquals("type", mediaEntity.getType());
115 | }
116 |
117 | @Test
118 | public void testMediaSubTypeIncorrect() {
119 | Mockito.when(mediaEntity.getSubType()).thenReturn(subType);
120 | Assert.assertNotEquals("subtype", mediaEntity.getSubType());
121 | }
122 |
123 | @Test
124 | public void testMediaCaptionIncorrect() {
125 | Mockito.when(mediaEntity.getCaption()).thenReturn(caption);
126 | Assert.assertNotEquals("caption", mediaEntity.getCaption());
127 | }
128 |
129 | @Test
130 | public void testMediaCopyrightIncorrect() {
131 | Mockito.when(mediaEntity.getCopyright()).thenReturn(copyright);
132 | Assert.assertNotEquals("copyright", mediaEntity.getCopyright());
133 | }
134 |
135 |
136 |
137 | @After
138 | public void tearDown() throws Exception {
139 | mediaEntity = null;
140 | }
141 | }
--------------------------------------------------------------------------------
/app/src/test/java/news/agoda/com/sample/api/model/NewsEntityTest.java:
--------------------------------------------------------------------------------
1 | package news.agoda.com.sample.api.model;
2 |
3 | import org.junit.After;
4 | import org.junit.Assert;
5 | import org.junit.Before;
6 | import org.junit.Test;
7 | import org.mockito.Mock;
8 | import org.mockito.Mockito;
9 | import org.mockito.MockitoAnnotations;
10 |
11 | import java.util.ArrayList;
12 | import java.util.List;
13 |
14 | public class NewsEntityTest {
15 |
16 |
17 | private final String title = "Testing Title";
18 | private final String summary = "Testing summary";
19 | private final String articleUrl = "www.google.com/image";
20 | private final String byline = "Testing byline";
21 | private final String publishedDate = "29-01-2019";
22 | private final List mediaEntityList = new ArrayList<>();
23 |
24 | @Mock
25 | NewsEntity newsEntity;
26 | @Mock
27 | List mediaList;
28 |
29 | @Before
30 | public void setUp() throws Exception {
31 | MockitoAnnotations.initMocks(this);
32 | Mockito.when(newsEntity.getTitle()).thenReturn(title);
33 | Mockito.when(newsEntity.getSummary()).thenReturn(summary);
34 | Mockito.when(newsEntity.getArticleUrl()).thenReturn(articleUrl);
35 | Mockito.when(newsEntity.getByline()).thenReturn(byline);
36 | Mockito.when(newsEntity.getPublishedDate()).thenReturn(publishedDate);
37 | Mockito.when(newsEntity.getMediaEntityList()).thenReturn(mediaEntityList);
38 | }
39 |
40 | @Test
41 | public void testNewsTitle(){
42 | Mockito.when(newsEntity.getTitle()).thenReturn(title);
43 | Assert.assertEquals("Testing Title",newsEntity.getTitle());
44 | }
45 |
46 | @Test
47 | public void testNewsSummary(){
48 | Mockito.when(newsEntity.getSummary()).thenReturn(summary);
49 | Assert.assertEquals("Testing summary",newsEntity.getSummary());
50 | }
51 |
52 | @Test
53 | public void testNewsArticleUrl(){
54 | Mockito.when(newsEntity.getArticleUrl()).thenReturn(articleUrl);
55 | Assert.assertEquals("www.google.com/image",newsEntity.getArticleUrl());
56 | }
57 |
58 | @Test
59 | public void testNewsByline(){
60 | Mockito.when(newsEntity.getByline()).thenReturn(byline);
61 | Assert.assertEquals("Testing byline",newsEntity.getByline());
62 | }
63 |
64 | @Test
65 | public void testNewsDate(){
66 | Mockito.when(newsEntity.getPublishedDate()).thenReturn(publishedDate);
67 | Assert.assertEquals("29-01-2019",newsEntity.getPublishedDate());
68 | }
69 |
70 |
71 | @Test
72 | public void testNewsMediaEntitiy(){
73 | Mockito.when(newsEntity.getMediaEntityList()).thenReturn(mediaEntityList);
74 | Assert.assertEquals(new ArrayList<>(),newsEntity.getMediaEntityList());
75 | }
76 |
77 |
78 |
79 | @Test
80 | public void testNewsTitleIncorrect(){
81 | Mockito.when(newsEntity.getTitle()).thenReturn(title);
82 | Assert.assertNotEquals("Title",newsEntity.getTitle());
83 | }
84 |
85 | @Test
86 | public void testNewsSummaryIncorrect(){
87 | Mockito.when(newsEntity.getSummary()).thenReturn(summary);
88 | Assert.assertNotEquals("summary",newsEntity.getSummary());
89 | }
90 |
91 | @Test
92 | public void testNewsArticleUrlIncorrect(){
93 | Mockito.when(newsEntity.getArticleUrl()).thenReturn(articleUrl);
94 | Assert.assertNotEquals("www.google.com",newsEntity.getArticleUrl());
95 | }
96 |
97 | @Test
98 | public void testNewsBylineIncorrect(){
99 | Mockito.when(newsEntity.getByline()).thenReturn(byline);
100 | Assert.assertNotEquals("byline",newsEntity.getByline());
101 | }
102 |
103 | @Test
104 | public void testNewsDateIncorrect(){
105 | Mockito.when(newsEntity.getPublishedDate()).thenReturn(publishedDate);
106 | Assert.assertNotEquals("29-02-2019",newsEntity.getPublishedDate());
107 | }
108 |
109 |
110 | @Test
111 | public void testNewsMediaEntitiyIncorrect(){
112 | Mockito.when(newsEntity.getMediaEntityList()).thenReturn(mediaEntityList);
113 | Assert.assertNotEquals("",newsEntity.getMediaEntityList());
114 | }
115 |
116 |
117 | @After
118 | public void tearDown() throws Exception {
119 | newsEntity =null;
120 | mediaList = null;
121 | }
122 | }
--------------------------------------------------------------------------------
/app/src/test/java/news/agoda/com/sample/ui/viewmodel/NewsViewModelTest.java:
--------------------------------------------------------------------------------
1 | package news.agoda.com.sample.ui.viewmodel;
2 |
3 | import android.arch.core.executor.testing.InstantTaskExecutorRule;
4 | import android.arch.lifecycle.Lifecycle;
5 | import android.arch.lifecycle.LifecycleOwner;
6 | import android.arch.lifecycle.LifecycleRegistry;
7 | import android.arch.lifecycle.Observer;
8 |
9 | import org.junit.After;
10 | import org.junit.Before;
11 | import org.junit.Rule;
12 | import org.junit.Test;
13 | import org.junit.runner.RunWith;
14 | import org.junit.runners.JUnit4;
15 | import org.mockito.Mock;
16 | import org.mockito.MockitoAnnotations;
17 |
18 | import io.reactivex.Single;
19 | import news.agoda.com.sample.api.ApiEndPoint;
20 | import news.agoda.com.sample.api.NewsApiClient;
21 | import news.agoda.com.sample.api.RxSingleSchedulers;
22 | import news.agoda.com.sample.api.model.NewsList;
23 |
24 | import static org.junit.Assert.assertNotNull;
25 | import static org.junit.Assert.assertTrue;
26 | import static org.mockito.Mockito.verify;
27 | import static org.mockito.Mockito.when;
28 |
29 | @RunWith(JUnit4.class)
30 | public class NewsViewModelTest {
31 | @Rule
32 | public InstantTaskExecutorRule instantExecutorRule = new InstantTaskExecutorRule();
33 |
34 | @Mock
35 | ApiEndPoint apiEndPoint;
36 | @Mock
37 | NewsApiClient apiClient;
38 | private NewsViewModel viewModel;
39 | @Mock
40 | Observer observer;
41 | @Mock
42 | LifecycleOwner lifecycleOwner;
43 | Lifecycle lifecycle;
44 |
45 |
46 | @Before
47 | public void setUp() throws Exception {
48 | MockitoAnnotations.initMocks(this);
49 | lifecycle = new LifecycleRegistry(lifecycleOwner);
50 | viewModel = new NewsViewModel(apiClient, RxSingleSchedulers.TEST_SCHEDULER);
51 | viewModel.getNewsListState().observeForever(observer);
52 | }
53 |
54 | @Test
55 | public void testNull() {
56 | when(apiClient.fetchNews()).thenReturn(null);
57 | assertNotNull(viewModel.getNewsListState());
58 | assertTrue(viewModel.getNewsListState().hasObservers());
59 | }
60 |
61 | @Test
62 | public void testApiFetchDataSuccess() {
63 | // Mock API response
64 | when(apiClient.fetchNews()).thenReturn(Single.just(new NewsList()));
65 | viewModel.fetchNews();
66 | verify(observer).onChanged(NewsListViewState.LOADING_STATE);
67 | verify(observer).onChanged(NewsListViewState.SUCCESS_STATE);
68 | }
69 |
70 | @Test
71 | public void testApiFetchDataError() {
72 | when(apiClient.fetchNews()).thenReturn(Single.error(new Throwable("Api error")));
73 | viewModel.fetchNews();
74 | verify(observer).onChanged(NewsListViewState.LOADING_STATE);
75 | verify(observer).onChanged(NewsListViewState.ERROR_STATE);
76 | }
77 |
78 | @After
79 | public void tearDown() throws Exception {
80 | apiClient = null;
81 | viewModel = null;
82 | }
83 | }
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | google()
4 | jcenter()
5 |
6 | }
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:3.3.0'
9 | }
10 | }
11 |
12 | allprojects {
13 | repositories {
14 | google()
15 | jcenter()
16 |
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droiddevgeeks/NewsApp/06861a8726aaa82d2657f022bab304f109b36df8/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Tue Jan 22 13:06:50 IST 2019
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------