├── app ├── .gitignore ├── src │ ├── test │ │ ├── resources │ │ │ ├── mocikito-extensions │ │ │ │ └── org.mockito.plugins.mockmaker │ │ │ └── mocks │ │ │ │ └── articles.json │ │ └── java │ │ │ └── lingaraj │ │ │ └── hourglass │ │ │ └── in │ │ │ ├── testutils │ │ │ └── TestUtil.java │ │ │ └── base │ │ │ └── home │ │ │ └── articlesFragment │ │ │ └── ArticlesViewModelTest.java │ ├── main │ │ ├── res │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ ├── values │ │ │ │ ├── strings.xml │ │ │ │ ├── dimens.xml │ │ │ │ ├── styles.xml │ │ │ │ └── colors.xml │ │ │ ├── drawable │ │ │ │ ├── round_edged_shape.xml │ │ │ │ ├── ic_refresh.xml │ │ │ │ └── ic_launcher_background.xml │ │ │ ├── layout │ │ │ │ ├── fragment_webview.xml │ │ │ │ ├── activity_main.xml │ │ │ │ ├── card_trending.xml │ │ │ │ ├── fragment_main.xml │ │ │ │ └── card_default.xml │ │ │ └── drawable-v24 │ │ │ │ └── ic_launcher_foreground.xml │ │ ├── java │ │ │ └── lingaraj │ │ │ │ └── hourglass │ │ │ │ └── in │ │ │ │ └── base │ │ │ │ ├── ui │ │ │ │ ├── ScreenProvider.java │ │ │ │ ├── ActivityViewInterceptorModule.java │ │ │ │ ├── ScreenNavigator.java │ │ │ │ ├── NavigationModule.java │ │ │ │ ├── ActivityViewInterceptor.java │ │ │ │ └── DefaultScreenNavigator.java │ │ │ │ ├── api │ │ │ │ ├── response │ │ │ │ │ ├── Source.java │ │ │ │ │ ├── Status.java │ │ │ │ │ └── NewsResponse.java │ │ │ │ ├── APIRouter.java │ │ │ │ ├── di │ │ │ │ │ ├── APIModule.java │ │ │ │ │ └── APIRequester.java │ │ │ │ └── NewsAPIRequester.java │ │ │ │ ├── di │ │ │ │ ├── ApplicationScope.java │ │ │ │ ├── ActivityScope.java │ │ │ │ ├── ScreenScope.java │ │ │ │ ├── ScreenComponent.java │ │ │ │ ├── Names.java │ │ │ │ ├── Injector.java │ │ │ │ ├── ActivityInjector.java │ │ │ │ └── ScreenInjector.java │ │ │ │ ├── database │ │ │ │ ├── AppDatabase.java │ │ │ │ ├── DatabaseModule.java │ │ │ │ ├── ArticlesDao.java │ │ │ │ └── Article.java │ │ │ │ ├── utils │ │ │ │ ├── Constants.java │ │ │ │ ├── ButterKnifeUtils.java │ │ │ │ ├── General.java │ │ │ │ └── Storage.java │ │ │ │ ├── home │ │ │ │ ├── articlesFragment │ │ │ │ │ ├── di │ │ │ │ │ │ ├── HomeFragmentScreenModule.java │ │ │ │ │ │ ├── HomeFragmentComponent.java │ │ │ │ │ │ └── HomeFragmentUIManager.java │ │ │ │ │ ├── ArticleFragmentRepository.java │ │ │ │ │ ├── ArticlesViewModel.java │ │ │ │ │ ├── ArticlesAdapter.java │ │ │ │ │ └── ArticlesFragment.java │ │ │ │ ├── HomeActivity.java │ │ │ │ ├── preview │ │ │ │ │ ├── di │ │ │ │ │ │ └── PreviewComponent.java │ │ │ │ │ └── WebLinkPreviewFragment.java │ │ │ │ └── di │ │ │ │ │ ├── HomeActivityComponent.java │ │ │ │ │ └── HomeBindingModule.java │ │ │ │ ├── lifecycle │ │ │ │ ├── DisposableManager.java │ │ │ │ ├── ActivityLifecycleTask.java │ │ │ │ └── ScreenLifecycleTask.java │ │ │ │ ├── core │ │ │ │ ├── ScreenModule.java │ │ │ │ ├── ApplicationComponent.java │ │ │ │ ├── ActivityBindingModule.java │ │ │ │ ├── BaseViewModelFactory.java │ │ │ │ ├── ApplicationModule.java │ │ │ │ ├── AppViewModelsModule.java │ │ │ │ ├── BaseFragment.java │ │ │ │ ├── NetworkModule.java │ │ │ │ └── BaseActivity.java │ │ │ │ └── BaseApp.java │ │ └── AndroidManifest.xml │ └── androidTest │ │ └── java │ │ └── lingaraj │ │ └── hourglass │ │ └── in │ │ └── base │ │ └── ExampleInstrumentedTest.java ├── proguard-rules.pro ├── build.gradle └── app.iml ├── settings.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── News.iml ├── Base2.iml ├── Base.iml ├── .gitignore ├── gradlew.bat └── gradlew /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | -------------------------------------------------------------------------------- /app/src/test/resources/mocikito-extensions/org.mockito.plugins.mockmaker: -------------------------------------------------------------------------------- 1 | mock-maker-inline -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lingarajsankaravelu/News/master/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lingarajsankaravelu/News/master/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lingarajsankaravelu/News/master/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lingarajsankaravelu/News/master/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lingarajsankaravelu/News/master/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lingarajsankaravelu/News/master/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lingarajsankaravelu/News/master/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lingarajsankaravelu/News/master/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lingarajsankaravelu/News/master/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lingarajsankaravelu/News/master/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lingarajsankaravelu/News/master/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/java/lingaraj/hourglass/in/base/ui/ScreenProvider.java: -------------------------------------------------------------------------------- 1 | package lingaraj.hourglass.in.base.ui; 2 | 3 | import android.support.v4.app.Fragment; 4 | 5 | public interface ScreenProvider { 6 | Fragment initialScreen(); 7 | } 8 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Mar 18 17:11:19 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 | -------------------------------------------------------------------------------- /app/src/main/java/lingaraj/hourglass/in/base/api/response/Source.java: -------------------------------------------------------------------------------- 1 | package lingaraj.hourglass.in.base.api.response; 2 | 3 | import java.io.Serializable; 4 | 5 | public class Source implements Serializable { 6 | private String name; 7 | 8 | public String getName() { 9 | return name; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/java/lingaraj/hourglass/in/base/di/ApplicationScope.java: -------------------------------------------------------------------------------- 1 | package lingaraj.hourglass.in.base.di; 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.CLASS) 10 | public @interface ApplicationScope { 11 | } -------------------------------------------------------------------------------- /app/src/main/java/lingaraj/hourglass/in/base/di/ActivityScope.java: -------------------------------------------------------------------------------- 1 | package lingaraj.hourglass.in.base.di; 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 ActivityScope { 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/java/lingaraj/hourglass/in/base/di/ScreenScope.java: -------------------------------------------------------------------------------- 1 | package lingaraj.hourglass.in.base.di; 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 ScreenScope { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/java/lingaraj/hourglass/in/base/api/response/Status.java: -------------------------------------------------------------------------------- 1 | package lingaraj.hourglass.in.base.api.response; 2 | 3 | public class Status { 4 | private boolean error; 5 | private String message; 6 | 7 | public boolean isError() { 8 | return error; 9 | } 10 | 11 | public String getMessage() { 12 | return message; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/lingaraj/hourglass/in/base/di/ScreenComponent.java: -------------------------------------------------------------------------------- 1 | package lingaraj.hourglass.in.base.di; 2 | 3 | import dagger.android.AndroidInjector; 4 | import lingaraj.hourglass.in.base.lifecycle.DisposableManager; 5 | 6 | public interface ScreenComponent extends AndroidInjector { 7 | 8 | @ScreenScope 9 | DisposableManager disposableManager(); 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/java/lingaraj/hourglass/in/base/database/AppDatabase.java: -------------------------------------------------------------------------------- 1 | package lingaraj.hourglass.in.base.database; 2 | 3 | import android.arch.persistence.room.Database; 4 | import android.arch.persistence.room.RoomDatabase; 5 | 6 | @Database(entities = { Article.class}, version = 1, exportSchema = false) 7 | public abstract class AppDatabase extends RoomDatabase { 8 | public abstract ArticlesDao articlesDao(); 9 | 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Base 3 | Getting things for you 4 | Please Try again! 5 | Milan reportedly frightened by Richarlisons high price 6 | Lingaraj 7 | %1d h 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/java/lingaraj/hourglass/in/base/di/Names.java: -------------------------------------------------------------------------------- 1 | package lingaraj.hourglass.in.base.di; 2 | 3 | public class Names { 4 | public static final String NAMED_ERROR_COMMON = "ERROR_MESSAGE_COMMON"; 5 | 6 | public static final String NAMED_LOADING = "MESSAGE_COMMON_LOADING"; 7 | 8 | public static final String ASESSED_NETWORKING_REST = "ASSESSED_NETWORKING_REST"; 9 | public static final String BASE_URL = "BASE_URL"; 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/java/lingaraj/hourglass/in/base/api/APIRouter.java: -------------------------------------------------------------------------------- 1 | package lingaraj.hourglass.in.base.api; 2 | 3 | import io.reactivex.Single; 4 | import lingaraj.hourglass.in.base.api.response.NewsResponse; 5 | import retrofit2.http.GET; 6 | import retrofit2.http.Query; 7 | 8 | public interface APIRouter { 9 | 10 | @GET("/v2/top-headlines") 11 | Single getResults(@Query("country") String country,@Query("apiKey") String apiKey); 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/round_edged_shape.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16sp 4 | 20sp 5 | 16dp 6 | 8dp 7 | 16sp 8 | 12sp 9 | 10sp 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/java/lingaraj/hourglass/in/base/ui/ActivityViewInterceptorModule.java: -------------------------------------------------------------------------------- 1 | package lingaraj.hourglass.in.base.ui; 2 | 3 | import dagger.Module; 4 | import dagger.Provides; 5 | import lingaraj.hourglass.in.base.ui.ActivityViewInterceptor; 6 | 7 | @Module 8 | public abstract class ActivityViewInterceptorModule { 9 | 10 | @Provides 11 | static ActivityViewInterceptor provideActivityViewInterceptor() { 12 | return ActivityViewInterceptor.DEFAULT; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_webview.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/java/lingaraj/hourglass/in/base/utils/Constants.java: -------------------------------------------------------------------------------- 1 | package lingaraj.hourglass.in.base.utils; 2 | 3 | public class Constants { 4 | 5 | public static final String INSTANCE_ID = "instance_id"; 6 | public static final String SHARED_PREFERENCE_NAME = "BASEAPPSHAREDPREFERENCE"; 7 | public static final String BASE_URL = "https://newsapi.org/"; 8 | public static final String API_KEY = "def5b5bdf0464343bbd5d8f8ef99f858"; 9 | public static final String COUNTRY_INDIA = "in"; 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/java/lingaraj/hourglass/in/base/api/di/APIModule.java: -------------------------------------------------------------------------------- 1 | package lingaraj.hourglass.in.base.api.di; 2 | 3 | import dagger.Module; 4 | import dagger.Provides; 5 | import javax.inject.Singleton; 6 | import lingaraj.hourglass.in.base.api.APIRouter; 7 | import retrofit2.Retrofit; 8 | 9 | @Module 10 | public abstract class APIModule { 11 | @Singleton 12 | @Provides 13 | static APIRouter provideAPIRouter(Retrofit retrofit){ 14 | return retrofit.create(APIRouter.class); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/lingaraj/hourglass/in/base/ui/ScreenNavigator.java: -------------------------------------------------------------------------------- 1 | package lingaraj.hourglass.in.base.ui; 2 | 3 | import android.support.v4.app.Fragment; 4 | 5 | import java.util.List; 6 | 7 | public interface ScreenNavigator { 8 | 9 | boolean pop(); 10 | 11 | void onBackPressed(); 12 | 13 | void finish(); 14 | 15 | void showFragment(Fragment tobeHidden,String fragmentTag); 16 | 17 | void loadFragments(Fragment activeFragment, List hiddenFragmentList); 18 | 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/lingaraj/hourglass/in/base/home/articlesFragment/di/HomeFragmentScreenModule.java: -------------------------------------------------------------------------------- 1 | package lingaraj.hourglass.in.base.home.articlesFragment.di; 2 | 3 | import dagger.Binds; 4 | import dagger.Module; 5 | import dagger.multibindings.IntoSet; 6 | import lingaraj.hourglass.in.base.lifecycle.ScreenLifecycleTask; 7 | 8 | @Module 9 | public abstract class HomeFragmentScreenModule { 10 | 11 | @Binds 12 | @IntoSet 13 | abstract ScreenLifecycleTask bindUiManagerTask(HomeFragmentUIManager homeFragmentUIManager); 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_refresh.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/java/lingaraj/hourglass/in/base/database/DatabaseModule.java: -------------------------------------------------------------------------------- 1 | package lingaraj.hourglass.in.base.database; 2 | 3 | import android.arch.persistence.room.Room; 4 | import android.content.Context; 5 | import dagger.Module; 6 | import dagger.Provides; 7 | import javax.inject.Singleton; 8 | 9 | @Module 10 | public class DatabaseModule { 11 | @Provides 12 | @Singleton 13 | static AppDatabase provideAppDatabase(Context context){ 14 | return Room.databaseBuilder(context,AppDatabase.class,"news-room-database").allowMainThreadQueries().build(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/lingaraj/hourglass/in/base/lifecycle/DisposableManager.java: -------------------------------------------------------------------------------- 1 | package lingaraj.hourglass.in.base.lifecycle; 2 | 3 | import io.reactivex.disposables.CompositeDisposable; 4 | import io.reactivex.disposables.Disposable; 5 | 6 | public class DisposableManager { 7 | 8 | private final CompositeDisposable compositeDisposable = new CompositeDisposable(); 9 | 10 | public void add(Disposable... disposables) { 11 | compositeDisposable.addAll(disposables); 12 | } 13 | 14 | public void dispose() { 15 | compositeDisposable.clear(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/lingaraj/hourglass/in/base/utils/ButterKnifeUtils.java: -------------------------------------------------------------------------------- 1 | package lingaraj.hourglass.in.base.utils; 2 | 3 | import butterknife.Unbinder; 4 | import timber.log.Timber; 5 | 6 | public class ButterKnifeUtils { 7 | 8 | private ButterKnifeUtils() { 9 | 10 | } 11 | 12 | public static void unbind(Unbinder unbinder) { 13 | if (unbinder != null) { 14 | try { 15 | unbinder.unbind(); 16 | } catch (IllegalStateException e) { 17 | Timber.e(e, "Error unbinding views"); 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/lingaraj/hourglass/in/base/ui/NavigationModule.java: -------------------------------------------------------------------------------- 1 | package lingaraj.hourglass.in.base.ui; 2 | 3 | import dagger.Binds; 4 | import dagger.Module; 5 | import dagger.multibindings.IntoSet; 6 | import lingaraj.hourglass.in.base.lifecycle.ActivityLifecycleTask; 7 | 8 | @Module 9 | public abstract class NavigationModule { 10 | 11 | @Binds 12 | abstract ScreenNavigator provideScreenNavigator(DefaultScreenNavigator screenNavigator); 13 | 14 | @Binds 15 | @IntoSet 16 | abstract ActivityLifecycleTask bindScreenNavigatorTask(DefaultScreenNavigator screenNavigator); 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/lingaraj/hourglass/in/base/api/response/NewsResponse.java: -------------------------------------------------------------------------------- 1 | package lingaraj.hourglass.in.base.api.response; 2 | 3 | import java.io.Serializable; 4 | import java.util.List; 5 | import lingaraj.hourglass.in.base.database.Article; 6 | 7 | public class NewsResponse implements Serializable { 8 | private String status; 9 | private int totalResults; 10 | private List
articles; 11 | 12 | public String getStatus() { 13 | return status; 14 | } 15 | 16 | public int getTotalResults() { 17 | return totalResults; 18 | } 19 | 20 | public List
getArticles() { 21 | return articles; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/java/lingaraj/hourglass/in/base/home/HomeActivity.java: -------------------------------------------------------------------------------- 1 | package lingaraj.hourglass.in.base.home; 2 | 3 | import android.support.v4.app.Fragment; 4 | import lingaraj.hourglass.in.base.R; 5 | import lingaraj.hourglass.in.base.core.BaseActivity; 6 | import lingaraj.hourglass.in.base.home.articlesFragment.ArticlesFragment; 7 | import timber.log.Timber; 8 | 9 | public class HomeActivity extends BaseActivity { 10 | 11 | @Override protected int layoutRes() { 12 | Timber.d("Layout Supplied"); 13 | return R.layout.activity_main; 14 | } 15 | 16 | @Override public Fragment initialScreen() { 17 | return ArticlesFragment.newInstance(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/lingaraj/hourglass/in/base/ui/ActivityViewInterceptor.java: -------------------------------------------------------------------------------- 1 | package lingaraj.hourglass.in.base.ui; 2 | 3 | import android.app.Activity; 4 | import android.support.annotation.LayoutRes; 5 | 6 | public interface ActivityViewInterceptor { 7 | 8 | void setContentView(Activity activity, @LayoutRes int layoutRes); 9 | 10 | void clear(); 11 | 12 | ActivityViewInterceptor DEFAULT = new ActivityViewInterceptor() { 13 | @Override 14 | public void setContentView(Activity activity, int layoutRes) { 15 | activity.setContentView(layoutRes); 16 | } 17 | 18 | @Override 19 | public void clear() { 20 | 21 | } 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/lingaraj/hourglass/in/base/database/ArticlesDao.java: -------------------------------------------------------------------------------- 1 | package lingaraj.hourglass.in.base.database; 2 | 3 | import android.arch.lifecycle.LiveData; 4 | import android.arch.lifecycle.MutableLiveData; 5 | import android.arch.persistence.room.Dao; 6 | import android.arch.persistence.room.Insert; 7 | import android.arch.persistence.room.OnConflictStrategy; 8 | import android.arch.persistence.room.Query; 9 | import java.util.List; 10 | 11 | @Dao 12 | public interface ArticlesDao { 13 | 14 | @Insert(onConflict = OnConflictStrategy.IGNORE) 15 | void insertArticles(List
articles); 16 | 17 | @Query("SELECT * FROM "+Article.TABLE_NAME) 18 | LiveData> getArticles(); 19 | 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/lingaraj/hourglass/in/base/lifecycle/ActivityLifecycleTask.java: -------------------------------------------------------------------------------- 1 | package lingaraj.hourglass.in.base.lifecycle; 2 | 3 | import android.support.v7.app.AppCompatActivity; 4 | 5 | public abstract class ActivityLifecycleTask { 6 | 7 | public void onCreate(AppCompatActivity activity) { 8 | 9 | } 10 | 11 | public void onStart(AppCompatActivity activity) { 12 | 13 | } 14 | 15 | public void onResume(AppCompatActivity activity) { 16 | 17 | } 18 | 19 | public void onPause(AppCompatActivity activity) { 20 | 21 | } 22 | 23 | public void onStop(AppCompatActivity activity) { 24 | 25 | } 26 | 27 | public void onDestroy(AppCompatActivity activity) { 28 | 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/lingaraj/hourglass/in/base/api/di/APIRequester.java: -------------------------------------------------------------------------------- 1 | package lingaraj.hourglass.in.base.api.di; 2 | 3 | import io.reactivex.Single; 4 | import javax.inject.Inject; 5 | import lingaraj.hourglass.in.base.api.APIRouter; 6 | import lingaraj.hourglass.in.base.api.response.NewsResponse; 7 | import lingaraj.hourglass.in.base.api.response.Status; 8 | import lingaraj.hourglass.in.base.utils.Constants; 9 | 10 | public class APIRequester { 11 | private final APIRouter apiRouter; 12 | 13 | @Inject 14 | APIRequester(APIRouter router){ 15 | this.apiRouter = router; 16 | } 17 | 18 | public Single getResult(){ 19 | return apiRouter.getResults(Constants.COUNTRY_INDIA,Constants.API_KEY); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/lingaraj/hourglass/in/base/core/ScreenModule.java: -------------------------------------------------------------------------------- 1 | package lingaraj.hourglass.in.base.core; 2 | 3 | import java.util.Set; 4 | 5 | import dagger.Module; 6 | import dagger.Provides; 7 | import dagger.multibindings.Multibinds; 8 | import lingaraj.hourglass.in.base.di.ScreenScope; 9 | import lingaraj.hourglass.in.base.lifecycle.DisposableManager; 10 | import lingaraj.hourglass.in.base.lifecycle.ScreenLifecycleTask; 11 | 12 | @Module 13 | public abstract class ScreenModule { 14 | 15 | @ScreenScope 16 | @Provides 17 | static DisposableManager provideDisposableManager() { 18 | return new DisposableManager(); 19 | } 20 | 21 | @Multibinds 22 | abstract Set screenLifecycleTasks(); 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/lingaraj/hourglass/in/base/api/NewsAPIRequester.java: -------------------------------------------------------------------------------- 1 | package lingaraj.hourglass.in.base.api; 2 | 3 | import io.reactivex.Flowable; 4 | import io.reactivex.Single; 5 | import java.util.List; 6 | import javax.inject.Inject; 7 | import lingaraj.hourglass.in.base.api.response.NewsResponse; 8 | import lingaraj.hourglass.in.base.database.Article; 9 | import lingaraj.hourglass.in.base.utils.Constants; 10 | 11 | public class NewsAPIRequester { 12 | private final APIRouter router; 13 | 14 | @Inject 15 | NewsAPIRequester(APIRouter apiRouter){ 16 | this.router = apiRouter; 17 | } 18 | 19 | public Single getArticles(){ 20 | return router.getResults(Constants.COUNTRY_INDIA,Constants.API_KEY).doOnError(error->{}); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/lingaraj/hourglass/in/base/home/preview/di/PreviewComponent.java: -------------------------------------------------------------------------------- 1 | package lingaraj.hourglass.in.base.home.preview.di; 2 | 3 | import dagger.Subcomponent; 4 | import dagger.android.AndroidInjector; 5 | import lingaraj.hourglass.in.base.core.ScreenModule; 6 | import lingaraj.hourglass.in.base.di.ScreenComponent; 7 | import lingaraj.hourglass.in.base.di.ScreenScope; 8 | import lingaraj.hourglass.in.base.home.preview.WebLinkPreviewFragment; 9 | 10 | @ScreenScope 11 | @Subcomponent(modules = { ScreenModule.class }) 12 | public abstract interface PreviewComponent extends ScreenComponent { 13 | 14 | @Subcomponent.Builder 15 | abstract class Builder extends AndroidInjector.Builder{ 16 | 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/lingaraj/hourglass/in/base/core/ApplicationComponent.java: -------------------------------------------------------------------------------- 1 | package lingaraj.hourglass.in.base.core; 2 | 3 | import dagger.Component; 4 | import javax.inject.Singleton; 5 | import lingaraj.hourglass.in.base.BaseApp; 6 | import lingaraj.hourglass.in.base.api.di.APIModule; 7 | import lingaraj.hourglass.in.base.database.DatabaseModule; 8 | 9 | /** 10 | * All singleton Modules goes here i.e application,ActivityBindingModule, database and network module 11 | */ 12 | @Singleton 13 | @Component(modules = { 14 | ApplicationModule.class, 15 | ActivityBindingModule.class, 16 | DatabaseModule.class, 17 | NetworkModule.class, 18 | APIModule.class 19 | 20 | }) 21 | public interface ApplicationComponent { 22 | void inject(BaseApp application); 23 | } 24 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/java/lingaraj/hourglass/in/base/home/articlesFragment/di/HomeFragmentComponent.java: -------------------------------------------------------------------------------- 1 | package lingaraj.hourglass.in.base.home.articlesFragment.di; 2 | 3 | import dagger.Subcomponent; 4 | import dagger.android.AndroidInjector; 5 | import lingaraj.hourglass.in.base.core.ScreenModule; 6 | import lingaraj.hourglass.in.base.di.ScreenComponent; 7 | import lingaraj.hourglass.in.base.di.ScreenScope; 8 | import lingaraj.hourglass.in.base.home.articlesFragment.ArticlesFragment; 9 | 10 | @ScreenScope 11 | @Subcomponent(modules = { ScreenModule.class,HomeFragmentScreenModule.class,}) 12 | public interface HomeFragmentComponent extends ScreenComponent { 13 | 14 | @Subcomponent.Builder 15 | abstract class Builder extends AndroidInjector.Builder{ 16 | 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/lingaraj/hourglass/in/base/di/Injector.java: -------------------------------------------------------------------------------- 1 | package lingaraj.hourglass.in.base.di; 2 | 3 | import android.app.Activity; 4 | import android.support.v4.app.Fragment; 5 | 6 | public class Injector { 7 | 8 | private Injector() { 9 | 10 | } 11 | 12 | public static void inject(Activity activity) { 13 | ActivityInjector.get(activity).inject(activity); 14 | } 15 | 16 | public static void clearComponent(Activity activity) { 17 | ActivityInjector.get(activity).clear(activity); 18 | } 19 | 20 | public static void inject(Fragment fragment) { 21 | ScreenInjector.get(fragment.getActivity()).inject(fragment); 22 | } 23 | 24 | public static void clearComponent(Fragment fragment) { 25 | ScreenInjector.get(fragment.getActivity()).clear(fragment); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/lingaraj/hourglass/in/base/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package lingaraj.hourglass.in.base; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) public class ExampleInstrumentedTest { 18 | @Test public void useAppContext() { 19 | // Context of the app under test. 20 | Context appContext = InstrumentationRegistry.getTargetContext(); 21 | 22 | assertEquals("lingaraj.hourglass.in.base", appContext.getPackageName()); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/lingaraj/hourglass/in/base/core/ActivityBindingModule.java: -------------------------------------------------------------------------------- 1 | package lingaraj.hourglass.in.base.core; 2 | 3 | 4 | import android.app.Activity; 5 | import dagger.Binds; 6 | import dagger.Module; 7 | import dagger.android.ActivityKey; 8 | import dagger.android.AndroidInjector; 9 | import dagger.multibindings.IntoMap; 10 | import lingaraj.hourglass.in.base.home.HomeActivity; 11 | import lingaraj.hourglass.in.base.home.di.HomeActivityComponent; 12 | 13 | /** 14 | * All activities component get binded to dagger map here. 15 | */ 16 | @Module(subcomponents = { 17 | HomeActivityComponent.class 18 | }) 19 | public abstract class ActivityBindingModule { 20 | 21 | @Binds 22 | @IntoMap 23 | @ActivityKey(HomeActivity.class) 24 | abstract AndroidInjector.Factory provideMainActivityInjector(HomeActivityComponent.Builder builder); 25 | 26 | } 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/java/lingaraj/hourglass/in/base/lifecycle/ScreenLifecycleTask.java: -------------------------------------------------------------------------------- 1 | package lingaraj.hourglass.in.base.lifecycle; 2 | 3 | import android.view.View; 4 | import lingaraj.hourglass.in.base.di.ActivityScope; 5 | 6 | public abstract class ScreenLifecycleTask { 7 | 8 | /** 9 | * Callback received when a Screen becomes the visible screen. 10 | */ 11 | public void onEnterScope(View view) { 12 | 13 | } 14 | 15 | /** 16 | * Callback received when a Screen is either popped of moved to the back stack. 17 | */ 18 | public void onExitScope() { 19 | 20 | } 21 | 22 | /** 23 | * Callback received when a Screen is destroyed and will not be coming back (except as a new instance). This should 24 | * be used to clear any {@link ActivityScope} connections (Disposables, etc). 25 | */ 26 | public void onDestroy() { 27 | 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/src/test/java/lingaraj/hourglass/in/testutils/TestUtil.java: -------------------------------------------------------------------------------- 1 | package lingaraj.hourglass.in.testutils; 2 | 3 | import com.google.gson.Gson; 4 | import java.io.InputStream; 5 | import lingaraj.hourglass.in.base.api.response.NewsResponse; 6 | 7 | public class TestUtil { 8 | 9 | public static NewsResponse getTestResponse(){ 10 | try { 11 | InputStream inputStream = TestUtil.class.getClassLoader().getResourceAsStream( 12 | "mocks/articles.json"); 13 | int size = inputStream.available(); 14 | byte[] buffer = new byte[size]; 15 | inputStream.read(buffer); 16 | inputStream.close(); 17 | String articles_json = new String(buffer, "UTF-8"); 18 | Gson gson = new Gson(); 19 | return gson.fromJson(articles_json,NewsResponse.class); 20 | } 21 | catch (Exception e){ 22 | return null; 23 | } 24 | 25 | } 26 | 27 | 28 | 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/lingaraj/hourglass/in/base/core/BaseViewModelFactory.java: -------------------------------------------------------------------------------- 1 | package lingaraj.hourglass.in.base.core; 2 | 3 | import android.arch.lifecycle.ViewModel; 4 | import android.arch.lifecycle.ViewModelProvider; 5 | import android.support.annotation.NonNull; 6 | import java.util.Map; 7 | import javax.inject.Inject; 8 | import javax.inject.Provider; 9 | 10 | public class BaseViewModelFactory implements ViewModelProvider.Factory { 11 | 12 | private final Map, Provider> mProviderMap; 13 | 14 | @Inject 15 | BaseViewModelFactory(Map, Provider> providerMap) { 16 | mProviderMap = providerMap; 17 | } 18 | 19 | @SuppressWarnings("unchecked") 20 | @NonNull 21 | @Override 22 | public T create(@NonNull Class modelClass) { 23 | return (T) mProviderMap.get(modelClass).get(); 24 | } 25 | } -------------------------------------------------------------------------------- /News.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /Base2.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/java/lingaraj/hourglass/in/base/utils/General.java: -------------------------------------------------------------------------------- 1 | package lingaraj.hourglass.in.base.utils; 2 | 3 | import android.support.annotation.NonNull; 4 | import java.text.SimpleDateFormat; 5 | import java.util.Calendar; 6 | import java.util.Date; 7 | import timber.log.Timber; 8 | 9 | public class General { 10 | 11 | public static int getHourDifference(@NonNull String timeStamp){ 12 | try { 13 | SimpleDateFormat inputFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); 14 | Date date = inputFormat.parse(timeStamp); 15 | Calendar calendar = Calendar.getInstance(); 16 | long current_time_in_milliseconds = calendar.getTimeInMillis(); 17 | calendar.setTime(date); 18 | return (int) Math.abs(current_time_in_milliseconds - calendar.getTimeInMillis()); 19 | } 20 | catch (Exception e) { 21 | Timber.d(e.toString()); 22 | return 0; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 15 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/java/lingaraj/hourglass/in/base/home/di/HomeActivityComponent.java: -------------------------------------------------------------------------------- 1 | package lingaraj.hourglass.in.base.home.di; 2 | 3 | import dagger.Subcomponent; 4 | import dagger.android.AndroidInjector; 5 | import lingaraj.hourglass.in.base.core.AppViewModelsModule; 6 | import lingaraj.hourglass.in.base.di.ActivityScope; 7 | import lingaraj.hourglass.in.base.home.HomeActivity; 8 | import lingaraj.hourglass.in.base.ui.ActivityViewInterceptorModule; 9 | import lingaraj.hourglass.in.base.ui.NavigationModule; 10 | 11 | @ActivityScope 12 | @Subcomponent(modules = { AppViewModelsModule.class , ActivityViewInterceptorModule.class,HomeBindingModule.class, 13 | NavigationModule.class 14 | }) 15 | public abstract interface HomeActivityComponent extends AndroidInjector { 16 | 17 | @Subcomponent.Builder abstract class Builder extends AndroidInjector.Builder{ 18 | 19 | @Override public void seedInstance(HomeActivity instance) { 20 | 21 | } 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /Base.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | /*/build/ 3 | 4 | # Crashlytics configuations 5 | com_crashlytics_export_strings.xml 6 | 7 | # Local configuration file (sdk path, etc) 8 | local.properties 9 | 10 | # Gradle generated files 11 | .gradle/ 12 | 13 | # Signing files 14 | .signing/ 15 | 16 | # User-specific configurations 17 | .idea/ 18 | .idea/libraries/ 19 | .idea/workspace.xml 20 | .idea/tasks.xml 21 | .idea/.name 22 | .idea/compiler.xml 23 | .idea/copyright/profiles_settings.xml 24 | .idea/encodings.xml 25 | .idea/misc.xml 26 | .idea/modules.xml 27 | .idea/scopes/scope_settings.xml 28 | .idea/vcs.xml 29 | .idea/caches/ 30 | .idea/codeStyles/ 31 | 32 | # OS-specific files 33 | .DS_Store 34 | .DS_Store? 35 | ._* 36 | .Spotlight-V100 37 | .Trashes 38 | ehthumbs.db 39 | Thumbs.db 40 | 41 | app/.idea/workspace.xml 42 | build/intermediates/dex-cache/cache.xml 43 | build/android-profile/* 44 | build/generated/* 45 | build/intermediates/progurad-files/* 46 | 47 | /openCVLibrary310 48 | openCVLibrary310/build.gradle 49 | -------------------------------------------------------------------------------- /app/src/main/java/lingaraj/hourglass/in/base/home/articlesFragment/di/HomeFragmentUIManager.java: -------------------------------------------------------------------------------- 1 | package lingaraj.hourglass.in.base.home.articlesFragment.di; 2 | 3 | import android.view.View; 4 | import butterknife.ButterKnife; 5 | import butterknife.Unbinder; 6 | import javax.inject.Inject; 7 | import lingaraj.hourglass.in.base.lifecycle.ScreenLifecycleTask; 8 | import lingaraj.hourglass.in.base.ui.ScreenNavigator; 9 | import lingaraj.hourglass.in.base.utils.ButterKnifeUtils; 10 | 11 | public class HomeFragmentUIManager extends ScreenLifecycleTask { 12 | 13 | private ScreenNavigator navigator; 14 | private Unbinder unbinder; 15 | 16 | 17 | @Inject 18 | HomeFragmentUIManager(ScreenNavigator screenNavigator){ 19 | navigator = screenNavigator; 20 | } 21 | 22 | @Override public void onEnterScope(View view) { 23 | super.onEnterScope(view); 24 | unbinder = ButterKnife.bind(view); 25 | } 26 | 27 | @Override public void onExitScope() { 28 | super.onExitScope(); 29 | ButterKnifeUtils.unbind(unbinder); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/java/lingaraj/hourglass/in/base/BaseApp.java: -------------------------------------------------------------------------------- 1 | package lingaraj.hourglass.in.base; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | import com.jakewharton.threetenabp.AndroidThreeTen; 6 | import javax.inject.Inject; 7 | import lingaraj.hourglass.in.base.core.ApplicationComponent; 8 | import lingaraj.hourglass.in.base.core.ApplicationModule; 9 | import lingaraj.hourglass.in.base.core.DaggerApplicationComponent; 10 | import lingaraj.hourglass.in.base.di.ActivityInjector; 11 | import timber.log.Timber; 12 | 13 | public class BaseApp extends Application { 14 | private ApplicationComponent component; 15 | @Inject ActivityInjector activityInjector; 16 | private String currentImage; 17 | private String currentDiagram; 18 | 19 | @Override public void onCreate() { 20 | super.onCreate(); 21 | 22 | if(BuildConfig.DEBUG) { 23 | Timber.plant(new Timber.DebugTree()); 24 | } 25 | AndroidThreeTen.init(this); 26 | component = DaggerApplicationComponent.builder().applicationModule(new ApplicationModule(this)).build(); 27 | component.inject(this); 28 | 29 | } 30 | 31 | 32 | public ActivityInjector getActivityInjector() { 33 | return activityInjector; 34 | } 35 | 36 | 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #008577 4 | #00574B 5 | #D81B60 6 | 7 | #77009688 8 | #FF4081 9 | 10 | #334E70 11 | 12 | #63CCC8 13 | #F1F1F2 14 | #63CCC8 15 | #59B8B4 16 | #00ACC1 17 | #FFFFFF 18 | #EEE7E0 19 | #F5F5F5 20 | #c9c9c9 21 | #262626 22 | #C7C7C7 23 | 24 | 25 | #616161 26 | #9E9E9E 27 | #F44336 28 | #616161 29 | #757575 30 | 31 | 32 | -------------------------------------------------------------------------------- /app/src/main/java/lingaraj/hourglass/in/base/home/di/HomeBindingModule.java: -------------------------------------------------------------------------------- 1 | package lingaraj.hourglass.in.base.home.di; 2 | 3 | import android.support.v4.app.Fragment; 4 | import butterknife.BindView; 5 | import dagger.Binds; 6 | import dagger.Module; 7 | import dagger.android.AndroidInjector; 8 | import dagger.android.support.FragmentKey; 9 | import dagger.multibindings.IntoMap; 10 | import lingaraj.hourglass.in.base.home.articlesFragment.ArticlesFragment; 11 | import lingaraj.hourglass.in.base.home.articlesFragment.di.HomeFragmentComponent; 12 | import lingaraj.hourglass.in.base.home.preview.WebLinkPreviewFragment; 13 | import lingaraj.hourglass.in.base.home.preview.di.PreviewComponent; 14 | 15 | /** 16 | * Component of all the fragments component which is added to HomeActivity goes here 17 | */ 18 | @Module(subcomponents = { HomeFragmentComponent.class, PreviewComponent.class }) 19 | public abstract class HomeBindingModule { 20 | 21 | @Binds 22 | @IntoMap 23 | @FragmentKey(ArticlesFragment.class) 24 | abstract AndroidInjector.Factory bindHomeFragment(HomeFragmentComponent.Builder builder); 25 | 26 | @Binds 27 | @IntoMap 28 | @FragmentKey(WebLinkPreviewFragment.class) 29 | abstract AndroidInjector.Factory bindWebPreviewFragment(PreviewComponent.Builder builder); 30 | 31 | 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/lingaraj/hourglass/in/base/core/ApplicationModule.java: -------------------------------------------------------------------------------- 1 | package lingaraj.hourglass.in.base.core; 2 | 3 | import android.content.Context; 4 | import com.google.gson.Gson; 5 | import dagger.Module; 6 | import dagger.Provides; 7 | import javax.inject.Named; 8 | import lingaraj.hourglass.in.base.BaseApp; 9 | import lingaraj.hourglass.in.base.R; 10 | import lingaraj.hourglass.in.base.di.Names; 11 | 12 | @Module 13 | public class ApplicationModule { 14 | 15 | private final BaseApp application; 16 | private final String errorMessageCommon; 17 | private final String messageCommonLoading; 18 | private final Gson gson; 19 | 20 | 21 | public ApplicationModule(BaseApp application) { 22 | this.application = application; 23 | this.errorMessageCommon = application.getString(R.string.error_message_common); 24 | this.messageCommonLoading = application.getString(R.string.message_common_loading); 25 | this.gson = new Gson(); 26 | } 27 | 28 | @Provides 29 | Gson providesGson(){ 30 | return this.gson; 31 | } 32 | 33 | @Provides 34 | Context provideApplicationContext() { 35 | return application; 36 | } 37 | 38 | @Provides @Named(Names.NAMED_ERROR_COMMON) String providesErrorMessageCommon(){ 39 | return this.errorMessageCommon; 40 | } 41 | 42 | @Provides @Named(Names.NAMED_LOADING) String providesMessageLoading(){ 43 | return this.messageCommonLoading; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /app/src/main/java/lingaraj/hourglass/in/base/core/AppViewModelsModule.java: -------------------------------------------------------------------------------- 1 | package lingaraj.hourglass.in.base.core; 2 | 3 | import android.arch.lifecycle.ViewModel; 4 | import dagger.MapKey; 5 | import dagger.Module; 6 | import dagger.Provides; 7 | import dagger.multibindings.IntoMap; 8 | import java.lang.annotation.ElementType; 9 | import java.lang.annotation.Retention; 10 | import java.lang.annotation.RetentionPolicy; 11 | import java.lang.annotation.Target; 12 | import java.util.Map; 13 | import javax.inject.Named; 14 | import javax.inject.Provider; 15 | import lingaraj.hourglass.in.base.di.Names; 16 | import lingaraj.hourglass.in.base.home.articlesFragment.ArticleFragmentRepository; 17 | import lingaraj.hourglass.in.base.home.articlesFragment.ArticlesViewModel; 18 | 19 | /** 20 | * All view models binded to dagger here 21 | */ 22 | 23 | @Module 24 | public class AppViewModelsModule { 25 | 26 | @Target(ElementType.METHOD) 27 | @Retention(RetentionPolicy.RUNTIME) 28 | @MapKey 29 | @interface ViewModelKey { 30 | Class value(); 31 | } 32 | 33 | @Provides BaseViewModelFactory viewModelFactory( 34 | Map, Provider> providerMap) { 35 | return new BaseViewModelFactory(providerMap); 36 | } 37 | 38 | 39 | @Provides 40 | @IntoMap 41 | @ViewModelKey(ArticlesViewModel.class) 42 | ViewModel providesHomeFragmentViewModel(ArticleFragmentRepository repository,@Named(Names.NAMED_ERROR_COMMON) String errorCommon) { 43 | return new ArticlesViewModel(repository,errorCommon); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/src/main/java/lingaraj/hourglass/in/base/home/articlesFragment/ArticleFragmentRepository.java: -------------------------------------------------------------------------------- 1 | package lingaraj.hourglass.in.base.home.articlesFragment; 2 | 3 | import android.arch.lifecycle.LiveData; 4 | import android.arch.lifecycle.MutableLiveData; 5 | import io.reactivex.Completable; 6 | import io.reactivex.Single; 7 | import io.reactivex.functions.Action; 8 | import io.reactivex.schedulers.Schedulers; 9 | import java.util.List; 10 | import javax.inject.Inject; 11 | import javax.inject.Provider; 12 | import lingaraj.hourglass.in.base.api.NewsAPIRequester; 13 | import lingaraj.hourglass.in.base.api.response.NewsResponse; 14 | import lingaraj.hourglass.in.base.database.AppDatabase; 15 | import lingaraj.hourglass.in.base.database.Article; 16 | 17 | public class ArticleFragmentRepository { 18 | 19 | private final AppDatabase database; 20 | private final Provider api_requester; 21 | 22 | 23 | @Inject 24 | ArticleFragmentRepository(Provider apiRequesterProvider,AppDatabase appDatabase){ 25 | this.database = appDatabase; 26 | this.api_requester = apiRequesterProvider; 27 | } 28 | 29 | 30 | LiveData> getArticles(){ 31 | return database.articlesDao().getArticles(); 32 | } 33 | 34 | 35 | Single refreshArticles() { 36 | return api_requester.get().getArticles().subscribeOn(Schedulers.io()).map(newsResponse -> { 37 | if (newsResponse!=null && newsResponse.getArticles().size()>0){ ; 38 | Completable.fromAction(new Action() { 39 | @Override public void run() throws Exception { 40 | database.articlesDao().insertArticles(newsResponse.getArticles()); 41 | } 42 | }).subscribeOn(Schedulers.computation()).subscribe(); 43 | } 44 | return newsResponse; 45 | }); 46 | } 47 | 48 | 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/java/lingaraj/hourglass/in/base/database/Article.java: -------------------------------------------------------------------------------- 1 | package lingaraj.hourglass.in.base.database; 2 | 3 | import android.arch.persistence.room.Entity; 4 | import android.arch.persistence.room.PrimaryKey; 5 | import com.google.gson.annotations.SerializedName; 6 | import java.io.Serializable; 7 | import lingaraj.hourglass.in.base.api.response.Source; 8 | import lingaraj.hourglass.in.base.utils.Constants; 9 | 10 | @Entity(tableName = Article.TABLE_NAME) 11 | public class Article { 12 | 13 | public static final String TABLE_NAME ="ARTICLES"; 14 | 15 | @PrimaryKey(autoGenerate = true) 16 | private long id; 17 | 18 | private String author; 19 | 20 | private String title; 21 | 22 | private String description; 23 | private String url; 24 | private String urlToImage; 25 | private String publishedAt; 26 | 27 | public String getAuthor() { 28 | return author; 29 | } 30 | 31 | public void setAuthor(String author) { 32 | this.author = author; 33 | } 34 | 35 | public void setTitle(String title) { 36 | this.title = title; 37 | } 38 | 39 | public void setDescription(String description) { 40 | this.description = description; 41 | } 42 | 43 | public void setUrl(String url) { 44 | this.url = url; 45 | } 46 | 47 | public void setUrlToImage(String urlToImage) { 48 | this.urlToImage = urlToImage; 49 | } 50 | 51 | public void setPublishedAt(String publishedAt) { 52 | this.publishedAt = publishedAt; 53 | } 54 | 55 | 56 | public String getTitle() { 57 | return title; 58 | } 59 | 60 | public String getDescription() { 61 | return description; 62 | } 63 | 64 | public String getUrl() { 65 | return url; 66 | } 67 | 68 | public String getUrlToImage() { 69 | return urlToImage; 70 | } 71 | 72 | public String getPublishedAt() { 73 | return publishedAt; 74 | } 75 | 76 | public long getId() { 77 | return id; 78 | } 79 | 80 | public void setId(long id) { 81 | this.id = id; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /app/src/main/java/lingaraj/hourglass/in/base/utils/Storage.java: -------------------------------------------------------------------------------- 1 | package lingaraj.hourglass.in.base.utils; 2 | 3 | import android.content.Context; 4 | import android.content.ContextWrapper; 5 | import android.database.Observable; 6 | import android.graphics.Bitmap; 7 | import android.support.annotation.Nullable; 8 | import io.reactivex.Single; 9 | import java.io.File; 10 | import java.io.FileOutputStream; 11 | import timber.log.Timber; 12 | 13 | public class Storage { 14 | 15 | 16 | @Nullable 17 | public static String getLocalFilePath(String filename, Context mContext){ 18 | ContextWrapper context_wrapper = new ContextWrapper(mContext); 19 | String absolute_path = context_wrapper.getFilesDir().getAbsolutePath(); 20 | File file = new File(absolute_path,filename); 21 | if (file.exists()){ 22 | return file.getAbsolutePath(); 23 | } 24 | else { 25 | return null; 26 | } 27 | } 28 | 29 | public static String getFileNameWithExtension(String image_url) { 30 | Timber.d("Splitting_image_url"+image_url); 31 | String splitted_image_name = image_url.substring(image_url.lastIndexOf("/")+1); 32 | Timber.d("Image Name with extension" + splitted_image_name.trim()); 33 | return splitted_image_name; 34 | } 35 | 36 | public static Single saveBitmap(Bitmap bitmap,Context mcontext,String urlImage){ 37 | ContextWrapper contextWrapper =new ContextWrapper(mcontext); 38 | boolean file_saved = false; 39 | String fileName = getFileNameWithExtension(urlImage); 40 | File local_file_path = new File(contextWrapper.getFilesDir(),fileName); 41 | try { 42 | FileOutputStream fos = new FileOutputStream(local_file_path); 43 | bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos); 44 | fos.close(); 45 | Timber.d("Image Saved to internal storage:"+local_file_path.getAbsolutePath()); 46 | file_saved = true; 47 | } 48 | catch (Exception e){ 49 | Timber.d(e.toString()); 50 | } 51 | 52 | return Single.just(file_saved); 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /app/src/main/java/lingaraj/hourglass/in/base/di/ActivityInjector.java: -------------------------------------------------------------------------------- 1 | package lingaraj.hourglass.in.base.di; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | 6 | 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | 10 | import javax.inject.Inject; 11 | import javax.inject.Provider; 12 | 13 | import dagger.android.AndroidInjector; 14 | import lingaraj.hourglass.in.base.BaseApp; 15 | import lingaraj.hourglass.in.base.core.BaseActivity; 16 | 17 | public class ActivityInjector { 18 | 19 | private final Map, Provider>> activityInjectors; 20 | private final Map> cache = new HashMap<>(); 21 | 22 | @Inject 23 | ActivityInjector(Map, Provider>> activityInjectors) { 24 | 25 | this.activityInjectors = activityInjectors; 26 | } 27 | 28 | void inject(Activity activity) { 29 | if(!(activity instanceof BaseActivity)) { 30 | throw new IllegalArgumentException("Activity must extend BaseActivity"); 31 | } 32 | String instanceId = ((BaseActivity) activity).getInstanceId(); 33 | if(cache.containsKey(instanceId)) { 34 | ((AndroidInjector) cache.get(instanceId)).inject(activity); 35 | return; 36 | } 37 | 38 | AndroidInjector.Factory injectorFactory = (AndroidInjector.Factory) activityInjectors.get(activity.getClass()).get(); 39 | AndroidInjector injector = injectorFactory.create(activity); 40 | cache.put(instanceId, injector); 41 | injector.inject(activity); 42 | } 43 | 44 | void clear(Activity activity) { 45 | if(!(activity instanceof BaseActivity)) { 46 | throw new IllegalArgumentException("Activity must extend BaseActivity"); 47 | } 48 | cache.remove(((BaseActivity) activity).getInstanceId()); 49 | } 50 | 51 | static ActivityInjector get(Context context) { 52 | return ((BaseApp) context.getApplicationContext()).getActivityInjector(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /app/src/main/java/lingaraj/hourglass/in/base/home/articlesFragment/ArticlesViewModel.java: -------------------------------------------------------------------------------- 1 | package lingaraj.hourglass.in.base.home.articlesFragment; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.arch.lifecycle.LiveData; 5 | import android.arch.lifecycle.MutableLiveData; 6 | import android.arch.lifecycle.ViewModel; 7 | import io.reactivex.schedulers.Schedulers; 8 | import java.util.List; 9 | import javax.inject.Named; 10 | import lingaraj.hourglass.in.base.database.Article; 11 | import lingaraj.hourglass.in.base.di.Names; 12 | import timber.log.Timber; 13 | 14 | public class ArticlesViewModel extends ViewModel { 15 | 16 | private final ArticleFragmentRepository repository; 17 | 18 | private final MutableLiveData error = new MutableLiveData<>(); 19 | private final MutableLiveData loader = new MutableLiveData<>(); 20 | private LiveData> articleLiveData; 21 | private final String common_error_response; 22 | MutableLiveData liveErrors(){ 23 | return this.error; 24 | } 25 | LiveData> liveArticles(){ 26 | return this.articleLiveData; 27 | } 28 | 29 | MutableLiveData liveLoaderStatus(){ 30 | return this.loader; 31 | } 32 | 33 | 34 | 35 | public ArticlesViewModel(ArticleFragmentRepository articleFragmentRepository,@Named(Names.NAMED_ERROR_COMMON) String errorResponseCommon){ 36 | repository = articleFragmentRepository; 37 | common_error_response = errorResponseCommon; 38 | loader.postValue(true); 39 | this.articleLiveData = repository.getArticles(); 40 | } 41 | 42 | @SuppressLint("CheckResult") 43 | public void refresh(){ 44 | repository.refreshArticles() 45 | .subscribeOn(Schedulers.io()) 46 | .doOnSubscribe(__->loader.postValue(true)) 47 | .doOnError(__->error.postValue(common_error_response)) 48 | .doOnSuccess(__->loader.postValue(false)) 49 | .subscribe(newsResponse -> {Timber.d("Obtained Response");},throwable->{ 50 | Timber.d(throwable.toString()); 51 | error.postValue(common_error_response); 52 | }); 53 | } 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | } 65 | -------------------------------------------------------------------------------- /app/src/main/java/lingaraj/hourglass/in/base/home/preview/WebLinkPreviewFragment.java: -------------------------------------------------------------------------------- 1 | package lingaraj.hourglass.in.base.home.preview; 2 | 3 | import android.os.Bundle; 4 | import android.view.View; 5 | import android.webkit.WebChromeClient; 6 | import android.webkit.WebView; 7 | import android.webkit.WebViewClient; 8 | import butterknife.BindView; 9 | import java.util.UUID; 10 | import lingaraj.hourglass.in.base.R; 11 | import lingaraj.hourglass.in.base.core.BaseFragment; 12 | import lingaraj.hourglass.in.base.utils.Constants; 13 | 14 | public class WebLinkPreviewFragment extends BaseFragment { 15 | 16 | public class Keys { 17 | static final String WEBLINK = "WEB_LINK"; 18 | } 19 | 20 | @BindView(R.id.web_view) 21 | WebView webView; 22 | public static final String BACKSTACK_TAG = "WEB_PREVIEW_FRAG"; 23 | 24 | @Override protected int layoutRes() { 25 | return R.layout.fragment_webview; 26 | } 27 | 28 | public static WebLinkPreviewFragment newInstance(String webLink){ 29 | WebLinkPreviewFragment webLinkPreviewFragment = new WebLinkPreviewFragment(); 30 | Bundle bundle = new Bundle(); 31 | bundle.putString(Constants.INSTANCE_ID, UUID.randomUUID().toString()); 32 | bundle.putString(Keys.WEBLINK,webLink); 33 | webLinkPreviewFragment.setArguments(bundle); 34 | return webLinkPreviewFragment; 35 | } 36 | 37 | @Override protected void onViewBound(View view) { 38 | super.onViewBound(view); 39 | Bundle bundle = getArguments(); 40 | String web_url = null; 41 | if (bundle!=null){ 42 | web_url = bundle.getString(Keys.WEBLINK); 43 | } 44 | 45 | if (web_url==null) { 46 | throw new NullPointerException("WEB LINK CANNOT BE NULL"); 47 | } 48 | else { 49 | webView.loadUrl(web_url); 50 | webView.getSettings().setJavaScriptEnabled(true); 51 | 52 | // Set WebView client 53 | webView.setWebChromeClient(new WebChromeClient()); 54 | 55 | webView.setWebViewClient(new WebViewClient() { 56 | 57 | @Override 58 | public boolean shouldOverrideUrlLoading(WebView view, String url) { 59 | view.loadUrl(url); 60 | return true; 61 | } 62 | }); 63 | } 64 | 65 | 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /app/src/main/res/layout/card_trending.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 12 | 18 | 19 | 30 | 36 | 37 | 48 | 49 | 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /app/src/main/java/lingaraj/hourglass/in/base/ui/DefaultScreenNavigator.java: -------------------------------------------------------------------------------- 1 | package lingaraj.hourglass.in.base.ui; 2 | 3 | import android.support.v4.app.Fragment; 4 | import android.support.v4.app.FragmentManager; 5 | import android.support.v7.app.AppCompatActivity; 6 | import java.util.List; 7 | import javax.inject.Inject; 8 | import lingaraj.hourglass.in.base.R; 9 | import lingaraj.hourglass.in.base.core.BaseFragment; 10 | import lingaraj.hourglass.in.base.di.ActivityScope; 11 | import lingaraj.hourglass.in.base.lifecycle.ActivityLifecycleTask; 12 | 13 | @ActivityScope 14 | public class DefaultScreenNavigator extends ActivityLifecycleTask implements ScreenNavigator { 15 | 16 | private FragmentManager fragmentManager; 17 | private AppCompatActivity activity; 18 | 19 | 20 | @Inject 21 | DefaultScreenNavigator() { 22 | 23 | } 24 | 25 | @Override 26 | public void onCreate(AppCompatActivity activity) { 27 | this.activity = activity; 28 | init(activity.getSupportFragmentManager(), ((ScreenProvider) activity).initialScreen()); 29 | } 30 | 31 | private void init(FragmentManager fragmentManager, Fragment rootScreen) { 32 | this.fragmentManager = fragmentManager; 33 | if (fragmentManager.getFragments().size() == 0) { 34 | fragmentManager.beginTransaction() 35 | .replace(R.id.fragment_container, rootScreen) 36 | .commit(); 37 | } 38 | } 39 | 40 | @Override 41 | public boolean pop() { 42 | return fragmentManager != null && fragmentManager.popBackStackImmediate(); 43 | } 44 | 45 | 46 | @Override 47 | public void onBackPressed() { 48 | if (fragmentManager != null) { 49 | pop(); 50 | } 51 | } 52 | 53 | 54 | @Override 55 | public void finish() { 56 | activity.finish(); 57 | } 58 | 59 | 60 | @Override 61 | public void onDestroy(AppCompatActivity activity) { 62 | fragmentManager = null; 63 | } 64 | 65 | 66 | 67 | 68 | @Override 69 | public void loadFragments(Fragment activeFragment, List hiddenFragmentList) { 70 | } 71 | 72 | 73 | @Override 74 | public void showFragment(Fragment tobeShown,String fragmentTag) { 75 | if (fragmentManager!=null){ 76 | fragmentManager.beginTransaction().add(R.id.fragment_container,tobeShown).addToBackStack(fragmentTag).commit(); 77 | } 78 | } 79 | 80 | 81 | 82 | 83 | } 84 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 14 | 15 | 22 | 23 | 30 | 31 | 39 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /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 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 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 Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /app/src/test/java/lingaraj/hourglass/in/base/home/articlesFragment/ArticlesViewModelTest.java: -------------------------------------------------------------------------------- 1 | package lingaraj.hourglass.in.base.home.articlesFragment; 2 | 3 | import android.arch.core.executor.testing.InstantTaskExecutorRule; 4 | import android.arch.lifecycle.LiveData; 5 | import android.arch.lifecycle.MutableLiveData; 6 | import io.reactivex.Single; 7 | import io.reactivex.disposables.Disposable; 8 | import java.util.List; 9 | import java.util.Observer; 10 | import java.util.function.Consumer; 11 | import javax.inject.Provider; 12 | import lingaraj.hourglass.in.base.api.NewsAPIRequester; 13 | import lingaraj.hourglass.in.base.api.di.APIRequester; 14 | import lingaraj.hourglass.in.base.api.response.NewsResponse; 15 | import lingaraj.hourglass.in.base.database.AppDatabase; 16 | import lingaraj.hourglass.in.base.database.Article; 17 | import lingaraj.hourglass.in.base.database.ArticlesDao; 18 | import lingaraj.hourglass.in.testutils.TestUtil; 19 | import org.junit.After; 20 | import org.junit.Before; 21 | import org.junit.Rule; 22 | import org.junit.Test; 23 | import org.mockito.Mock; 24 | 25 | import static org.mockito.Mockito.*; 26 | 27 | import static org.junit.Assert.*; 28 | 29 | public class ArticlesViewModelTest { 30 | 31 | 32 | private ArticlesViewModel view_model; 33 | private ArticleFragmentRepository articles_repo; 34 | private String error_response_common = "Please Try again!"; 35 | @Mock ArticlesDao articlesDao; 36 | @Mock AppDatabase database; 37 | @Mock Provider requesterProvider; 38 | @Mock LiveData> articlesConsumer; 39 | @Mock MutableLiveData loadingConsumer; 40 | @Mock MutableLiveData erroConsumer; 41 | @Mock Consumer onSuccessConsumer; 42 | @Mock Consumer errorConsumer; 43 | 44 | 45 | 46 | @Rule 47 | public InstantTaskExecutorRule instantExecutorRule = new InstantTaskExecutorRule(); 48 | 49 | @Before public void setUp() throws Exception { 50 | articles_repo = new ArticleFragmentRepository(requesterProvider,database); 51 | view_model = new ArticlesViewModel(articles_repo,error_response_common); 52 | when(view_model.liveLoaderStatus()).thenReturn(loadingConsumer); 53 | when(view_model.liveErrors()).thenReturn(erroConsumer); 54 | when(view_model.liveArticles()).thenReturn(articlesConsumer); 55 | 56 | } 57 | 58 | @Test 59 | public void newsLoaded() throws Exception{ 60 | NewsResponse newsResponse = TestUtil.getTestResponse(); 61 | verify(requesterProvider).get().getArticles(); 62 | verify(onSuccessConsumer).accept(newsResponse); 63 | verifyZeroInteractions(); 64 | 65 | 66 | } 67 | } -------------------------------------------------------------------------------- /app/src/main/java/lingaraj/hourglass/in/base/di/ScreenInjector.java: -------------------------------------------------------------------------------- 1 | package lingaraj.hourglass.in.base.di; 2 | 3 | import android.app.Activity; 4 | 5 | import android.support.v4.app.Fragment; 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | import java.util.Objects; 9 | 10 | 11 | import dagger.android.AndroidInjector; 12 | import javax.inject.Inject; 13 | import javax.inject.Provider; 14 | import lingaraj.hourglass.in.base.core.BaseActivity; 15 | import lingaraj.hourglass.in.base.core.BaseFragment; 16 | 17 | @ActivityScope 18 | public class ScreenInjector { 19 | 20 | private final Map, Provider>> screenInjectors; 21 | private final Map> cache = new HashMap<>(); 22 | 23 | @Inject 24 | ScreenInjector(Map, Provider>> screenInjectors) { 25 | this.screenInjectors = screenInjectors; 26 | } 27 | 28 | void inject(Fragment fragment) { 29 | if (!(fragment instanceof BaseFragment)) { 30 | throw new IllegalArgumentException("Fragment must extend BaseFragment"); 31 | } 32 | 33 | assert fragment.getArguments() != null; 34 | String instanceId = fragment.getArguments().getString("instance_id"); 35 | if(instanceId == null) { 36 | throw new IllegalArgumentException("Fragment must have instance_id in newInstance method"); 37 | } 38 | if (cache.containsKey(instanceId)) { 39 | Objects.requireNonNull(cache.get(instanceId)).inject(fragment); 40 | return; 41 | } 42 | 43 | //noinspection unchecked 44 | AndroidInjector.Factory injectorFactory = 45 | (AndroidInjector.Factory) Objects.requireNonNull(screenInjectors.get(fragment.getClass())).get(); 46 | if(injectorFactory != null) { 47 | AndroidInjector injector = injectorFactory.create(fragment); 48 | cache.put(instanceId, injector); 49 | injector.inject(fragment); 50 | } 51 | } 52 | 53 | void clear(Fragment fragment) { 54 | assert fragment.getArguments() != null; 55 | AndroidInjector injector = cache.remove(fragment.getArguments().getString("instance_id")); 56 | if (injector instanceof ScreenComponent) { 57 | ((ScreenComponent) injector).disposableManager().dispose(); 58 | } 59 | } 60 | 61 | static ScreenInjector get(Activity activity) { 62 | if (!(activity instanceof BaseActivity)) { 63 | throw new IllegalArgumentException("Fragment must be hosted by BaseActivity"); 64 | } 65 | return ((BaseActivity) activity).getScreenInjector(); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /app/src/main/java/lingaraj/hourglass/in/base/core/BaseFragment.java: -------------------------------------------------------------------------------- 1 | package lingaraj.hourglass.in.base.core; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | import android.support.annotation.LayoutRes; 6 | import android.support.annotation.Nullable; 7 | import android.support.v4.app.Fragment; 8 | import android.view.LayoutInflater; 9 | import android.view.View; 10 | import android.view.ViewGroup; 11 | import butterknife.ButterKnife; 12 | import butterknife.Unbinder; 13 | import io.reactivex.disposables.CompositeDisposable; 14 | import io.reactivex.disposables.Disposable; 15 | import java.util.Objects; 16 | import java.util.Set; 17 | import javax.inject.Inject; 18 | import lingaraj.hourglass.in.base.di.Injector; 19 | import lingaraj.hourglass.in.base.lifecycle.ScreenLifecycleTask; 20 | import org.jetbrains.annotations.NotNull; 21 | 22 | public abstract class BaseFragment extends Fragment { 23 | 24 | @Inject 25 | Set screenLifecycleTasks; 26 | 27 | private final CompositeDisposable disposables = new CompositeDisposable(); 28 | 29 | private Unbinder unbinder; 30 | 31 | @Override 32 | public void onAttach(Context context) { 33 | Injector.inject(this); 34 | super.onAttach(context); 35 | } 36 | 37 | @Override public void onCreate(@Nullable Bundle savedInstanceState) { 38 | super.onCreate(savedInstanceState); 39 | } 40 | 41 | @Nullable 42 | @Override 43 | public View onCreateView(@NotNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 44 | View view = inflater.inflate(layoutRes(), container, false); 45 | unbinder = ButterKnife.bind(this, view); 46 | onViewBound(view); 47 | disposables.addAll(subscriptions()); 48 | for (ScreenLifecycleTask task : screenLifecycleTasks) { 49 | task.onEnterScope(view.getRootView()); 50 | } 51 | return view; 52 | } 53 | 54 | @Override 55 | public void onDestroyView() { 56 | disposables.clear(); 57 | if (unbinder != null) { 58 | unbinder.unbind(); 59 | unbinder = null; 60 | } 61 | for (ScreenLifecycleTask task : screenLifecycleTasks) { 62 | task.onExitScope(); 63 | } 64 | super.onDestroyView(); 65 | } 66 | 67 | @Override 68 | public void onDestroy() { 69 | super.onDestroy(); 70 | for (ScreenLifecycleTask task : screenLifecycleTasks) { 71 | task.onDestroy(); 72 | } 73 | if (!Objects.requireNonNull(getActivity()).isChangingConfigurations()) { 74 | Injector.clearComponent(this); 75 | } 76 | } 77 | 78 | protected void onViewBound(View view) { 79 | 80 | } 81 | 82 | protected Disposable[] subscriptions() { 83 | return new Disposable[0]; 84 | } 85 | 86 | @LayoutRes 87 | protected abstract int layoutRes(); 88 | } 89 | -------------------------------------------------------------------------------- /app/src/main/java/lingaraj/hourglass/in/base/core/NetworkModule.java: -------------------------------------------------------------------------------- 1 | package lingaraj.hourglass.in.base.core; 2 | 3 | import android.net.Uri; 4 | import dagger.Module; 5 | import dagger.Provides; 6 | import java.util.Arrays; 7 | import java.util.Collections; 8 | import java.util.concurrent.TimeUnit; 9 | import javax.inject.Singleton; 10 | import lingaraj.hourglass.in.base.BuildConfig; 11 | import lingaraj.hourglass.in.base.utils.Constants; 12 | import okhttp3.Call; 13 | import okhttp3.CipherSuite; 14 | import okhttp3.ConnectionSpec; 15 | import okhttp3.OkHttpClient; 16 | import okhttp3.Protocol; 17 | import okhttp3.Request; 18 | import okhttp3.TlsVersion; 19 | import okhttp3.logging.HttpLoggingInterceptor; 20 | import retrofit2.Retrofit; 21 | import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; 22 | import retrofit2.converter.gson.GsonConverterFactory; 23 | 24 | @Module 25 | public class NetworkModule { 26 | 27 | 28 | 29 | @Provides 30 | @Singleton 31 | static OkHttpClient provideHttpClient(HttpLoggingInterceptor logging) { 32 | ConnectionSpec spec = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS) 33 | .tlsVersions(TlsVersion.TLS_1_0) 34 | .cipherSuites( 35 | CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, 36 | CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, 37 | CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, 38 | CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA, 39 | CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA, 40 | CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA) 41 | .build(); 42 | return new OkHttpClient.Builder().addInterceptor(logging) .connectionSpecs(Collections.singletonList(spec)) 43 | .protocols(Arrays.asList(Protocol.HTTP_1_1)) 44 | .readTimeout(3, TimeUnit.MINUTES) 45 | .connectTimeout(3, TimeUnit.MINUTES).build(); 46 | 47 | } 48 | 49 | @Provides 50 | @Singleton 51 | static HttpLoggingInterceptor providesLoggingInterceptor(){ 52 | HttpLoggingInterceptor logging = new HttpLoggingInterceptor(); 53 | if (BuildConfig.DEBUG) { 54 | logging.setLevel(HttpLoggingInterceptor.Level.BODY); 55 | } 56 | else { 57 | logging.setLevel(HttpLoggingInterceptor.Level.NONE); 58 | } 59 | return logging; 60 | 61 | 62 | } 63 | 64 | 65 | @Provides 66 | @Singleton 67 | static Call.Factory providesCallFactory() { 68 | return new OkHttpClient.Builder().build(); 69 | } 70 | 71 | 72 | @Provides 73 | @Singleton 74 | static Retrofit providesRetrofit(OkHttpClient okHttpClient,Call.Factory callFactory){ 75 | return new Retrofit.Builder() 76 | .callFactory(callFactory) 77 | .addConverterFactory(GsonConverterFactory.create()) 78 | .client(okHttpClient) 79 | .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) 80 | .baseUrl(Uri.parse(Constants.BASE_URL).toString()) 81 | .build(); 82 | 83 | } 84 | 85 | 86 | 87 | } 88 | -------------------------------------------------------------------------------- /app/src/main/res/layout/card_default.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 22 | 23 | 31 | 36 | 49 | 50 | 55 | 56 | 67 | 68 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /app/src/main/java/lingaraj/hourglass/in/base/core/BaseActivity.java: -------------------------------------------------------------------------------- 1 | package lingaraj.hourglass.in.base.core; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.LayoutRes; 5 | import android.support.annotation.Nullable; 6 | import android.support.v7.app.AppCompatActivity; 7 | import android.view.ViewGroup; 8 | import java.util.Set; 9 | import java.util.UUID; 10 | import javax.inject.Inject; 11 | import lingaraj.hourglass.in.base.R; 12 | import lingaraj.hourglass.in.base.di.Injector; 13 | import lingaraj.hourglass.in.base.di.ScreenInjector; 14 | import lingaraj.hourglass.in.base.lifecycle.ActivityLifecycleTask; 15 | import lingaraj.hourglass.in.base.ui.ActivityViewInterceptor; 16 | import lingaraj.hourglass.in.base.ui.ScreenNavigator; 17 | import lingaraj.hourglass.in.base.ui.ScreenProvider; 18 | 19 | public abstract class BaseActivity extends AppCompatActivity implements ScreenProvider { 20 | 21 | private static final String INSTANCE_ID_KEY = "instance_id"; 22 | 23 | @Inject ScreenInjector screenInjector; 24 | @Inject ScreenNavigator screenNavigator; 25 | @Inject ActivityViewInterceptor activityViewInterceptor; 26 | @Inject Set activityLifecycleTasks; 27 | 28 | private String instanceId; 29 | 30 | @Override 31 | protected void onCreate(@Nullable Bundle savedInstanceState) { 32 | if (savedInstanceState != null) { 33 | instanceId = savedInstanceState.getString(INSTANCE_ID_KEY); 34 | } else { 35 | instanceId = UUID.randomUUID().toString(); 36 | } 37 | Injector.inject(this); 38 | super.onCreate(savedInstanceState); 39 | activityViewInterceptor.setContentView(this, layoutRes()); 40 | ViewGroup screenContainer = findViewById(R.id.screen_container); 41 | if (screenContainer == null) { 42 | throw new NullPointerException("Activity must have a view with id: screen_container"); 43 | } 44 | 45 | for (ActivityLifecycleTask task : activityLifecycleTasks) { 46 | task.onCreate(this); 47 | } 48 | } 49 | 50 | @Override 51 | protected void onStart() { 52 | super.onStart(); 53 | for (ActivityLifecycleTask task : activityLifecycleTasks) { 54 | task.onStart(this); 55 | } 56 | } 57 | 58 | @Override 59 | protected void onResume() { 60 | super.onResume(); 61 | for (ActivityLifecycleTask task : activityLifecycleTasks) { 62 | task.onResume(this); 63 | } 64 | } 65 | 66 | @Override 67 | protected void onPause() { 68 | super.onPause(); 69 | for (ActivityLifecycleTask task : activityLifecycleTasks) { 70 | task.onPause(this); 71 | } 72 | } 73 | 74 | @Override 75 | protected void onStop() { 76 | super.onStop(); 77 | for (ActivityLifecycleTask task : activityLifecycleTasks) { 78 | task.onStop(this); 79 | } 80 | } 81 | 82 | @Override 83 | public void onSaveInstanceState(Bundle outState) { 84 | super.onSaveInstanceState(outState); 85 | outState.putString(INSTANCE_ID_KEY, instanceId); 86 | } 87 | 88 | @Override 89 | public void onBackPressed() { 90 | if (!screenNavigator.pop()) { 91 | super.onBackPressed(); 92 | } 93 | } 94 | 95 | @LayoutRes 96 | protected abstract int layoutRes(); 97 | 98 | public String getInstanceId() { 99 | return instanceId; 100 | } 101 | 102 | @Override 103 | protected void onDestroy() { 104 | super.onDestroy(); 105 | if (isFinishing()) { 106 | Injector.clearComponent(this); 107 | } 108 | activityViewInterceptor.clear(); 109 | for (ActivityLifecycleTask task : activityLifecycleTasks) { 110 | task.onDestroy(this); 111 | } 112 | } 113 | 114 | public ScreenInjector getScreenInjector() { 115 | return screenInjector; 116 | } 117 | 118 | 119 | } 120 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion rootProject.ext.compileSdkVersion 5 | buildToolsVersion rootProject.ext.buildToolsVersion 6 | defaultConfig { 7 | applicationId "lingaraj.hourglass.in.base" 8 | minSdkVersion rootProject.ext.minSdkVersion 9 | targetSdkVersion rootProject.ext.targetSdkVersion 10 | versionCode 1 11 | versionName "1.0" 12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 13 | javaCompileOptions { 14 | annotationProcessorOptions { 15 | arguments = ["room.schemaLocation": "$projectDir/schemas".toString()] 16 | } 17 | } 18 | vectorDrawables.useSupportLibrary = true 19 | multiDexEnabled true 20 | 21 | } 22 | buildTypes { 23 | release { 24 | minifyEnabled false 25 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 26 | } 27 | } 28 | compileOptions { 29 | sourceCompatibility JavaVersion.VERSION_1_8 30 | targetCompatibility JavaVersion.VERSION_1_8 31 | } 32 | } 33 | 34 | dependencies { 35 | implementation "com.android.support:appcompat-v7:$supportLibraryVersion" 36 | implementation "com.android.support:design:$supportLibraryVersion" 37 | implementation "com.android.support:cardview-v7:$supportLibraryVersion" 38 | 39 | implementation "android.arch.lifecycle:extensions:$archComponentsVersion" 40 | implementation "com.android.support:multidex:$multidexVersion" 41 | 42 | //implementation "com.google.firebase:firebase-core:$firebaseVersion" 43 | //implementation "com.google.firebase:firebase-crash:$firebaseCrashVersion" 44 | //implementation "com.google.firebase:firebase-messaging:$firebaseMessagesVersion" 45 | 46 | implementation "android.arch.persistence.room:runtime:$archComponentsVersion" 47 | implementation "android.arch.persistence.room:rxjava2:$archComponentsVersion" 48 | annotationProcessor "android.arch.persistence.room:compiler:$archComponentsVersion" 49 | 50 | implementation "com.google.dagger:dagger:$daggerVersion" 51 | implementation "com.google.dagger:dagger-android-support:$daggerVersion" 52 | annotationProcessor "com.google.dagger:dagger-android-processor:$daggerVersion" 53 | annotationProcessor "com.google.dagger:dagger-compiler:$daggerVersion" 54 | 55 | implementation "com.squareup.retrofit2:retrofit:$retrofitVersion" 56 | implementation "com.squareup.retrofit2:adapter-rxjava2:$retrofitVersion" 57 | implementation "com.squareup.retrofit2:converter-moshi:$retrofitVersion" 58 | implementation "com.squareup.picasso:picasso:$picassoVersion" 59 | implementation "com.squareup.moshi:moshi:$moshiVersion" 60 | implementation "com.google.code.gson:gson:$gsonVersion" 61 | implementation "com.squareup.retrofit2:converter-gson:$gsonConverterVersion" 62 | implementation "com.squareup.okhttp3:logging-interceptor:$loggingInterceptorVersion" 63 | annotationProcessor "com.ryanharter.auto.value:auto-value-moshi:$autoValueMoshiVersion" 64 | compileOnly "com.ryanharter.auto.value:auto-value-moshi-annotations:$autoValueMoshiVersion" 65 | annotationProcessor "com.ryanharter.auto.value:auto-value-parcel:$autoValueParcelVersion" 66 | 67 | implementation "com.jakewharton:butterknife:$butterknifeVersion" 68 | annotationProcessor "com.jakewharton:butterknife-compiler:$butterknifeVersion" 69 | implementation "com.github.bumptech.glide:glide:$glideVersion" 70 | annotationProcessor "com.github.bumptech.glide:compiler:$glideVersion" 71 | 72 | compileOnly "com.google.auto.value:auto-value:$autoValueVersion" 73 | annotationProcessor "com.google.auto.value:auto-value:$autoValueVersion" 74 | 75 | implementation "io.reactivex.rxjava2:rxjava:$rxJavaVersion" 76 | implementation "io.reactivex.rxjava2:rxandroid:$rxAndroidVersion" 77 | implementation "com.jakewharton.rxrelay2:rxrelay:$rxRelayVersion" 78 | implementation "com.jakewharton.rxbinding2:rxbinding:$rxBindingVersion" 79 | implementation "com.jakewharton.threetenabp:threetenabp:$threeTenAbpVersion" 80 | implementation "com.jakewharton.timber:timber:$timberVersion" 81 | 82 | implementation "de.hdodenhof:circleimageview:$hdodenhofVersion" 83 | implementation "com.pnikosis:materialish-progress:$pnikosisProgressVersion" 84 | implementation "com.isseiaoki:simplecropview:$cropViewVersion" 85 | 86 | // Warning: Don't update simplepass loading-button version as future versions are not compatible 87 | implementation "br.com.simplepass:loading-button-android:$loadingButtonVersion" 88 | 89 | testImplementation "android.arch.core:core-testing:$archComponentsVersion" 90 | testImplementation "org.mockito:mockito-core:$mockitoVersion" 91 | testImplementation 'junit:junit:4.12' 92 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 93 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 94 | implementation 'com.android.support.constraint:constraint-layout:1.1.3' 95 | } 96 | 97 | -------------------------------------------------------------------------------- /app/src/main/java/lingaraj/hourglass/in/base/home/articlesFragment/ArticlesAdapter.java: -------------------------------------------------------------------------------- 1 | package lingaraj.hourglass.in.base.home.articlesFragment; 2 | 3 | import android.content.Context; 4 | import android.graphics.Bitmap; 5 | import android.graphics.drawable.Drawable; 6 | import android.net.Uri; 7 | import android.support.annotation.NonNull; 8 | import android.support.v7.widget.RecyclerView; 9 | import android.view.LayoutInflater; 10 | import android.view.View; 11 | import android.view.ViewGroup; 12 | import android.widget.ImageView; 13 | import android.widget.TextView; 14 | import butterknife.BindView; 15 | import butterknife.ButterKnife; 16 | import com.squareup.picasso.Callback; 17 | import com.squareup.picasso.NetworkPolicy; 18 | import com.squareup.picasso.Picasso; 19 | import com.squareup.picasso.Target; 20 | import io.reactivex.schedulers.Schedulers; 21 | import java.util.ArrayList; 22 | import java.util.List; 23 | import lingaraj.hourglass.in.base.R; 24 | import lingaraj.hourglass.in.base.database.Article; 25 | import lingaraj.hourglass.in.base.utils.General; 26 | import lingaraj.hourglass.in.base.utils.Storage; 27 | import timber.log.Timber; 28 | 29 | public class ArticlesAdapter extends RecyclerView.Adapter { 30 | 31 | 32 | private static final int TYPE_NEWS_DEFAULT = 100; 33 | private static final int TYPE_NEWS_TRENDING = 200; 34 | private final ArticlesFragment.ItemClick item_click; 35 | private Context mContext = null; 36 | private List
articles = new ArrayList
(); 37 | 38 | public ArticlesAdapter(ArticlesFragment.ItemClick clickListener){ 39 | this.item_click = clickListener; 40 | } 41 | 42 | @NonNull @Override 43 | public ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int viewType) { 44 | int chosen_layout = viewType==TYPE_NEWS_DEFAULT?R.layout.card_default:R.layout.card_trending; 45 | if (mContext==null){ 46 | mContext = viewGroup.getContext(); 47 | } 48 | View view = LayoutInflater.from(mContext).inflate(chosen_layout,viewGroup,false); 49 | view.setOnClickListener(this.item_click); 50 | return new ViewHolder(view); 51 | } 52 | 53 | @Override public void onBindViewHolder(@NonNull ViewHolder holder, int position) { 54 | Article article = articles.get(position); 55 | holder.title.setText(article.getTitle()); 56 | holder.publisher.setText(article.getAuthor()); 57 | int dimension = 0; 58 | 59 | String time_stamp = article.getPublishedAt(); 60 | if (time_stamp==null){ 61 | holder.hour.setVisibility(View.GONE); 62 | } 63 | else { 64 | holder.hour.setText(mContext.getString(R.string.hour, General.getHourDifference(time_stamp))); 65 | holder.hour.setVisibility(View.INVISIBLE); 66 | } 67 | if (holder.getItemViewType()==TYPE_NEWS_DEFAULT){ 68 | dimension = 50; 69 | } 70 | else { 71 | dimension = 100; 72 | } 73 | String url = article.getUrlToImage(); 74 | String file_name = Storage.getFileNameWithExtension(url); 75 | String local_storage_path = Storage.getLocalFilePath(file_name,mContext); 76 | String picasso_url = null; 77 | if (local_storage_path!=null){ 78 | picasso_url = local_storage_path; 79 | } 80 | else { 81 | picasso_url = url; 82 | } 83 | Picasso.with(mContext) 84 | .load(Uri.parse(url)) 85 | .networkPolicy(NetworkPolicy.OFFLINE) 86 | .into(holder.image_view, new Callback() { 87 | @Override 88 | public void onSuccess() { 89 | 90 | } 91 | 92 | @Override 93 | public void onError() { 94 | // Try again online if cache failed 95 | Picasso.with(mContext) 96 | .load(Uri.parse(url)) 97 | .into(holder.image_view); 98 | } 99 | }); 100 | 101 | } 102 | 103 | private void saveBitmap(Bitmap bitmap, String urlToImage) { 104 | String file_name = Storage.getFileNameWithExtension(urlToImage); 105 | if (Storage.getLocalFilePath(file_name,mContext)==null){ 106 | Storage.saveBitmap(bitmap,mContext,urlToImage). 107 | subscribeOn(Schedulers.io()).subscribe(); 108 | } 109 | } 110 | 111 | @Override 112 | public int getItemViewType(int position) { 113 | if (position%4==0){ 114 | return TYPE_NEWS_TRENDING; 115 | } 116 | else { 117 | return TYPE_NEWS_DEFAULT; 118 | } 119 | } 120 | 121 | @Override public int getItemCount() { 122 | return articles.size(); 123 | } 124 | 125 | public void addData(List
data) { 126 | data.addAll(articles); 127 | articles = data; 128 | notifyDataSetChanged(); 129 | Timber.d("Articles data updated"); 130 | } 131 | 132 | public String getWebLink(int position) { 133 | return articles.get(position).getUrl(); 134 | } 135 | 136 | public class ViewHolder extends RecyclerView.ViewHolder{ 137 | 138 | @BindView(R.id.image) 139 | ImageView image_view; 140 | 141 | @BindView(R.id.title) 142 | TextView title; 143 | 144 | @BindView(R.id.publisher) 145 | TextView publisher; 146 | 147 | @BindView(R.id.hour) 148 | TextView hour; 149 | 150 | public ViewHolder(@NonNull View itemView) { 151 | super(itemView); 152 | ButterKnife.bind(this,itemView); 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /app/src/main/java/lingaraj/hourglass/in/base/home/articlesFragment/ArticlesFragment.java: -------------------------------------------------------------------------------- 1 | package lingaraj.hourglass.in.base.home.articlesFragment; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | import android.support.annotation.Nullable; 6 | import android.support.annotation.VisibleForTesting; 7 | import android.support.design.widget.CoordinatorLayout; 8 | import android.support.design.widget.Snackbar; 9 | import android.support.v7.widget.LinearLayoutManager; 10 | import android.support.v7.widget.RecyclerView; 11 | import android.view.View; 12 | import android.widget.ImageView; 13 | import android.widget.LinearLayout; 14 | import android.widget.ProgressBar; 15 | import android.widget.TextView; 16 | import butterknife.BindView; 17 | import butterknife.OnClick; 18 | import java.util.List; 19 | import java.util.UUID; 20 | import javax.inject.Inject; 21 | import javax.inject.Named; 22 | import lingaraj.hourglass.in.base.R; 23 | import lingaraj.hourglass.in.base.core.BaseFragment; 24 | import lingaraj.hourglass.in.base.core.BaseViewModelFactory; 25 | import lingaraj.hourglass.in.base.database.Article; 26 | import lingaraj.hourglass.in.base.di.Names; 27 | import lingaraj.hourglass.in.base.home.preview.WebLinkPreviewFragment; 28 | import lingaraj.hourglass.in.base.ui.ScreenNavigator; 29 | import lingaraj.hourglass.in.base.utils.Constants; 30 | import timber.log.Timber; 31 | 32 | public class ArticlesFragment extends BaseFragment { 33 | 34 | @Inject BaseViewModelFactory base_view_model_factory; 35 | @Inject @Named(Names.NAMED_ERROR_COMMON) String error_message_common; 36 | 37 | @VisibleForTesting 38 | ArticlesViewModel view_model; 39 | 40 | 41 | @Inject ScreenNavigator navigator; 42 | 43 | @BindView(R.id.loader) ProgressBar loader; 44 | 45 | @VisibleForTesting 46 | @BindView(R.id.articles) 47 | RecyclerView mrecyclerview; 48 | 49 | @BindView(R.id.retry_container) LinearLayout retry_container; 50 | 51 | @BindView(R.id.retry_view) ImageView retry_view; 52 | 53 | @BindView(R.id.message) TextView message_view; 54 | 55 | @BindView(R.id.screen_container) CoordinatorLayout screen_container; 56 | 57 | private ArticlesAdapter madapter; 58 | 59 | 60 | 61 | public static ArticlesFragment newInstance(){ 62 | ArticlesFragment articlesFragment = new ArticlesFragment(); 63 | Bundle bundle = new Bundle(); 64 | bundle.putString(Constants.INSTANCE_ID, UUID.randomUUID().toString()); 65 | articlesFragment.setArguments(bundle); 66 | return articlesFragment; 67 | } 68 | 69 | @Override protected int layoutRes() { 70 | return R.layout.fragment_main; 71 | } 72 | 73 | @Override protected void onViewBound(View view) { 74 | super.onViewBound(view); 75 | Context mcontex = view.getContext(); 76 | view_model = base_view_model_factory.create(ArticlesViewModel.class); 77 | madapter = new ArticlesAdapter(new ItemClick()); 78 | mrecyclerview.setLayoutManager(new LinearLayoutManager(mcontex,LinearLayoutManager.VERTICAL,false)); 79 | mrecyclerview.setAdapter(madapter); 80 | setLiveDataRecepients(); 81 | view_model.refresh(); 82 | } 83 | 84 | private void setLiveDataRecepients() { 85 | view_model.liveLoaderStatus().observe(this, isLoading -> { 86 | if (isLoading!=null && isLoading){ 87 | showLoader(); 88 | } 89 | else { 90 | hideLoader(); 91 | } 92 | }); 93 | view_model.liveArticles().observe(this, articles -> { 94 | if (articles!=null && articles.size()>0){ 95 | showContent(); 96 | updateData(articles); 97 | hideLoader(); 98 | } 99 | }); 100 | view_model.liveErrors().observe(this, this::showError); 101 | } 102 | 103 | private void showContent() { 104 | if (mrecyclerview.getVisibility()==View.INVISIBLE){ 105 | mrecyclerview.setVisibility(View.VISIBLE); 106 | Timber.d("Showing content"); 107 | } 108 | } 109 | 110 | private void updateData(List
articles) { 111 | if(madapter!=null){ 112 | madapter.addData(articles); 113 | } 114 | } 115 | 116 | public void showLoader(){ 117 | loader.setVisibility(View.VISIBLE); 118 | retry_container.setVisibility(View.GONE); 119 | mrecyclerview.setVisibility(View.INVISIBLE); 120 | Timber.d("Loader Shown"); 121 | } 122 | 123 | public void hideLoader(){ 124 | loader.setVisibility(View.GONE); 125 | retry_container.setVisibility(View.GONE); 126 | 127 | Timber.d("Loader hidden"); 128 | } 129 | 130 | 131 | public void showError(@Nullable String message){ 132 | hideLoader(); 133 | if (message==null){ 134 | message = error_message_common; 135 | } 136 | if (madapter.getItemCount()>0){ 137 | //displaying cached data 138 | Snackbar.make(screen_container,error_message_common,Snackbar.LENGTH_SHORT).show(); 139 | } 140 | else { 141 | message_view.setText(message); 142 | retry_container.setVisibility(View.VISIBLE); 143 | } 144 | 145 | } 146 | 147 | public class ItemClick implements View.OnClickListener{ 148 | 149 | @Override public void onClick(View v) { 150 | Timber.d("Item Clicked"); 151 | int position = mrecyclerview.getChildAdapterPosition(v); 152 | String url = madapter.getWebLink(position); 153 | navigator.showFragment(WebLinkPreviewFragment.newInstance(url),WebLinkPreviewFragment.BACKSTACK_TAG); 154 | 155 | } 156 | } 157 | 158 | @OnClick(R.id.retry_view) 159 | public void retryClick(){ 160 | showLoader(); 161 | view_model.refresh(); 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 11 | 16 | 21 | 26 | 31 | 36 | 41 | 46 | 51 | 56 | 61 | 66 | 71 | 76 | 81 | 86 | 91 | 96 | 101 | 106 | 111 | 116 | 121 | 126 | 131 | 136 | 141 | 146 | 151 | 156 | 161 | 166 | 171 | 172 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 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 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /app/app.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | -------------------------------------------------------------------------------- /app/src/test/resources/mocks/articles.json: -------------------------------------------------------------------------------- 1 | { 2 | "status": "ok", 3 | "totalResults": 36, 4 | "articles": [ 5 | { 6 | "source": { 7 | "id": null, 8 | "name": "Business-standard.com" 9 | }, 10 | "author": "Press Trust of India", 11 | "title": "India reduced TB deaths among people living with HIV by 84 per cent:UNAIDS - Business Standard", 12 | "description": "India achieved an 84 per cent reduction in tuberculosis deaths among people living with HIV by 2017, the highest recorded decline among over 20 nations and three years ahead of the 2020 deadline, the Joint United Nations Programme on HIV/AIDS", 13 | "url": "https://www.business-standard.com/article/pti-stories/india-reduced-tb-deaths-among-people-living-with-hiv-by-84-per-cent-unaids-119032400098_1.html", 14 | "urlToImage": "https://bsmedia.business-standard.com/_media/bs/img/article/default/1190324/full-119032400098.jpg", 15 | "publishedAt": "2019-03-24T05:05:08Z", 16 | "content": "India achieved an 84 per cent reduction in tuberculosis deaths among people living with HIV by 2017, the highest recorded decline among over 20 nations and three years ahead of the 2020 deadline, the Joint United Nations Programme on HIV/ AIDS (UNAIDS) said. … [+3837 chars]" 17 | }, 18 | { 19 | "source": { 20 | "id": null, 21 | "name": "Ndtv.com" 22 | }, 23 | "author": null, 24 | "title": "\"Amethi Drove Him Away\": Smriti Irani On Rahul Gandhi For South Demand - NDTV News", 25 | "description": "Amid calls for Rahul Gandhi to contest from a seat in southern India apart from his Amethi constituency, Union Minister Smriti Irani, who challenged the Congress president in his Uttar Pradesh seat in the 2014 election, said that the calls were being \"staged\"…", 26 | "url": "https://www.ndtv.com/india-news/lok-sabha-election-2019-amethi-drove-him-away-smriti-irani-on-rahul-gandhi-for-south-demand-2011951", 27 | "urlToImage": "https://i.ndtvimg.com/i/2016-07/smriti-irani-afp_650x400_41467858151.jpg", 28 | "publishedAt": "2019-03-24T04:43:00Z", 29 | "content": "Lok Sabha election: Smriti Irani has accused Rahul Gandhi of treating Amethi as a vote bank. New Delhi: Amid calls for Rahul Gandhi to contest from a seat in southern India apart from his Amethi constituency, Union Minister Smriti Irani, who challenged the Co… [+3390 chars]" 30 | }, 31 | { 32 | "source": { 33 | "id": null, 34 | "name": "Hindustantimes.com" 35 | }, 36 | "author": "HT Correspondent", 37 | "title": "Alia Bhatt says ‘I love you’ to Ranbir Kapoor during Filmfare awards’ speech, calls him her special... - Hindustan Times", 38 | "description": "Alia Bhatt, who won the Best Actor (Female) at the Filmfare Awards on Saturday, mentioned Ranbir Kapoor in her acceptance speech. Watch it here.", 39 | "url": "https://www.hindustantimes.com/bollywood/alia-bhatt-says-i-love-you-to-ranbir-kapoor-during-filmfare-awards-speech-calls-him-her-special-man-watch/story-GVyl7HigCnhRZVdEl4h6fO.html", 40 | "urlToImage": "https://www.hindustantimes.com/rf/image_size_960x540/HT/p2/2019/03/24/Pictures/_8fe62868-4ded-11e9-bf2d-62d340f7f10c.JPG", 41 | "publishedAt": "2019-03-24T04:07:00Z", 42 | "content": "Actor Alia Bhatt gave a special mention to her boyfriend and Brahmastra co-star Ranbir Kapoor at the 64th Filmfare Awards on Saturday. She called him her ‘special man’ in her speech as she took home the Best Actor (Female) award for Raazi. After receiving the… [+1209 chars]" 43 | }, 44 | { 45 | "source": { 46 | "id": null, 47 | "name": "News18.com" 48 | }, 49 | "author": "PTI", 50 | "title": "Father or Husband? Glaring Blooper in Nomination Papers of Naidu, Son Leave TDP Red-faced - News18", 51 | "description": "While Naidu filed his nomination for the Kuppam assembly seat in his native Chittoor district, Lokesh filed his papers for Mangalagiri seat in the state capital region Amaravati.", 52 | "url": "https://www.news18.com/news/politics/father-or-husband-glaring-blooper-in-nomination-papers-of-naidu-son-leave-tdp-red-faced-2075903.html", 53 | "urlToImage": "https://images.news18.com/ibnlive/uploads/2019/03/Chandrababu-Naidu-and-son.jpg", 54 | "publishedAt": "2019-03-24T03:30:34Z", 55 | "content": "Amaravati: Telugu Desam leaders in Andhra Pradesh were left red-faced after a glaring blooper in the election nomination papers submitted by party president N Chandrababu Naidu and his son Nara Lokesh went viral on the social media on Saturday. In one of the … [+997 chars]" 56 | }, 57 | { 58 | "source": { 59 | "id": null, 60 | "name": "Indiatoday.in" 61 | }, 62 | "author": null, 63 | "title": "Kesari box office collection Day 3: Akshay Kumar film charges towards Rs 60 crore - India Today", 64 | "description": "Kesari, directed by Anurag Singh and featuring Akshay Kumar in the lead role, continues its victory march at the box office and is heading towards Rs 60 crore.", 65 | "url": "https://www.indiatoday.in/movies/bollywood/story/kesari-box-office-collection-day-3-akshay-kumar-film-charges-towards-rs-60-crore-1485091-2019-03-24", 66 | "urlToImage": "https://akm-img-a-in.tosshub.com/indiatoday/images/story/201903/kesari2_1-647x363.jpeg?XzYHEr1mz6fr8RkWLAu33OuUFwvMxX_k", 67 | "publishedAt": "2019-03-24T03:25:00Z", 68 | "content": "Akshay Kumar is known for choosing topics that strike a chord with the audience. After delivering consecutive hits in 2018 - R Balki's Pad Man, Reema Kagti's Gold and Shankar's 2.0, Akshay returned with war-drama Kesari that hit the screens on Holi, this year… [+1408 chars]" 69 | }, 70 | { 71 | "source": { 72 | "id": "the-times-of-india", 73 | "name": "The Times of India" 74 | }, 75 | "author": "Sunil Gavaskar", 76 | "title": "With Rohit Sharma opening, Mumbai Indians are favourites: Sunil Gavaskar - Times of India", 77 | "description": "Expert Column News: Both Mumbai and Delhi will try to begin their campaign on a winning note. Mumbai have won the trophy every alternate year since 2013 and will be fancy", 78 | "url": "https://timesofindia.indiatimes.com/sports/expert-column/sunil-gavaskar/with-rohit-sharma-opening-mumbai-indians-are-favourites-sunil-gavaskar/articleshow/68543801.cms", 79 | "urlToImage": "https://static.toiimg.com/thumb/msid-68543804,width-1070,height-580,imgsize-1256245,resizemode-6,overlay-toi_sw,pt-32,y_pad-40/photo.jpg", 80 | "publishedAt": "2019-03-24T03:10:10Z", 81 | "content": null 82 | }, 83 | { 84 | "source": { 85 | "id": null, 86 | "name": "Indianexpress.com" 87 | }, 88 | "author": "New York Times", 89 | "title": "Boeing 737 MAX: A jet born of a frantic race to outdo a rival - The Indian Express", 90 | "description": "The competitive pressure to build the 737 Max — which permeated the entire design and development — now threatens the reputation and profits of Boeing, after two deadly crashes in less than five months.", 91 | "url": "https://indianexpress.com/article/business/aviation/boeing-737-max-a-jet-born-of-a-frantic-race-to-outdo-a-rival-5640058/", 92 | "urlToImage": "https://images.indianexpress.com/2019/03/boeing-8.jpg?w=759", 93 | "publishedAt": "2019-03-24T02:44:25Z", 94 | "content": "FILE- In this March 14, 2019, file photo a worker walks next to a Boeing 737 MAX 8 airplane parked at Boeing Field in Seattle (AP) Written by David Gelles, Natalie Kitroeff, Jack Nicas and Rebecca R. Ruiz Boeing faced an unthinkable defection in the spring of… [+12292 chars]" 95 | }, 96 | { 97 | "source": { 98 | "id": null, 99 | "name": "Hindustantimes.com" 100 | }, 101 | "author": "Soumya Pillai", 102 | "title": "Squatters take over government flats in Delhi’s Sarojini Nagar as demolition yet to start - Hindustan Times", 103 | "description": "Abandoned central government flats in Sarojini Nagar are now occupied — not by government employees or anyone authorised by the government, but by squatters.", 104 | "url": "https://www.hindustantimes.com/india-news/squatters-take-over-government-flats-in-delhi-s-sarojini-nagar-as-demolition-yet-to-start/story-vlSJKEAia32WooVUkjdChN.html", 105 | "urlToImage": "https://www.hindustantimes.com/rf/image_size_960x540/HT/p2/2019/02/19/Pictures/jaipur-house_3a25ac86-3408-11e9-9527-26b2d5de00dc.jpg", 106 | "publishedAt": "2019-03-24T02:31:00Z", 107 | "content": "Abandoned central government flats in Sarojini Nagar are now occupied — not by government employees or anyone authorised by the government, but by squatters. “The flats are not locked. Just get your luggage. No one has stopped us,” said Sukhdev (who goes by h… [+3898 chars]" 108 | }, 109 | { 110 | "source": { 111 | "id": null, 112 | "name": "Hindustantimes.com" 113 | }, 114 | "author": "Faizan Haidar and Smriti Kak Ramachandran", 115 | "title": "Lok Sabha elections 2019: As electoral race gathers pace, political parties take fight to the skies - Hindustan Times", 116 | "description": "As the Lok Sabha electoral race gathers pace, demand for chartered planes and helicopters to fly around candidates and their political paraphernalia is rising rapidly", 117 | "url": "https://www.hindustantimes.com/lok-sabha-elections/lok-sabha-elections-2019-as-electoral-race-gathers-pace-political-parties-take-fight-to-the-skies/story-YI3r0GazwNe8JDUiW212cJ.html", 118 | "urlToImage": "https://www.hindustantimes.com/rf/image_size_960x540/HT/p2/2019/03/24/Pictures/_2ac14fb4-4ddc-11e9-aca9-eac9e517f545.png", 119 | "publishedAt": "2019-03-24T02:27:00Z", 120 | "content": "As the Lok Sabha electoral race gathers pace, demand for chartered planes and helicopters to fly around candidates and their political paraphernalia is rising rapidly. Most available aircraft have already been booked, air service operators said, and given tha… [+2899 chars]" 121 | }, 122 | { 123 | "source": { 124 | "id": null, 125 | "name": "Hindustantimes.com" 126 | }, 127 | "author": "Asian News International", 128 | "title": "World Tuberculosis Day: Anti-TB drugs can add to the risk of TB re-infection - Hindustan Times", 129 | "description": "In a recent study published in the Journal of Mucosal Immunology, researchers have shown that anti-TB drugs caused changes to gut microbiota, the diverse community of microbes living in human beings’ intestines, and increased susceptibility to Mtb infection.", 130 | "url": "https://www.hindustantimes.com/health/world-tuberculosis-day-anti-tb-drugs-can-add-to-the-risk-of-tb-re-infection/story-oymSAgjUw7goBGSCO1UbMO.html", 131 | "urlToImage": "https://www.hindustantimes.com/rf/image_size_960x540/HT/p2/2019/03/24/Pictures/_070ab232-4ddb-11e9-bf2d-62d340f7f10c.JPG", 132 | "publishedAt": "2019-03-24T02:20:00Z", 133 | "content": "A recent study revealed that treatments available for tuberculosis (TB) caused by Mycobacterium tuberculosis (Mtb), may not always prevent re-infection. In a recent study published in the Journal of Mucosal Immunology, researchers have shown that anti-TB drug… [+3006 chars]" 134 | }, 135 | { 136 | "source": { 137 | "id": null, 138 | "name": "Ndtv.com" 139 | }, 140 | "author": null, 141 | "title": "Mayawati, Akhilesh Yadav Attack PM Over \"Secret Letter\" To Imran Khan - NDTV News", 142 | "description": "Uttar Pradesh allies Mayawati and Akhilesh Yadav have attacked Prime Minister Narendra Modi for sending a letter to Pakistan PM Imran Khan on the country's national day, saying it should have been written after taking permission from people of the country.", 143 | "url": "https://www.ndtv.com/india-news/lok-sabha-election-2019-mayawati-akhilesh-yadav-attack-pm-modi-over-secret-letter-to-imran-khan-2011945", 144 | "urlToImage": "https://c.ndtvimg.com/2019-01/hae44spo_akhilesh-mayawati_625x300_07_January_19.jpg", 145 | "publishedAt": "2019-03-24T02:03:00Z", 146 | "content": "New Delhi: Uttar Pradesh allies Mayawati and Akhilesh Yadav have attacked Prime Minister Narendra Modi for sending a letter to Pakistan PM Imran Khan on the country's national day, saying it should have been written after taking permission from people of the … [+2075 chars]" 147 | }, 148 | { 149 | "source": { 150 | "id": null, 151 | "name": "Ndtv.com" 152 | }, 153 | "author": null, 154 | "title": "Over 700 Killed In Africa Cyclone, UN Says More Floods Likely - NDTV News", 155 | "description": "Mozambique reported scores more deaths on Saturday from a cyclone and floods around southern Africa that have killed at least 732 people and left thousands in desperate need of help, many on rooftops and trees.", 156 | "url": "https://www.ndtv.com/world-news/more-than-700-killed-in-africas-cyclone-un-says-more-floods-likely-2011940", 157 | "urlToImage": "https://c.ndtvimg.com/2019-03/faqua2mg_africa_625x300_24_March_19.jpg", 158 | "publishedAt": "2019-03-24T01:55:00Z", 159 | "content": "A woman stands besides a car that was swept away with debris by Cyclone Idai in Chimanimani. (Reuters) Beira: Mozambique reported scores more deaths on Saturday from a cyclone and floods around southern Africa that have killed at least 732 people and left tho… [+3094 chars]" 160 | }, 161 | { 162 | "source": { 163 | "id": null, 164 | "name": "Cricbuzz.com" 165 | }, 166 | "author": "", 167 | "title": "RCB blighted by poor decisions again - Cricbuzz", 168 | "description": "If not reading the track at Chepauk properly wasn't bad enough, the shot selection by the batsmen left many wondering if anything has changed at all", 169 | "url": "https://www.cricbuzz.com/cricket-news/107263/rcb-blighted-by-poor-decisions-again", 170 | "urlToImage": "http://www.cricbuzz.com/a/img/v1/0x0/i1/c1/1.jpg", 171 | "publishedAt": "2019-03-24T00:58:39Z", 172 | "content": "If not reading the track at Chepauk properly wasn't bad enough, the shot selection by the batsmen left many wondering if anything has changed at all. © AFP Royal Challengers Bangalore will hold the Chepauk pitch as an alibi, but even that will be modest compe… [+5894 chars]" 173 | }, 174 | { 175 | "source": { 176 | "id": "the-times-of-india", 177 | "name": "The Times of India" 178 | }, 179 | "author": "Pushpa Narayan", 180 | "title": "‘Increase in rate of TB cases shows better awareness’ - Times of India", 181 | "description": "CHENNAI: There has been a 20 point increase in the rate of active TB cases reported per 10 lakh population in the private and public sector in Tamil N.", 182 | "url": "https://timesofindia.indiatimes.com/city/chennai/increase-in-rate-of-tb-cases-shows-better-awareness/articleshow/68541846.cms", 183 | "urlToImage": "https://static.toiimg.com/thumb/msid-68544294,width-1070,height-580,imgsize-1592972,resizemode-6,overlay-toi_sw,pt-32,y_pad-40/photo.jpg", 184 | "publishedAt": "2019-03-23T23:13:00Z", 185 | "content": "CHENNAI: There has been a 20 point increase in the rate of active TB cases reported per 10 lakh population in the private and public sector in Tamil Nadu in the past one year. But the state health department is not shy about announcing it, because the spike i… [+2542 chars]" 186 | }, 187 | { 188 | "source": { 189 | "id": null, 190 | "name": "Espn.in" 191 | }, 192 | "author": null, 193 | "title": "Spain vs. Norway - Football Match Report - March 24, 2019 - ESPN", 194 | "description": "Get a report of the Spain vs. Norway 2020 European Championship Qualifying, Group Stage football match.", 195 | "url": "http://www.espn.in/football/report?gameId=529072", 196 | "urlToImage": "https://a4.espncdn.com/combiner/i?img=%2Fphoto%2F2019%2F0323%2Fr518706_1296x729_16%2D9.jpg", 197 | "publishedAt": "2019-03-23T22:30:22Z", 198 | "content": "Spain captain Sergio Ramos scored another \"Panenka\" penalty to give his side a 2-1 home win over Norway on Saturday in their opening Euro 2020 qualifier but it was a far from convincing display from Luis Enrique's side. Spain made a slick start and took the l… [+3223 chars]" 199 | }, 200 | { 201 | "source": { 202 | "id": null, 203 | "name": "Hindustantimes.com" 204 | }, 205 | "author": "HT Correspondent", 206 | "title": "IPL 2019, KKR vs SRH Live Streaming: When and Where to Watch, Live Coverage on TV and Online - Hindustan Times", 207 | "description": "Here’s a look at when and where to watch the IPL match Kolkata Knight Riders and Sunrisers Hyderabad.", 208 | "url": "https://www.hindustantimes.com/cricket/ipl-2019-kkr-vs-srh-live-streaming-when-and-where-to-watch-live-coverage-on-tv-and-online/story-X3OVWpuPxh48h2b8P7CoCM.html", 209 | "urlToImage": "https://www.hindustantimes.com/rf/image_size_960x540/HT/p2/2019/03/24/Pictures/cricket-t20-ind-ipl-hyderabad_600991aa-4da4-11e9-9111-3135b956f139.jpg", 210 | "publishedAt": "2019-03-23T19:49:00Z", 211 | "content": "Comeback man David Warner would be the cynosure of all eyes when last year’s finalists Sunrisers Hyderabad open their campaign against two-time champions Kolkata Knight Riders in the 12th Indian Premier League in Kolkata on Sunday. Under Warner’s captaincy Su… [+1326 chars]" 212 | }, 213 | { 214 | "source": { 215 | "id": null, 216 | "name": "Ndtv.com" 217 | }, 218 | "author": null, 219 | "title": "Filmfare Awards 2019 Highlights: Alia Bhatt-Ranbir Kapoor, Ranveer Singh-Deepika Padukone Catching Up To Tribute To Sridevi - NDTV News", 220 | "description": "Filmfare Awards 2019 Highlights: There was an epic hug between Ranbir Kapoor and Ranveer Singh", 221 | "url": "https://www.ndtv.com/entertainment/filmfare-awards-2019-highlights-alia-bhatt-ranbir-kapoor-ranveer-singh-deepika-padukone-catching-up-2011892", 222 | "urlToImage": "https://c.ndtvimg.com/2019-03/r7v2ltk_alia-bhatt-filmfare_625x300_24_March_19.jpg", 223 | "publishedAt": "2019-03-23T18:40:00Z", 224 | "content": "Alia, Ranbir, Deepika, Ranveer caught up at the Filmfare Awards 2019 (courtesy taranadarsh ) New Delhi: At the 64th edition of the Filmfare Awards, Alia Bhatt's Raazi won Best Film and Ranbir Kapoor also won Best Actor for Sanju but ahead of that, all eyes we… [+3141 chars]" 225 | }, 226 | { 227 | "source": { 228 | "id": null, 229 | "name": "Ndtv.com" 230 | }, 231 | "author": null, 232 | "title": "NASA Publishes Images of the Meteor No One Saw - NDTV", 233 | "description": "NASA on Friday published satellite photos of a powerful meteor which appeared just above the Bering Sea on December 18 but went unnoticed until months later.", 234 | "url": "https://gadgets.ndtv.com/science/news/nasa-publishes-images-of-the-meteor-no-one-saw-2011874", 235 | "urlToImage": "https://i.gadgets360cdn.com/large/NASA_main_fireball_1553361655308.jpg", 236 | "publishedAt": "2019-03-23T17:26:48Z", 237 | "content": "NASA on Friday published satellite photos of a powerful meteor which appeared just above the Bering Sea on December 18 but went unnoticed until months later. The explosion unleashed around 173 kilotons of energy, more than 10 times that of the atomic bomb bla… [+1311 chars]" 238 | }, 239 | { 240 | "source": { 241 | "id": null, 242 | "name": "Business-standard.com" 243 | }, 244 | "author": "Abhijit Lele & Aneesh Phadnis", 245 | "title": "Lenders to cash-strapped Jet Airways weigh open auction process - Business Standard", 246 | "description": "Stake sale likely to be completed in 2 months; banks to infuse Rs 1,500 cr immediately to restore normalcy", 247 | "url": "https://www.business-standard.com/article/companies/lenders-to-cash-strapped-jet-airways-weigh-open-auction-process-119032300838_1.html", 248 | "urlToImage": "https://bsmedia.business-standard.com/_media/bs/img/article/2017-05/18/full/1495100067-362.jpg", 249 | "publishedAt": "2019-03-23T17:19:00Z", 250 | "content": "Lenders to cash-strapped Jet Airways plan to sell their stake in the airline through an open auction process over the next two months, seeking maximum value for the asset. In the meantime, the consortium led by State Bank of India will provide emergency fundi… [+4098 chars]" 251 | }, 252 | { 253 | "source": { 254 | "id": "the-hindu", 255 | "name": "The Hindu" 256 | }, 257 | "author": "Shubashree Desikan", 258 | "title": "Ooty’s muon detection facility measures potential of thundercloud - The Hindu", 259 | "description": "At 1.3 gigavolts, this cloud had ten times higher potential than the previous record in a cloud", 260 | "url": "https://www.thehindu.com/sci-tech/science/ootys-muon-detection-facility-measures-potential-of-thundercloud/article26619932.ece", 261 | "urlToImage": "https://www.thehindu.com/sci-tech/science/e6604l/article26619931.ece/ALTERNATES/LANDSCAPE_615/TH22Thundercloudcol-1", 262 | "publishedAt": "2019-03-23T13:35:00Z", 263 | "content": "For the first time in the world, researchers at the GRAPES-3 muon telescope facility in Ooty have measured the electrical potential, size and height of a thundercloud that passed overhead on December 1, 2014. At 1.3 gigavolts (GV), this cloud had 10 times hig… [+2980 chars]" 264 | } 265 | ] 266 | } --------------------------------------------------------------------------------