├── .circleci └── config.yml ├── .gitignore ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── example │ │ └── arc │ │ ├── ExampleInstrumentedTest.java │ │ └── view │ │ └── ui │ │ └── main │ │ └── MainActivityTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── example │ │ │ └── arc │ │ │ ├── AApp.java │ │ │ ├── core │ │ │ ├── AppSchedulerProvider.java │ │ │ ├── BaseViewModel.java │ │ │ ├── Constants.java │ │ │ ├── SchedulerProvider.java │ │ │ └── base │ │ │ │ ├── ArticleUtils.java │ │ │ │ └── BaseActivity.java │ │ │ ├── di │ │ │ ├── ActivityBuilder.java │ │ │ ├── AppComponent.java │ │ │ ├── ViewModelKey.java │ │ │ └── module │ │ │ │ ├── AppModule.java │ │ │ │ ├── NetworkModule.java │ │ │ │ └── ViewModelModule.java │ │ │ ├── model │ │ │ ├── api │ │ │ │ └── Api.java │ │ │ ├── data │ │ │ │ ├── Article.java │ │ │ │ ├── Articles.java │ │ │ │ ├── Source.java │ │ │ │ └── Sources.java │ │ │ └── db │ │ │ │ ├── AppDatabase.java │ │ │ │ ├── ArticleDao.java │ │ │ │ ├── DataRepository.java │ │ │ │ └── SourceDao.java │ │ │ ├── view │ │ │ ├── adapter │ │ │ │ ├── NewsAdapter.java │ │ │ │ └── SourcesAdapter.java │ │ │ └── ui │ │ │ │ ├── DetailActivity.java │ │ │ │ ├── MainActivity.java │ │ │ │ └── SourcesActivity.java │ │ │ └── viewmodel │ │ │ ├── DetailViewModel.java │ │ │ ├── MainViewModel.java │ │ │ ├── SourceViewModel.java │ │ │ └── ViewModelFactory.java │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ ├── ic_launcher_background.xml │ │ └── square_border.xml │ │ ├── font │ │ ├── .xml │ │ └── allerta_stencil.xml │ │ ├── layout │ │ ├── activity_detail.xml │ │ ├── activity_main.xml │ │ ├── activity_sources.xml │ │ ├── item_news_view.xml │ │ └── item_source_view.xml │ │ ├── menu │ │ └── main_menu.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── 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 │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── font_certs.xml │ │ ├── preloaded_fonts.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── example │ └── arc │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── images ├── detail.jpg ├── feed.jpg └── resources.jpg └── settings.gradle /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | branches: 5 | only: 6 | - master 7 | working_directory: ~/android-architecture-components 8 | docker: 9 | - image: circleci/android:api-27-alpha 10 | environment: 11 | JVM_OPTS: -Xmx3200m 12 | steps: 13 | - checkout 14 | - restore_cache: 15 | key: jars-{{ checksum "build.gradle" }}-{{ checksum "app/build.gradle" }} 16 | - run: 17 | name: Download Dependencies 18 | command: ./gradlew androidDependencies 19 | - save_cache: 20 | paths: 21 | - ~/.gradle 22 | key: jars-{{ checksum "build.gradle" }}-{{ checksum "app/build.gradle" }} 23 | - run: 24 | name: Run Tests 25 | command: ./gradlew lint test 26 | - store_artifacts: 27 | path: app/build/reports 28 | destination: reports 29 | - store_test_results: 30 | path: app/build/test-results 31 | - run: 32 | name: Build 33 | command: ./gradlew assembleRelease -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | .DS_Store 5 | /build 6 | /captures 7 | .externalNativeBuild 8 | /.idea/ 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 ihsan BAL 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Android Architecture Components Sample - [![CircleCI](https://circleci.com/gh/ihsanbal/android-architecture-components.svg?style=svg)](https://circleci.com/gh/ihsanbal/android-architecture-components) 2 | =================================== 3 | 4 | A Sample project of the news app that uses ViewModels, LiveData, Locale Cache with Room and Data Binding with an MVVM architecture. 5 | 6 |

7 | 8 | 9 | 10 |

11 | 12 | ### Used Technologies and Patterns 13 | - Room 14 | - LiveData 15 | - Data Binding 16 | - RxJava2 17 | - Dagger2 18 | - OkHttp3 19 | - Retrofit2 20 | - ViewModels 21 | - MVVM (Model-View-ViewModel) 22 | ### Guide 23 | - Replace [API_KEY](https://github.com/ihsanbal/android-architecture-components/blob/master/gradle.properties#L19) with yours via [NEWS API](https://newsapi.org) 24 | - Feel free to contribute. 25 | - Enjoy! 26 | ### Reporting Issues 27 | The project is still under development, especially I am trying to learn more about popular patterns and Dagger2 but you can report issues or create with feature tag to discuss about ideas for keep project under development. I am thinking to start a blog series for describe what I did in this project. Keep in touch -> [Medium](https://medium.com/@ihsanbal) 28 | ### Next Steps 29 | - Build Unit & UI tests 30 | - Duplicate project with Kotlin 31 | - Start blog series 32 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | def buildVersionCode = new Date().format("yyMMddHHmm", TimeZone.getTimeZone("Europe/Istanbul")).toInteger() 4 | def buildVersionName = "1.0" 5 | 6 | android { 7 | compileSdkVersion 27 8 | defaultConfig { 9 | applicationId "com.example.arc" 10 | minSdkVersion 21 11 | targetSdkVersion 27 12 | versionCode buildVersionCode 13 | versionName buildVersionName 14 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 15 | buildConfigField("String", "API_KEY", "\"" + API_KEY + "\"") 16 | buildConfigField("String", "END_POINT", "\"https://newsapi.org/v2/\"") 17 | } 18 | 19 | buildTypes { 20 | release { 21 | postprocessing { 22 | removeUnusedCode true 23 | removeUnusedResources true 24 | obfuscate true 25 | optimizeCode true 26 | proguardFile 'proguard-rules.pro' 27 | } 28 | } 29 | debug { 30 | applicationIdSuffix '.debug' 31 | } 32 | } 33 | 34 | flavorDimensions 'dimension' 35 | 36 | productFlavors { 37 | internal { 38 | applicationId 'com.example.arc.internal' 39 | resValue "string", "app_name", '"Arc Internal"' 40 | } 41 | 42 | production { 43 | applicationId 'com.example.arc' 44 | resValue "string", "app_name", '"Arc"' 45 | } 46 | } 47 | dataBinding { 48 | enabled = true 49 | } 50 | compileOptions { 51 | sourceCompatibility JavaVersion.VERSION_1_8 52 | targetCompatibility JavaVersion.VERSION_1_8 53 | } 54 | } 55 | 56 | dependencies { 57 | def supportVersion = "27.0.2" 58 | def daggerVersion = "2.14.1" 59 | def okHttpVersion = "3.9.1" 60 | def interceptorVersion = "2.0.4" 61 | def retrofitVersion = "2.3.0" 62 | def archVersion = "1.0.0" 63 | def rxAndroidVersion = "2.0.1" 64 | def rxJavaVersion = "2.1.7" 65 | def constVersion = "1.0.2" 66 | def glideVersion = "4.4.0" 67 | def espressoVersion = "3.0.1" 68 | 69 | implementation fileTree(include: ['*.jar'], dir: 'libs') 70 | 71 | //noinspection GradleCompatible,Support 72 | implementation "com.android.support:appcompat-v7:$supportVersion" 73 | implementation "com.android.support.constraint:constraint-layout:$constVersion" 74 | implementation "com.android.support:design:$supportVersion" 75 | implementation "com.android.support:cardview-v7:$supportVersion" 76 | 77 | //ArcComponents 78 | implementation "android.arch.lifecycle:extensions:$archVersion" 79 | implementation "android.arch.persistence.room:runtime:$archVersion" 80 | implementation "android.arch.lifecycle:runtime:$archVersion" 81 | // implementation "android.arch.persistence.room:rxjava2:$archVersion" 82 | annotationProcessor "android.arch.lifecycle:compiler:$archVersion" 83 | annotationProcessor "android.arch.persistence.room:compiler:$archVersion" 84 | 85 | //Network 86 | implementation "com.squareup.okhttp3:okhttp:$okHttpVersion" 87 | implementation "com.squareup.retrofit2:retrofit:$retrofitVersion" 88 | implementation "com.squareup.retrofit2:adapter-rxjava2:$retrofitVersion" 89 | implementation "com.squareup.retrofit2:converter-gson:$retrofitVersion" 90 | implementation "com.github.bumptech.glide:glide:$glideVersion" 91 | annotationProcessor "com.github.bumptech.glide:compiler:$glideVersion" 92 | implementation("com.github.ihsanbal:LoggingInterceptor:$interceptorVersion") { 93 | exclude group: 'org.json', module: 'json' 94 | } 95 | 96 | //Rx 97 | implementation "io.reactivex.rxjava2:rxandroid:$rxAndroidVersion" 98 | implementation "io.reactivex.rxjava2:rxjava:$rxJavaVersion" 99 | 100 | //Di 101 | implementation "com.google.dagger:dagger:$daggerVersion" 102 | annotationProcessor "com.google.dagger:dagger-compiler:$daggerVersion" 103 | implementation "com.google.dagger:dagger-android:$daggerVersion" 104 | implementation "com.google.dagger:dagger-android-support:$daggerVersion" 105 | annotationProcessor "com.google.dagger:dagger-android-processor:$daggerVersion" 106 | 107 | //Test 108 | testImplementation 'junit:junit:4.12' 109 | androidTestImplementation 'com.android.support.test:runner:1.0.1' 110 | androidTestImplementation "com.android.support.test.espresso:espresso-core:$espressoVersion" 111 | androidTestImplementation "com.android.support.test.espresso:espresso-contrib:$espressoVersion" 112 | debugImplementation "com.android.support.test.espresso.idling:idling-concurrent:$espressoVersion" 113 | debugImplementation("com.jakewharton.espresso:okhttp3-idling-resource:1.0.0") { 114 | exclude module: 'support-annotations' 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /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 | #Retrofit2 23 | -dontwarn retrofit2.** 24 | -keep class retrofit2.** { *; } 25 | -keepattributes Signature 26 | -keepattributes Exceptions 27 | 28 | #OkHttp3 29 | -dontwarn com.squareup.okhttp3.** 30 | -keep class com.squareup.okhttp3.** { *;} 31 | -dontwarn com.squareup.okhttp.** 32 | -keep class com.squareup.okhttp.** { *;} 33 | -keep class com.bumptech.glide.integration.okhttp.OkHttpGlideModule 34 | -keep public class * implements com.bumptech.glide.module.GlideModule 35 | -dontwarn okio.** 36 | -dontwarn javax.annotation.Nullable 37 | -dontwarn javax.annotation.ParametersAreNonnullByDefault 38 | 39 | #Dagger2 40 | -dontwarn com.google.errorprone.annotations.* -------------------------------------------------------------------------------- /app/src/androidTest/java/com/example/arc/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.example.arc; 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.assertEquals; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.example.arc", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/example/arc/view/ui/main/MainActivityTest.java: -------------------------------------------------------------------------------- 1 | package com.example.arc.view.ui.main; 2 | 3 | import android.support.test.espresso.ViewInteraction; 4 | import android.support.test.rule.ActivityTestRule; 5 | import android.support.test.runner.AndroidJUnit4; 6 | import android.test.suitebuilder.annotation.LargeTest; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.view.ViewParent; 10 | 11 | import com.example.arc.R; 12 | import com.example.arc.view.ui.MainActivity; 13 | 14 | import org.hamcrest.Description; 15 | import org.hamcrest.Matcher; 16 | import org.hamcrest.TypeSafeMatcher; 17 | import org.junit.Rule; 18 | import org.junit.Test; 19 | import org.junit.runner.RunWith; 20 | 21 | import static android.support.test.espresso.Espresso.onView; 22 | import static android.support.test.espresso.action.ViewActions.click; 23 | import static android.support.test.espresso.contrib.RecyclerViewActions.actionOnItemAtPosition; 24 | import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; 25 | import static android.support.test.espresso.matcher.ViewMatchers.withClassName; 26 | import static android.support.test.espresso.matcher.ViewMatchers.withContentDescription; 27 | import static android.support.test.espresso.matcher.ViewMatchers.withId; 28 | import static android.support.test.espresso.matcher.ViewMatchers.withText; 29 | import static org.hamcrest.Matchers.allOf; 30 | import static org.hamcrest.Matchers.is; 31 | 32 | @LargeTest 33 | @RunWith(AndroidJUnit4.class) 34 | public class MainActivityTest { 35 | 36 | @Rule 37 | public ActivityTestRule mActivityTestRule = new ActivityTestRule<>(MainActivity.class); 38 | 39 | @Test 40 | public void mainActivityTest() { 41 | // Added a sleep statement to match the app's execution delay. 42 | // The recommended way to handle such scenarios is to use Espresso idling resources: 43 | // https://google.github.io/android-testing-support-library/docs/espresso/idling-resource/index.html 44 | try { 45 | Thread.sleep(1700); 46 | } catch (InterruptedException e) { 47 | e.printStackTrace(); 48 | } 49 | ViewInteraction appCompatButton = onView( 50 | allOf(withId(R.id.button), withText("Add"), 51 | childAtPosition( 52 | allOf(withId(R.id.constraintLayout), 53 | childAtPosition( 54 | withId(R.id.recycler_view), 55 | 0)), 56 | 1), 57 | isDisplayed())); 58 | appCompatButton.perform(click()); 59 | 60 | ViewInteraction appCompatButton2 = onView( 61 | allOf(withId(R.id.button), withText("Add"), 62 | childAtPosition( 63 | allOf(withId(R.id.constraintLayout), 64 | childAtPosition( 65 | withId(R.id.recycler_view), 66 | 1)), 67 | 1), 68 | isDisplayed())); 69 | appCompatButton2.perform(click()); 70 | 71 | ViewInteraction appCompatButton3 = onView( 72 | allOf(withId(R.id.snackbar_action), withText("Start"), 73 | childAtPosition( 74 | childAtPosition( 75 | withClassName(is("android.support.design.widget.Snackbar$SnackbarLayout")), 76 | 0), 77 | 1), 78 | isDisplayed())); 79 | appCompatButton3.perform(click()); 80 | 81 | // Added a sleep statement to match the app's execution delay. 82 | // The recommended way to handle such scenarios is to use Espresso idling resources: 83 | // https://google.github.io/android-testing-support-library/docs/espresso/idling-resource/index.html 84 | try { 85 | Thread.sleep(1700); 86 | } catch (InterruptedException e) { 87 | e.printStackTrace(); 88 | } 89 | 90 | ViewInteraction recyclerView = onView( 91 | allOf(withId(R.id.recycler_view), 92 | childAtPosition( 93 | withClassName(is("android.support.constraint.ConstraintLayout")), 94 | 1))); 95 | recyclerView.perform(actionOnItemAtPosition(0, click())); 96 | 97 | // Added a sleep statement to match the app's execution delay. 98 | // The recommended way to handle such scenarios is to use Espresso idling resources: 99 | // https://google.github.io/android-testing-support-library/docs/espresso/idling-resource/index.html 100 | try { 101 | Thread.sleep(2000); 102 | } catch (InterruptedException e) { 103 | e.printStackTrace(); 104 | } 105 | 106 | ViewInteraction appCompatImageButton = onView( 107 | allOf(withContentDescription("Navigate up"), 108 | childAtPosition( 109 | allOf(withId(R.id.toolbar), 110 | childAtPosition( 111 | withClassName(is("android.support.constraint.ConstraintLayout")), 112 | 0)), 113 | 0), 114 | isDisplayed())); 115 | appCompatImageButton.perform(click()); 116 | 117 | } 118 | 119 | private static Matcher childAtPosition( 120 | final Matcher parentMatcher, final int position) { 121 | 122 | return new TypeSafeMatcher() { 123 | @Override 124 | public void describeTo(Description description) { 125 | description.appendText("Child at position " + position + " in parent "); 126 | parentMatcher.describeTo(description); 127 | } 128 | 129 | @Override 130 | public boolean matchesSafely(View view) { 131 | ViewParent parent = view.getParent(); 132 | return parent instanceof ViewGroup && parentMatcher.matches(parent) 133 | && view.equals(((ViewGroup) parent).getChildAt(position)); 134 | } 135 | }; 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/arc/AApp.java: -------------------------------------------------------------------------------- 1 | package com.example.arc; 2 | 3 | import com.example.arc.di.DaggerAppComponent; 4 | 5 | import dagger.android.AndroidInjector; 6 | import dagger.android.support.DaggerApplication; 7 | 8 | /** 9 | * @author ihsan on 11/29/17. 10 | */ 11 | 12 | public class AApp extends DaggerApplication { 13 | 14 | @Override 15 | protected AndroidInjector applicationInjector() { 16 | return DaggerAppComponent.builder().create(this); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/arc/core/AppSchedulerProvider.java: -------------------------------------------------------------------------------- 1 | package com.example.arc.core; 2 | 3 | import io.reactivex.Scheduler; 4 | import io.reactivex.android.schedulers.AndroidSchedulers; 5 | import io.reactivex.schedulers.Schedulers; 6 | 7 | /** 8 | * @author ihsan on 12/10/17. 9 | */ 10 | 11 | public class AppSchedulerProvider implements SchedulerProvider { 12 | 13 | @Override 14 | public Scheduler ui() { 15 | return AndroidSchedulers.mainThread(); 16 | } 17 | 18 | @Override 19 | public Scheduler io() { 20 | return Schedulers.io(); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/arc/core/BaseViewModel.java: -------------------------------------------------------------------------------- 1 | package com.example.arc.core; 2 | 3 | /** 4 | * @author ihsan on 2/28/18. 5 | */ 6 | 7 | public interface BaseViewModel { 8 | void onClear(); 9 | } 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/arc/core/Constants.java: -------------------------------------------------------------------------------- 1 | package com.example.arc.core; 2 | 3 | /** 4 | * @author ihsan on 12/18/17. 5 | */ 6 | 7 | public class Constants { 8 | public static final String PREFERENCES = "sp:reference"; 9 | public static final String DB = "db-source"; 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/arc/core/SchedulerProvider.java: -------------------------------------------------------------------------------- 1 | package com.example.arc.core; 2 | 3 | import io.reactivex.Scheduler; 4 | 5 | /** 6 | * @author ihsan on 12/10/17. 7 | */ 8 | 9 | public interface SchedulerProvider { 10 | 11 | Scheduler ui(); 12 | 13 | Scheduler io(); 14 | 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/arc/core/base/ArticleUtils.java: -------------------------------------------------------------------------------- 1 | package com.example.arc.core.base; 2 | 3 | import android.text.TextUtils; 4 | 5 | import com.example.arc.model.data.Article; 6 | import com.example.arc.model.data.Articles; 7 | 8 | import java.text.ParseException; 9 | import java.text.SimpleDateFormat; 10 | import java.util.Date; 11 | import java.util.Locale; 12 | 13 | /** 14 | * @author ihsan on 2/28/18. 15 | */ 16 | 17 | public class ArticleUtils { 18 | 19 | public static Articles formatDate(Articles articles) throws ParseException { 20 | SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", 21 | Locale.getDefault()); 22 | for (Article article : articles.getArticles()) { 23 | if (!TextUtils.isEmpty(article.getPublishedAt())) { 24 | Date date = simpleDateFormat.parse(article.getPublishedAt()); 25 | article.setPublishedAt(new SimpleDateFormat("h:mm a", Locale.getDefault()).format(date)); 26 | } 27 | } 28 | return articles; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/arc/core/base/BaseActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.arc.core.base; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.arch.lifecycle.ViewModel; 5 | import android.arch.lifecycle.ViewModelProvider; 6 | import android.arch.lifecycle.ViewModelProviders; 7 | import android.databinding.DataBindingUtil; 8 | import android.databinding.ViewDataBinding; 9 | import android.os.Bundle; 10 | import android.support.annotation.LayoutRes; 11 | import android.support.annotation.Nullable; 12 | 13 | import javax.inject.Inject; 14 | 15 | import dagger.android.support.DaggerAppCompatActivity; 16 | 17 | /** 18 | * @author ihsan on 11/29/17. 19 | */ 20 | 21 | public abstract class BaseActivity extends DaggerAppCompatActivity { 22 | 23 | @Inject 24 | ViewModelProvider.Factory viewModelFactory; 25 | 26 | @SuppressWarnings("unchecked") 27 | @SuppressLint("CheckResult") 28 | @Override 29 | protected void onCreate(@Nullable Bundle savedInstanceState) { 30 | super.onCreate(savedInstanceState); 31 | ViewDataBinding binding = DataBindingUtil.setContentView(this, getLayoutResId()); 32 | ViewModel viewModel = ViewModelProviders.of(this, viewModelFactory).get(getViewModel()); 33 | onCreate(savedInstanceState, (M) viewModel, (B) binding); 34 | } 35 | 36 | protected abstract Class getViewModel(); 37 | 38 | protected abstract void onCreate(Bundle instance, M viewModel, B binding); 39 | 40 | protected abstract 41 | @LayoutRes 42 | int getLayoutResId(); 43 | 44 | } 45 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/arc/di/ActivityBuilder.java: -------------------------------------------------------------------------------- 1 | package com.example.arc.di; 2 | 3 | import com.example.arc.view.ui.DetailActivity; 4 | import com.example.arc.view.ui.MainActivity; 5 | import com.example.arc.view.ui.SourcesActivity; 6 | 7 | import dagger.Module; 8 | import dagger.android.ContributesAndroidInjector; 9 | 10 | /** 11 | * @author ihsan on 12/2/17. 12 | */ 13 | 14 | @Module 15 | abstract class ActivityBuilder { 16 | 17 | @ContributesAndroidInjector 18 | abstract SourcesActivity bindSourceActivity(); 19 | 20 | @ContributesAndroidInjector 21 | abstract MainActivity bindMainActivity(); 22 | 23 | @ContributesAndroidInjector 24 | abstract DetailActivity bindDetailActivity(); 25 | 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/arc/di/AppComponent.java: -------------------------------------------------------------------------------- 1 | package com.example.arc.di; 2 | 3 | import com.example.arc.AApp; 4 | import com.example.arc.di.module.AppModule; 5 | 6 | import javax.inject.Singleton; 7 | 8 | import dagger.Component; 9 | import dagger.android.AndroidInjector; 10 | import dagger.android.support.AndroidSupportInjectionModule; 11 | 12 | /** 13 | * @author ihsan on 12/2/17. 14 | */ 15 | @Singleton 16 | @Component(modules = { 17 | AndroidSupportInjectionModule.class, 18 | AppModule.class, 19 | ActivityBuilder.class}) 20 | public interface AppComponent extends AndroidInjector { 21 | 22 | @Component.Builder 23 | abstract class Builder extends AndroidInjector.Builder { 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/arc/di/ViewModelKey.java: -------------------------------------------------------------------------------- 1 | package com.example.arc.di; 2 | 3 | import android.arch.lifecycle.ViewModel; 4 | 5 | import java.lang.annotation.Documented; 6 | import java.lang.annotation.ElementType; 7 | import java.lang.annotation.Retention; 8 | import java.lang.annotation.RetentionPolicy; 9 | import java.lang.annotation.Target; 10 | 11 | import dagger.MapKey; 12 | 13 | /** 14 | * @author ihsan on 12/27/17. 15 | */ 16 | 17 | @Documented 18 | @Target({ElementType.METHOD}) 19 | @Retention(RetentionPolicy.RUNTIME) 20 | @MapKey 21 | public @interface ViewModelKey { 22 | Class value(); 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/arc/di/module/AppModule.java: -------------------------------------------------------------------------------- 1 | package com.example.arc.di.module; 2 | 3 | import android.arch.persistence.room.Room; 4 | import android.content.Context; 5 | import android.content.SharedPreferences; 6 | 7 | import com.example.arc.AApp; 8 | import com.example.arc.core.AppSchedulerProvider; 9 | import com.example.arc.core.Constants; 10 | import com.example.arc.model.db.AppDatabase; 11 | 12 | import javax.inject.Singleton; 13 | 14 | import dagger.Module; 15 | import dagger.Provides; 16 | 17 | /** 18 | * @author ihsan on 12/2/17. 19 | */ 20 | 21 | @Module(includes = {ViewModelModule.class, NetworkModule.class}) 22 | public class AppModule { 23 | 24 | @Provides 25 | @Singleton 26 | Context provideContext(AApp application) { 27 | return application.getApplicationContext(); 28 | } 29 | 30 | @Provides 31 | @Singleton 32 | AppSchedulerProvider provideSchedulerProvider() { 33 | return new AppSchedulerProvider(); 34 | } 35 | 36 | @Provides 37 | @Singleton 38 | SharedPreferences provideSplashViewModel(Context context) { 39 | return context.getSharedPreferences(Constants.PREFERENCES, Context.MODE_PRIVATE); 40 | } 41 | 42 | @Provides 43 | @Singleton 44 | AppDatabase provideAppDatabase(Context context) { 45 | return Room.databaseBuilder(context, 46 | AppDatabase.class, Constants.DB) 47 | .allowMainThreadQueries() 48 | .build(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/arc/di/module/NetworkModule.java: -------------------------------------------------------------------------------- 1 | package com.example.arc.di.module; 2 | 3 | import com.example.arc.BuildConfig; 4 | import com.example.arc.model.api.Api; 5 | import com.ihsanbal.logging.Level; 6 | import com.ihsanbal.logging.LoggingInterceptor; 7 | 8 | import javax.inject.Singleton; 9 | 10 | import dagger.Module; 11 | import dagger.Provides; 12 | import okhttp3.OkHttpClient; 13 | import okhttp3.internal.platform.Platform; 14 | import retrofit2.Retrofit; 15 | import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; 16 | import retrofit2.converter.gson.GsonConverterFactory; 17 | 18 | /** 19 | * @author ihsan on 2/28/18. 20 | */ 21 | @Module 22 | public class NetworkModule { 23 | 24 | @Provides 25 | @Singleton 26 | LoggingInterceptor provideInterceptor() { 27 | return new LoggingInterceptor.Builder() 28 | .loggable(BuildConfig.DEBUG) 29 | .setLevel(Level.BASIC) 30 | .log(Platform.INFO) 31 | .request("Request") 32 | .response("Response") 33 | .addQueryParam("apiKey", BuildConfig.API_KEY) 34 | .build(); 35 | } 36 | 37 | @Provides 38 | @Singleton 39 | OkHttpClient provideOkHttp(LoggingInterceptor interceptor) { 40 | return new OkHttpClient.Builder() 41 | .addNetworkInterceptor(interceptor) 42 | .build(); 43 | } 44 | 45 | @Provides 46 | @Singleton 47 | Retrofit provideRetrofit(OkHttpClient client) { 48 | return new Retrofit.Builder() 49 | .addConverterFactory(GsonConverterFactory.create()) 50 | .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) 51 | .baseUrl(BuildConfig.END_POINT) 52 | .client(client) 53 | .build(); 54 | } 55 | 56 | @Provides 57 | @Singleton 58 | Api provideApi(Retrofit retrofit) { 59 | return retrofit.create(Api.class); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/arc/di/module/ViewModelModule.java: -------------------------------------------------------------------------------- 1 | package com.example.arc.di.module; 2 | 3 | import android.arch.lifecycle.ViewModel; 4 | import android.arch.lifecycle.ViewModelProvider; 5 | 6 | import com.example.arc.di.ViewModelKey; 7 | import com.example.arc.viewmodel.DetailViewModel; 8 | import com.example.arc.viewmodel.MainViewModel; 9 | import com.example.arc.viewmodel.SourceViewModel; 10 | import com.example.arc.viewmodel.ViewModelFactory; 11 | 12 | import dagger.Binds; 13 | import dagger.Module; 14 | import dagger.multibindings.IntoMap; 15 | 16 | /** 17 | * @author ihsan on 12/27/17. 18 | */ 19 | 20 | @SuppressWarnings("WeakerAccess") 21 | @Module 22 | public abstract class ViewModelModule { 23 | 24 | @Binds 25 | @IntoMap 26 | @ViewModelKey(MainViewModel.class) 27 | abstract ViewModel bindsMainViewModel(MainViewModel mainViewModel); 28 | 29 | @Binds 30 | @IntoMap 31 | @ViewModelKey(SourceViewModel.class) 32 | abstract ViewModel bindsSourceViewModel(SourceViewModel sourceViewModel); 33 | 34 | @Binds 35 | @IntoMap 36 | @ViewModelKey(DetailViewModel.class) 37 | abstract ViewModel bindsDetailViewModel(DetailViewModel sourceViewModel); 38 | 39 | @Binds 40 | abstract ViewModelProvider.Factory bindsViewModelFactory(ViewModelFactory viewModelFactory); 41 | 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/arc/model/api/Api.java: -------------------------------------------------------------------------------- 1 | package com.example.arc.model.api; 2 | 3 | import com.example.arc.model.data.Articles; 4 | import com.example.arc.model.data.Sources; 5 | 6 | import io.reactivex.Observable; 7 | import retrofit2.http.GET; 8 | import retrofit2.http.Query; 9 | 10 | /** 11 | * @author ihsan on 11/29/17. 12 | */ 13 | 14 | public interface Api { 15 | @GET("sources") 16 | Observable sources(); 17 | 18 | @GET("top-headlines") 19 | Observable topHeadlines(@Query("sources") String sources); 20 | 21 | 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/arc/model/data/Article.java: -------------------------------------------------------------------------------- 1 | package com.example.arc.model.data; 2 | 3 | import android.arch.persistence.room.ColumnInfo; 4 | import android.arch.persistence.room.Embedded; 5 | import android.arch.persistence.room.Entity; 6 | import android.arch.persistence.room.PrimaryKey; 7 | import android.databinding.BindingAdapter; 8 | import android.graphics.drawable.Drawable; 9 | import android.os.Parcel; 10 | import android.os.Parcelable; 11 | import android.widget.ImageView; 12 | 13 | import com.bumptech.glide.Glide; 14 | import com.bumptech.glide.request.RequestOptions; 15 | 16 | /** 17 | * @author ihsan on 12/19/17. 18 | */ 19 | @Entity 20 | public class Article implements Parcelable { 21 | 22 | @PrimaryKey(autoGenerate = true) 23 | private int id; 24 | @Embedded 25 | private Source source; 26 | private String author; 27 | private String title; 28 | @ColumnInfo(name = "desc_") 29 | private String description; 30 | @ColumnInfo(name = "url_") 31 | private String url; 32 | @ColumnInfo(name = "url_to_image") 33 | private String urlToImage; 34 | @ColumnInfo(name = "publish_at") 35 | private String publishedAt; 36 | 37 | public void setId(int id) { 38 | this.id = id; 39 | } 40 | 41 | public int getId() { 42 | return id; 43 | } 44 | 45 | public Source getSource() { 46 | return source; 47 | } 48 | 49 | public void setSource(Source source) { 50 | this.source = source; 51 | } 52 | 53 | public String getAuthor() { 54 | return author; 55 | } 56 | 57 | public void setAuthor(String author) { 58 | this.author = author; 59 | } 60 | 61 | public String getTitle() { 62 | return title; 63 | } 64 | 65 | public void setTitle(String title) { 66 | this.title = title; 67 | } 68 | 69 | public String getDescription() { 70 | return description; 71 | } 72 | 73 | public void setDescription(String description) { 74 | this.description = description; 75 | } 76 | 77 | public String getUrl() { 78 | return url; 79 | } 80 | 81 | public void setUrl(String url) { 82 | this.url = url; 83 | } 84 | 85 | public String getUrlToImage() { 86 | return urlToImage; 87 | } 88 | 89 | public void setUrlToImage(String urlToImage) { 90 | this.urlToImage = urlToImage; 91 | } 92 | 93 | public String getPublishedAt() { 94 | return publishedAt; 95 | } 96 | 97 | public void setPublishedAt(String publishedAt) { 98 | this.publishedAt = publishedAt; 99 | } 100 | 101 | @BindingAdapter({"app:glideBinding", "app:placeholder"}) 102 | public static void bindImage(ImageView imageView, String url, Drawable placeHolder) { 103 | Glide.with(imageView) 104 | .setDefaultRequestOptions(RequestOptions.placeholderOf(placeHolder)) 105 | .asBitmap() 106 | .load(url) 107 | .into(imageView); 108 | } 109 | 110 | @Override 111 | public int describeContents() { 112 | return 0; 113 | } 114 | 115 | @Override 116 | public void writeToParcel(Parcel dest, int flags) { 117 | dest.writeParcelable(this.source, flags); 118 | dest.writeString(this.author); 119 | dest.writeString(this.title); 120 | dest.writeString(this.description); 121 | dest.writeString(this.url); 122 | dest.writeString(this.urlToImage); 123 | dest.writeString(this.publishedAt); 124 | } 125 | 126 | public Article() { 127 | } 128 | 129 | protected Article(Parcel in) { 130 | this.source = in.readParcelable(Source.class.getClassLoader()); 131 | this.author = in.readString(); 132 | this.title = in.readString(); 133 | this.description = in.readString(); 134 | this.url = in.readString(); 135 | this.urlToImage = in.readString(); 136 | this.publishedAt = in.readString(); 137 | } 138 | 139 | public static final Parcelable.Creator
CREATOR = new Parcelable.Creator
() { 140 | @Override 141 | public Article createFromParcel(Parcel source) { 142 | return new Article(source); 143 | } 144 | 145 | @Override 146 | public Article[] newArray(int size) { 147 | return new Article[size]; 148 | } 149 | }; 150 | } 151 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/arc/model/data/Articles.java: -------------------------------------------------------------------------------- 1 | package com.example.arc.model.data; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * @author ihsan on 12/19/17. 7 | */ 8 | 9 | @SuppressWarnings("ALL") 10 | public class Articles { 11 | private List
articles; 12 | 13 | public List
getArticles() { 14 | return articles; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/arc/model/data/Source.java: -------------------------------------------------------------------------------- 1 | package com.example.arc.model.data; 2 | 3 | import android.arch.persistence.room.ColumnInfo; 4 | import android.arch.persistence.room.Entity; 5 | import android.arch.persistence.room.Ignore; 6 | import android.arch.persistence.room.PrimaryKey; 7 | import android.databinding.BaseObservable; 8 | import android.os.Parcel; 9 | import android.os.Parcelable; 10 | 11 | /** 12 | * @author ihsan on 12/3/17. 13 | */ 14 | @Entity 15 | public class Source extends BaseObservable implements Parcelable { 16 | 17 | @PrimaryKey(autoGenerate = true) 18 | private int uid; 19 | @ColumnInfo(name = "news_id") 20 | private String id; 21 | private String name; 22 | private String description; 23 | private String url; 24 | private String category; 25 | private String language; 26 | private String country; 27 | @Ignore 28 | private boolean isSelected; 29 | 30 | public Source() { 31 | //Default constructor 32 | } 33 | 34 | public Source(int uid, String id, String name, String description, String url, String category, 35 | String language, String country, boolean isSelected) { 36 | this.uid = uid; 37 | this.id = id; 38 | this.name = name; 39 | this.description = description; 40 | this.url = url; 41 | this.category = category; 42 | this.language = language; 43 | this.country = country; 44 | this.isSelected = isSelected; 45 | } 46 | 47 | public boolean isSelected() { 48 | return isSelected; 49 | } 50 | 51 | public void setSelected(boolean selected) { 52 | isSelected = selected; 53 | } 54 | 55 | public int getUid() { 56 | return uid; 57 | } 58 | 59 | public void setUid(int uid) { 60 | this.uid = uid; 61 | } 62 | 63 | public String getId() { 64 | return id; 65 | } 66 | 67 | public void setId(String id) { 68 | this.id = id; 69 | } 70 | 71 | public String getName() { 72 | return name; 73 | } 74 | 75 | public void setName(String name) { 76 | this.name = name; 77 | } 78 | 79 | public String getDescription() { 80 | return description; 81 | } 82 | 83 | public void setDescription(String description) { 84 | this.description = description; 85 | } 86 | 87 | public String getUrl() { 88 | return url; 89 | } 90 | 91 | public void setUrl(String url) { 92 | this.url = url; 93 | } 94 | 95 | public String getCategory() { 96 | return category; 97 | } 98 | 99 | public void setCategory(String category) { 100 | this.category = category; 101 | } 102 | 103 | public String getLanguage() { 104 | return language; 105 | } 106 | 107 | public void setLanguage(String language) { 108 | this.language = language; 109 | } 110 | 111 | public String getCountry() { 112 | return country; 113 | } 114 | 115 | public void setCountry(String country) { 116 | this.country = country; 117 | } 118 | 119 | @Override 120 | public int describeContents() { 121 | return 0; 122 | } 123 | 124 | @Override 125 | public void writeToParcel(Parcel dest, int flags) { 126 | dest.writeInt(this.uid); 127 | dest.writeString(this.id); 128 | dest.writeString(this.name); 129 | dest.writeString(this.description); 130 | dest.writeString(this.url); 131 | dest.writeString(this.category); 132 | dest.writeString(this.language); 133 | dest.writeString(this.country); 134 | dest.writeByte(this.isSelected ? (byte) 1 : (byte) 0); 135 | } 136 | 137 | protected Source(Parcel in) { 138 | this.uid = in.readInt(); 139 | this.id = in.readString(); 140 | this.name = in.readString(); 141 | this.description = in.readString(); 142 | this.url = in.readString(); 143 | this.category = in.readString(); 144 | this.language = in.readString(); 145 | this.country = in.readString(); 146 | this.isSelected = in.readByte() != 0; 147 | } 148 | 149 | public static final Creator CREATOR = new Creator() { 150 | @Override 151 | public Source createFromParcel(Parcel source) { 152 | return new Source(source); 153 | } 154 | 155 | @Override 156 | public Source[] newArray(int size) { 157 | return new Source[size]; 158 | } 159 | }; 160 | } 161 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/arc/model/data/Sources.java: -------------------------------------------------------------------------------- 1 | package com.example.arc.model.data; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * @author ihsan on 12/3/17. 7 | */ 8 | 9 | @SuppressWarnings("ALL") 10 | public class Sources { 11 | private List sources; 12 | 13 | public List getSources() { 14 | return sources; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/arc/model/db/AppDatabase.java: -------------------------------------------------------------------------------- 1 | package com.example.arc.model.db; 2 | 3 | import android.arch.persistence.room.Database; 4 | import android.arch.persistence.room.RoomDatabase; 5 | 6 | import com.example.arc.model.data.Article; 7 | import com.example.arc.model.data.Source; 8 | 9 | /** 10 | * @author ihsan on 12/19/17. 11 | */ 12 | 13 | @Database(entities = {Source.class, Article.class}, version = 1) 14 | public abstract class AppDatabase extends RoomDatabase { 15 | public abstract SourceDao sourceDao(); 16 | 17 | public abstract ArticleDao articleDao(); 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/arc/model/db/ArticleDao.java: -------------------------------------------------------------------------------- 1 | package com.example.arc.model.db; 2 | 3 | import android.arch.lifecycle.LiveData; 4 | import android.arch.persistence.room.Dao; 5 | import android.arch.persistence.room.Insert; 6 | import android.arch.persistence.room.OnConflictStrategy; 7 | import android.arch.persistence.room.Query; 8 | 9 | import com.example.arc.model.data.Article; 10 | 11 | import java.util.List; 12 | 13 | /** 14 | * @author ihsan on 12/19/17. 15 | */ 16 | @Dao 17 | public interface ArticleDao { 18 | 19 | @Query("SELECT * FROM article") 20 | List
getAll(); 21 | 22 | @Insert(onConflict = OnConflictStrategy.REPLACE) 23 | void insert(List
articles); 24 | 25 | @Query("DELETE FROM article") 26 | void clear(); 27 | 28 | @Query("SELECT * FROM article WHERE id == :id") 29 | LiveData
get(int id); 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/arc/model/db/DataRepository.java: -------------------------------------------------------------------------------- 1 | package com.example.arc.model.db; 2 | 3 | import android.arch.lifecycle.MutableLiveData; 4 | 5 | import com.example.arc.core.AppSchedulerProvider; 6 | import com.example.arc.core.BaseViewModel; 7 | import com.example.arc.core.base.ArticleUtils; 8 | import com.example.arc.model.api.Api; 9 | import com.example.arc.model.data.Article; 10 | import com.example.arc.model.data.Articles; 11 | import com.example.arc.model.data.Source; 12 | import com.example.arc.model.data.Sources; 13 | 14 | import java.util.List; 15 | 16 | import javax.inject.Inject; 17 | 18 | import io.reactivex.Observer; 19 | import io.reactivex.disposables.CompositeDisposable; 20 | import io.reactivex.disposables.Disposable; 21 | 22 | /** 23 | * @author ihsan on 12/28/17. 24 | */ 25 | 26 | public class DataRepository implements BaseViewModel { 27 | 28 | private final List sourceList; 29 | private CompositeDisposable disposables = new CompositeDisposable(); 30 | private final Api api; 31 | private final MutableLiveData> articleMutableLiveData; 32 | private final MutableLiveData> sourceMutableLiveData; 33 | private final AppSchedulerProvider schedulerProvider; 34 | private final SourceDao sourceDao; 35 | private final ArticleDao articleDao; 36 | 37 | @Inject 38 | DataRepository(AppDatabase database, Api api, AppSchedulerProvider schedulerProvider) { 39 | this.api = api; 40 | this.schedulerProvider = schedulerProvider; 41 | sourceDao = database.sourceDao(); 42 | articleDao = database.articleDao(); 43 | sourceList = sourceDao.getAllList(); 44 | articleMutableLiveData = new MutableLiveData<>(); 45 | sourceMutableLiveData = new MutableLiveData<>(); 46 | sourceMutableLiveData.postValue(sourceList); 47 | articleMutableLiveData.postValue(articleDao.getAll()); 48 | } 49 | 50 | public void insertSource(Source item) { 51 | sourceDao.insert(item); 52 | } 53 | 54 | public void deleteSource(String id) { 55 | sourceDao.delete(id); 56 | } 57 | 58 | public MutableLiveData> getSourceLiveList() { 59 | api.sources() 60 | .observeOn(schedulerProvider.ui()) 61 | .subscribeOn(schedulerProvider.io()) 62 | .map(sources -> { 63 | if (sourceList != null) { 64 | for (Source item : sources.getSources()) { 65 | for (Source data : sourceList) { 66 | if (item.getId().equals(data.getId())) { 67 | item.setSelected(true); 68 | } 69 | } 70 | } 71 | } 72 | return sources; 73 | }) 74 | .subscribe(new Observer() { 75 | @Override 76 | public void onSubscribe(Disposable d) { 77 | disposables.add(d); 78 | } 79 | 80 | @Override 81 | public void onNext(Sources sources) { 82 | sourceMutableLiveData.postValue(sources.getSources()); 83 | } 84 | 85 | @Override 86 | public void onError(Throwable e) { 87 | 88 | } 89 | 90 | @Override 91 | public void onComplete() { 92 | 93 | } 94 | }); 95 | return sourceMutableLiveData; 96 | } 97 | 98 | public MutableLiveData> getArticleLiveList() { 99 | api.topHeadlines(getQuery()) 100 | .observeOn(schedulerProvider.ui()) 101 | .subscribeOn(schedulerProvider.io()) 102 | .map(ArticleUtils::formatDate) 103 | .subscribe(new Observer() { 104 | @Override 105 | public void onSubscribe(Disposable d) { 106 | disposables.add(d); 107 | } 108 | 109 | @Override 110 | public void onNext(Articles articles) { 111 | articleDao.clear(); 112 | articleDao.insert(articles.getArticles()); 113 | articleMutableLiveData.postValue(articleDao.getAll()); 114 | } 115 | 116 | @Override 117 | public void onError(Throwable e) { 118 | 119 | } 120 | 121 | @Override 122 | public void onComplete() { 123 | 124 | } 125 | }); 126 | return articleMutableLiveData; 127 | } 128 | 129 | private String getQuery() { 130 | StringBuilder builder = new StringBuilder(); 131 | if (sourceList != null && sourceList.size() > 0) { 132 | for (Source item : sourceList) { 133 | builder.append(item.getId()).append(","); 134 | } 135 | builder.deleteCharAt(builder.lastIndexOf(",")); 136 | } 137 | return builder.toString(); 138 | } 139 | 140 | @Override 141 | public void onClear() { 142 | disposables.clear(); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/arc/model/db/SourceDao.java: -------------------------------------------------------------------------------- 1 | package com.example.arc.model.db; 2 | 3 | import android.arch.persistence.room.Dao; 4 | import android.arch.persistence.room.Insert; 5 | import android.arch.persistence.room.OnConflictStrategy; 6 | import android.arch.persistence.room.Query; 7 | 8 | import com.example.arc.model.data.Source; 9 | 10 | import java.util.List; 11 | 12 | /** 13 | * @author ihsan on 12/19/17. 14 | */ 15 | @Dao 16 | public interface SourceDao { 17 | 18 | @Query("SELECT * FROM source") 19 | List getAllList(); 20 | 21 | @Insert(onConflict = OnConflictStrategy.REPLACE) 22 | void insert(Source... sources); 23 | 24 | @Query("DELETE FROM source WHERE news_id == :news_id") 25 | void delete(String news_id); 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/arc/view/adapter/NewsAdapter.java: -------------------------------------------------------------------------------- 1 | package com.example.arc.view.adapter; 2 | 3 | import android.databinding.DataBindingUtil; 4 | import android.databinding.ViewDataBinding; 5 | import android.support.v7.widget.RecyclerView; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | 10 | import com.example.arc.BR; 11 | import com.example.arc.R; 12 | import com.example.arc.model.data.Article; 13 | 14 | import java.util.ArrayList; 15 | 16 | /** 17 | * @author ihsan on 12/19/17. 18 | */ 19 | 20 | public class NewsAdapter extends RecyclerView.Adapter { 21 | 22 | private ArrayList
data; 23 | private ItemSelectedListener listener; 24 | 25 | public NewsAdapter() { 26 | data = new ArrayList<>(); 27 | } 28 | 29 | public void setData(ArrayList
data) { 30 | this.data.clear(); 31 | this.data.addAll(data); 32 | notifyDataSetChanged(); 33 | } 34 | 35 | public ArrayList
getData() { 36 | return data; 37 | } 38 | 39 | @Override 40 | public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 41 | ViewDataBinding binding = DataBindingUtil.inflate( 42 | LayoutInflater.from(parent.getContext()), R.layout.item_news_view, parent, false); 43 | return new ViewHolder(binding, listener); 44 | } 45 | 46 | @Override 47 | public void onBindViewHolder(ViewHolder holder, int position) { 48 | holder.bind(data.get(position)); 49 | } 50 | 51 | @Override 52 | public int getItemCount() { 53 | return data.size(); 54 | } 55 | 56 | public interface ItemSelectedListener { 57 | void onItemSelected(View view, Article item); 58 | } 59 | 60 | public void setOnItemClickListener(ItemSelectedListener listener) { 61 | this.listener = listener; 62 | } 63 | 64 | class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { 65 | 66 | private final ViewDataBinding binding; 67 | private final ItemSelectedListener listener; 68 | 69 | ViewHolder(ViewDataBinding binding, ItemSelectedListener listener) { 70 | super(binding.getRoot()); 71 | this.binding = binding; 72 | this.listener = listener; 73 | binding.getRoot().setOnClickListener(this); 74 | } 75 | 76 | void bind(Article data) { 77 | binding.setVariable(BR.article, data); 78 | binding.executePendingBindings(); 79 | } 80 | 81 | @Override 82 | public void onClick(View view) { 83 | if (listener != null) { 84 | listener.onItemSelected(view, data.get(getAdapterPosition())); 85 | } 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/arc/view/adapter/SourcesAdapter.java: -------------------------------------------------------------------------------- 1 | package com.example.arc.view.adapter; 2 | 3 | import android.databinding.DataBindingUtil; 4 | import android.databinding.ViewDataBinding; 5 | import android.support.v7.widget.RecyclerView; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | 10 | import com.example.arc.BR; 11 | import com.example.arc.R; 12 | import com.example.arc.model.data.Source; 13 | 14 | import java.util.ArrayList; 15 | 16 | /** 17 | * @author ihsan on 12/18/17. 18 | */ 19 | 20 | public class SourcesAdapter extends RecyclerView.Adapter { 21 | 22 | private ArrayList data; 23 | private ItemSelectedListener listener; 24 | 25 | public SourcesAdapter() { 26 | this.data = new ArrayList<>(); 27 | } 28 | 29 | public void setData(ArrayList data) { 30 | this.data.clear(); 31 | this.data.addAll(data); 32 | notifyDataSetChanged(); 33 | } 34 | 35 | @Override 36 | public SourcesAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 37 | ViewDataBinding binding = DataBindingUtil.inflate( 38 | LayoutInflater.from(parent.getContext()), R.layout.item_source_view, parent, false); 39 | return new ViewHolder(binding, listener); 40 | } 41 | 42 | @Override 43 | public void onBindViewHolder(SourcesAdapter.ViewHolder holder, int position) { 44 | holder.bind(data.get(position)); 45 | } 46 | 47 | @Override 48 | public int getItemCount() { 49 | return data.size(); 50 | } 51 | 52 | public interface ItemSelectedListener { 53 | void onItemSelected(Source item); 54 | } 55 | 56 | public void setItemSelectedListener(ItemSelectedListener listener) { 57 | this.listener = listener; 58 | } 59 | 60 | class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { 61 | private final ItemSelectedListener listener; 62 | private final ViewDataBinding binding; 63 | 64 | ViewHolder(ViewDataBinding binding, ItemSelectedListener listener) { 65 | super(binding.getRoot()); 66 | binding.getRoot().findViewById(R.id.button).setOnClickListener(this); 67 | this.binding = binding; 68 | this.listener = listener; 69 | } 70 | 71 | void bind(Source source) { 72 | binding.setVariable(BR.source, source); 73 | binding.executePendingBindings(); 74 | } 75 | 76 | @Override 77 | public void onClick(View view) { 78 | if (listener != null) { 79 | Source item = data.get(getAdapterPosition()); 80 | item.setSelected(!item.isSelected()); 81 | notifyItemChanged(getAdapterPosition()); 82 | listener.onItemSelected(item); 83 | } 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/arc/view/ui/DetailActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.arc.view.ui; 2 | 3 | import android.arch.lifecycle.LiveData; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.os.Bundle; 7 | import android.support.v4.app.ActivityCompat; 8 | import android.support.v4.app.ActivityOptionsCompat; 9 | import android.view.MenuItem; 10 | 11 | import com.example.arc.R; 12 | import com.example.arc.databinding.ActivityDetailBinding; 13 | import com.example.arc.model.data.Article; 14 | import com.example.arc.core.base.BaseActivity; 15 | import com.example.arc.viewmodel.DetailViewModel; 16 | 17 | /** 18 | * @author ihsan on 12/28/17. 19 | */ 20 | 21 | public class DetailActivity extends BaseActivity { 22 | 23 | private static final String KEY_ITEM_ID = "item:article"; 24 | 25 | @Override 26 | protected Class getViewModel() { 27 | return DetailViewModel.class; 28 | } 29 | 30 | @Override 31 | protected int getLayoutResId() { 32 | return R.layout.activity_detail; 33 | } 34 | 35 | @Override 36 | protected void onCreate(Bundle instance, DetailViewModel viewModel, ActivityDetailBinding binding) { 37 | setSupportActionBar(binding.toolbar); 38 | if (getSupportActionBar() != null) { 39 | getSupportActionBar().setDisplayHomeAsUpEnabled(true); 40 | } 41 | LiveData
article = viewModel.getArticle(getIntent().getIntExtra(KEY_ITEM_ID, -1)); 42 | if (article != null) { 43 | article.observe(this, (Article item) -> { 44 | binding.setArticle(item); 45 | if (item != null) { 46 | binding.webView.loadData(item.getDescription(), "text/html", "utf-8"); 47 | } 48 | }); 49 | } 50 | } 51 | 52 | @Override 53 | public boolean onOptionsItemSelected(MenuItem item) { 54 | switch (item.getItemId()) { 55 | case android.R.id.home: 56 | ActivityCompat.finishAfterTransition(this); 57 | break; 58 | } 59 | return super.onOptionsItemSelected(item); 60 | } 61 | 62 | public static void start(Context context, int id, ActivityOptionsCompat options) { 63 | Intent starter = new Intent(context, DetailActivity.class); 64 | starter.putExtra(KEY_ITEM_ID, id); 65 | context.startActivity(starter, options.toBundle()); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/arc/view/ui/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.arc.view.ui; 2 | 3 | import android.os.Bundle; 4 | import android.support.v4.app.ActivityOptionsCompat; 5 | import android.support.v7.widget.OrientationHelper; 6 | import android.support.v7.widget.StaggeredGridLayoutManager; 7 | import android.support.v7.widget.Toolbar; 8 | import android.view.MenuItem; 9 | import android.view.View; 10 | 11 | import com.example.arc.R; 12 | import com.example.arc.core.base.BaseActivity; 13 | import com.example.arc.databinding.ActivityMainBinding; 14 | import com.example.arc.model.data.Article; 15 | import com.example.arc.view.adapter.NewsAdapter; 16 | import com.example.arc.viewmodel.MainViewModel; 17 | 18 | import java.util.ArrayList; 19 | 20 | /** 21 | * @author ihsan on 12/19/17. 22 | */ 23 | 24 | public class MainActivity extends BaseActivity implements Toolbar.OnMenuItemClickListener, NewsAdapter.ItemSelectedListener { 25 | 26 | private NewsAdapter mAdapter; 27 | 28 | @Override 29 | protected Class getViewModel() { 30 | return MainViewModel.class; 31 | } 32 | 33 | @Override 34 | protected int getLayoutResId() { 35 | return R.layout.activity_main; 36 | } 37 | 38 | @Override 39 | protected void onCreate(Bundle instance, MainViewModel viewModel, ActivityMainBinding binding) { 40 | mAdapter = new NewsAdapter(); 41 | mAdapter.setOnItemClickListener(this); 42 | binding.recyclerView.setLayoutManager(new StaggeredGridLayoutManager(2, OrientationHelper.VERTICAL)); 43 | binding.recyclerView.setAdapter(mAdapter); 44 | binding.toolbar.setTitle(R.string.str_feed); 45 | binding.toolbar.inflateMenu(R.menu.main_menu); 46 | binding.toolbar.setOnMenuItemClickListener(this); 47 | init(viewModel); 48 | } 49 | 50 | private void init(MainViewModel viewModel) { 51 | viewModel.getArticleList().observe(this, articles -> mAdapter.setData((ArrayList
) articles)); 52 | viewModel.getSourceList().observe(this, sources -> { 53 | if (sources == null || sources.size() < 1) { 54 | SourcesActivity.start(this); 55 | } 56 | }); 57 | } 58 | 59 | @Override 60 | public boolean onMenuItemClick(MenuItem item) { 61 | SourcesActivity.start(this); 62 | return false; 63 | } 64 | 65 | @Override 66 | public void onItemSelected(View view, Article item) { 67 | View viewAnimation = view.findViewById(R.id.imageView); 68 | ActivityOptionsCompat options = ActivityOptionsCompat. 69 | makeSceneTransitionAnimation(this, viewAnimation, getString(R.string.trans_shared_image)); 70 | DetailActivity.start(this, item.getId(), options); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/arc/view/ui/SourcesActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.arc.view.ui; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import android.support.annotation.NonNull; 7 | import android.support.design.widget.Snackbar; 8 | import android.support.v7.widget.LinearLayoutManager; 9 | import android.view.View; 10 | 11 | import com.example.arc.R; 12 | import com.example.arc.core.base.BaseActivity; 13 | import com.example.arc.databinding.ActivitySourcesBinding; 14 | import com.example.arc.model.data.Source; 15 | import com.example.arc.view.adapter.SourcesAdapter; 16 | import com.example.arc.viewmodel.SourceViewModel; 17 | 18 | import java.util.ArrayList; 19 | 20 | /** 21 | * @author ihsan on 12/3/17. 22 | */ 23 | 24 | public class SourcesActivity extends BaseActivity implements SourcesAdapter.ItemSelectedListener { 25 | 26 | private SourcesAdapter mAdapter; 27 | private int count; 28 | private Snackbar snackBar; 29 | private SourceViewModel viewModel; 30 | 31 | @Override 32 | protected Class getViewModel() { 33 | return SourceViewModel.class; 34 | } 35 | 36 | @Override 37 | protected int getLayoutResId() { 38 | return R.layout.activity_sources; 39 | } 40 | 41 | public static void start(Context context) { 42 | Intent starter = new Intent(context, SourcesActivity.class); 43 | context.startActivity(starter); 44 | } 45 | 46 | @Override 47 | protected void onCreate(@NonNull Bundle instance, SourceViewModel viewModel, ActivitySourcesBinding binding) { 48 | mAdapter = new SourcesAdapter(); 49 | mAdapter.setItemSelectedListener(this); 50 | binding.recyclerView.setLayoutManager(new LinearLayoutManager(this)); 51 | binding.recyclerView.setHasFixedSize(true); 52 | binding.recyclerView.setAdapter(mAdapter); 53 | initSnackBar(binding.coordinatorLayout); 54 | this.viewModel = viewModel; 55 | viewModel.getSourceList().observe(this, sources1 -> mAdapter.setData((ArrayList) sources1)); 56 | } 57 | 58 | private void initSnackBar(View root) { 59 | snackBar = Snackbar.make(root, "Let\'s check your feed", Snackbar.LENGTH_INDEFINITE).setAction("Start", 60 | view -> finish()); 61 | } 62 | 63 | @Override 64 | public void onItemSelected(Source item) { 65 | if (item.isSelected()) { 66 | viewModel.insert(item); 67 | count++; 68 | } else { 69 | viewModel.delete(item.getId()); 70 | count--; 71 | } 72 | if (count >= 1) { 73 | snackBar.show(); 74 | } else if (count < 1 && snackBar != null && snackBar.isShown()) { 75 | snackBar.dismiss(); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/arc/viewmodel/DetailViewModel.java: -------------------------------------------------------------------------------- 1 | package com.example.arc.viewmodel; 2 | 3 | import android.arch.lifecycle.LiveData; 4 | import android.arch.lifecycle.ViewModel; 5 | 6 | import com.example.arc.model.data.Article; 7 | import com.example.arc.model.db.AppDatabase; 8 | import com.example.arc.model.db.ArticleDao; 9 | 10 | import javax.inject.Inject; 11 | 12 | /** 13 | * @author ihsan on 12/28/17. 14 | */ 15 | 16 | public class DetailViewModel extends ViewModel { 17 | 18 | private final ArticleDao articleDao; 19 | 20 | @Inject 21 | DetailViewModel(AppDatabase database) { 22 | this.articleDao = database.articleDao(); 23 | } 24 | 25 | public LiveData
getArticle(int id) { 26 | return articleDao.get(id); 27 | } 28 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/arc/viewmodel/MainViewModel.java: -------------------------------------------------------------------------------- 1 | package com.example.arc.viewmodel; 2 | 3 | import android.arch.lifecycle.LiveData; 4 | import android.arch.lifecycle.ViewModel; 5 | 6 | import com.example.arc.model.data.Article; 7 | import com.example.arc.model.data.Source; 8 | import com.example.arc.model.db.DataRepository; 9 | 10 | import java.util.List; 11 | 12 | import javax.inject.Inject; 13 | 14 | /** 15 | * @author ihsan on 12/27/17. 16 | */ 17 | 18 | public class MainViewModel extends ViewModel { 19 | 20 | private final LiveData> articleList; 21 | private final LiveData> sourceList; 22 | private final DataRepository repository; 23 | 24 | @Inject 25 | MainViewModel(DataRepository repository) { 26 | this.repository = repository; 27 | articleList = repository.getArticleLiveList(); 28 | sourceList = repository.getSourceLiveList(); 29 | } 30 | 31 | public LiveData> getArticleList() { 32 | return articleList; 33 | } 34 | 35 | public LiveData> getSourceList() { 36 | return sourceList; 37 | } 38 | 39 | @Override 40 | protected void onCleared() { 41 | super.onCleared(); 42 | repository.onClear(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/arc/viewmodel/SourceViewModel.java: -------------------------------------------------------------------------------- 1 | package com.example.arc.viewmodel; 2 | 3 | import android.arch.lifecycle.LiveData; 4 | import android.arch.lifecycle.ViewModel; 5 | 6 | import com.example.arc.model.data.Source; 7 | import com.example.arc.model.db.DataRepository; 8 | 9 | import java.util.List; 10 | 11 | import javax.inject.Inject; 12 | 13 | /** 14 | * @author ihsan on 12/10/17. 15 | */ 16 | 17 | public class SourceViewModel extends ViewModel { 18 | 19 | private final DataRepository repository; 20 | private final LiveData> sourceList; 21 | 22 | @Inject 23 | SourceViewModel(DataRepository repository) { 24 | this.repository = repository; 25 | sourceList = repository.getSourceLiveList(); 26 | } 27 | 28 | public LiveData> getSourceList() { 29 | return sourceList; 30 | } 31 | 32 | public void insert(Source item) { 33 | repository.insertSource(item); 34 | } 35 | 36 | public void delete(String id) { 37 | repository.deleteSource(id); 38 | } 39 | 40 | @Override 41 | protected void onCleared() { 42 | super.onCleared(); 43 | repository.onClear(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/arc/viewmodel/ViewModelFactory.java: -------------------------------------------------------------------------------- 1 | package com.example.arc.viewmodel; 2 | 3 | /* 4 | * Copyright (C) 2017 The Android Open Source Project 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | 20 | import android.arch.lifecycle.ViewModel; 21 | import android.arch.lifecycle.ViewModelProvider; 22 | 23 | import java.util.Map; 24 | 25 | import javax.inject.Inject; 26 | import javax.inject.Provider; 27 | import javax.inject.Singleton; 28 | 29 | @Singleton 30 | public class ViewModelFactory implements ViewModelProvider.Factory { 31 | 32 | private final Map, Provider> creators; 33 | 34 | @Inject 35 | public ViewModelFactory(Map, Provider> creators) { 36 | this.creators = creators; 37 | } 38 | 39 | @Override 40 | public T create(Class modelClass) { 41 | Provider creator = creators.get(modelClass); 42 | if (creator == null) { 43 | for (Map.Entry, Provider> entry : creators.entrySet()) { 44 | if (modelClass.isAssignableFrom(entry.getKey())) { 45 | creator = entry.getValue(); 46 | break; 47 | } 48 | } 49 | } 50 | if (creator == null) { 51 | throw new IllegalArgumentException("unknown model class " + modelClass); 52 | } 53 | try { 54 | return (T) creator.get(); 55 | } catch (Exception e) { 56 | throw new RuntimeException(e); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /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/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/square_border.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/font/.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/font/allerta_stencil.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_detail.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 11 | 12 | 13 | 17 | 18 | 22 | 23 | 33 | 34 | 47 | 48 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 10 | 11 | 20 | 21 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_sources.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 9 | 10 | 14 | 15 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_news_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 11 | 12 | 13 | 17 | 18 | 21 | 22 | 34 | 35 | 54 | 55 | 73 | 74 | 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_source_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 11 | 12 | 13 | 17 | 18 | 35 | 36 |