From 4b2d2f0f9cc7ca409ac49eb08308315c61b480cc Mon Sep 17 00:00:00 2001 From: KurekWioletta Date: Tue, 16 Apr 2019 21:56:32 +0200 Subject: [PATCH 01/18] upload github client app to forked repository --- GithubClient/.gitignore | 183 +++++++ GithubClient/.idea/assetWizardSettings.xml | 32 ++ .../.idea/caches/build_file_checksums.ser | Bin 0 -> 537 bytes GithubClient/.idea/caches/gradle_models.ser | Bin 0 -> 178886 bytes GithubClient/.idea/codeStyles/Project.xml | 77 +++ GithubClient/.idea/dbnavigator.xml | 456 ++++++++++++++++++ GithubClient/.idea/runConfigurations.xml | 12 + GithubClient/app/.gitignore | 1 + GithubClient/app/build.gradle | 52 ++ GithubClient/app/proguard-rules.pro | 21 + .../githubclient/ExampleInstrumentedTest.java | 26 + GithubClient/app/src/main/AndroidManifest.xml | 34 ++ .../githubclient/GithubClientApp.java | 28 ++ .../data/AppGithubApiManager.java | 49 ++ .../githubclient/data/GithubApiManager.java | 18 + .../githubclient/data/model/Repository.java | 120 +++++ .../githubclient/di/ApplicationContext.java | 11 + .../githubclient/di/PerActivity.java | 11 + .../di/components/ActivityComponent.java | 31 ++ .../di/components/AppComponent.java | 31 ++ .../githubclient/di/modules/AppModule.java | 34 ++ .../di/modules/GithubApiModule.java | 40 ++ .../modules/activity/BaseActivityModule.java | 32 ++ .../modules/activity/MainActivityModule.java | 20 + .../RepositoriesListActivityModule.java | 36 ++ .../RepositoryDetailsActivityModule.java | 20 + .../githubclient/ui/base/BaseActivity.java | 49 ++ .../githubclient/ui/base/BasePresenter.java | 44 ++ .../githubclient/ui/base/MvpPresenter.java | 9 + .../githubclient/ui/base/MvpView.java | 9 + .../githubclient/ui/main/MainActivity.java | 65 +++ .../githubclient/ui/main/MainContract.java | 20 + .../githubclient/ui/main/MainPresenter.java | 59 +++ .../RepositoriesListActivity.java | 90 ++++ .../RepositoriesListContract.java | 23 + .../RepositoriesListPresenter.java | 45 ++ .../adapter/RepositoriesListAdapter.java | 59 +++ .../RepositoriesListViewHolder.java | 32 ++ .../RepositoryDetailsActivity.java | 98 ++++ .../RepositoryDetailsContract.java | 17 + .../RepositoryDetailsPresenter.java | 22 + .../githubclient/utils/AppConstants.java | 15 + .../githubclient/utils/Validator.java | 22 + .../utils/rx/AppSchedulerProvider.java | 19 + .../utils/rx/DisposableObserverWrapper.java | 16 + .../utils/rx/SchedulerProvider.java | 11 + .../drawable-v24/ic_launcher_foreground.xml | 34 ++ .../res/drawable/ic_launcher_background.xml | 170 +++++++ .../main/res/drawable/ic_main_github_mark.xml | 9 + .../drawable/ic_repository_details_eye.xml | 12 + .../drawable/ic_repository_details_fork.xml | 21 + .../drawable/ic_repository_details_star.xml | 9 + .../main/res/drawable/ic_repository_star.xml | 9 + .../app/src/main/res/layout/activity_main.xml | 70 +++ .../res/layout/activity_repositories_list.xml | 32 ++ .../layout/activity_repository_details.xml | 124 +++++ .../src/main/res/layout/dialog_progress.xml | 18 + .../src/main/res/layout/item_repository.xml | 40 ++ .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 + .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 2963 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 4905 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2060 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 2783 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 4490 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 6895 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 6387 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 10413 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 9128 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 15132 bytes .../app/src/main/res/values/colors.xml | 10 + .../app/src/main/res/values/dimens.xml | 11 + .../app/src/main/res/values/strings.xml | 31 ++ .../app/src/main/res/values/styles.xml | 17 + .../utils/ui/main/MainPresenterTest.java | 127 +++++ .../utils/ui/main/response/User.java | 5 + .../RepositoryDetailsPresenterTest.java | 48 ++ .../RepositoriesListPresenterTest.java | 83 ++++ .../utils/utils/TestConstans.java | 10 + .../utils/utils/ValidatorTest.java | 42 ++ GithubClient/build.gradle | 24 + GithubClient/gradle.properties | 15 + .../gradle/wrapper/gradle-wrapper.properties | 6 + GithubClient/gradlew | 172 +++++++ GithubClient/gradlew.bat | 84 ++++ GithubClient/settings.gradle | 1 + 86 files changed, 3343 insertions(+) create mode 100644 GithubClient/.gitignore create mode 100644 GithubClient/.idea/assetWizardSettings.xml create mode 100644 GithubClient/.idea/caches/build_file_checksums.ser create mode 100644 GithubClient/.idea/caches/gradle_models.ser create mode 100644 GithubClient/.idea/codeStyles/Project.xml create mode 100644 GithubClient/.idea/dbnavigator.xml create mode 100644 GithubClient/.idea/runConfigurations.xml create mode 100644 GithubClient/app/.gitignore create mode 100644 GithubClient/app/build.gradle create mode 100644 GithubClient/app/proguard-rules.pro create mode 100644 GithubClient/app/src/androidTest/java/com/kurekwioletta/githubclient/ExampleInstrumentedTest.java create mode 100644 GithubClient/app/src/main/AndroidManifest.xml create mode 100644 GithubClient/app/src/main/java/com/kurekwioletta/githubclient/GithubClientApp.java create mode 100644 GithubClient/app/src/main/java/com/kurekwioletta/githubclient/data/AppGithubApiManager.java create mode 100644 GithubClient/app/src/main/java/com/kurekwioletta/githubclient/data/GithubApiManager.java create mode 100644 GithubClient/app/src/main/java/com/kurekwioletta/githubclient/data/model/Repository.java create mode 100644 GithubClient/app/src/main/java/com/kurekwioletta/githubclient/di/ApplicationContext.java create mode 100644 GithubClient/app/src/main/java/com/kurekwioletta/githubclient/di/PerActivity.java create mode 100644 GithubClient/app/src/main/java/com/kurekwioletta/githubclient/di/components/ActivityComponent.java create mode 100644 GithubClient/app/src/main/java/com/kurekwioletta/githubclient/di/components/AppComponent.java create mode 100644 GithubClient/app/src/main/java/com/kurekwioletta/githubclient/di/modules/AppModule.java create mode 100644 GithubClient/app/src/main/java/com/kurekwioletta/githubclient/di/modules/GithubApiModule.java create mode 100644 GithubClient/app/src/main/java/com/kurekwioletta/githubclient/di/modules/activity/BaseActivityModule.java create mode 100644 GithubClient/app/src/main/java/com/kurekwioletta/githubclient/di/modules/activity/MainActivityModule.java create mode 100644 GithubClient/app/src/main/java/com/kurekwioletta/githubclient/di/modules/activity/RepositoriesListActivityModule.java create mode 100644 GithubClient/app/src/main/java/com/kurekwioletta/githubclient/di/modules/activity/RepositoryDetailsActivityModule.java create mode 100644 GithubClient/app/src/main/java/com/kurekwioletta/githubclient/ui/base/BaseActivity.java create mode 100644 GithubClient/app/src/main/java/com/kurekwioletta/githubclient/ui/base/BasePresenter.java create mode 100644 GithubClient/app/src/main/java/com/kurekwioletta/githubclient/ui/base/MvpPresenter.java create mode 100644 GithubClient/app/src/main/java/com/kurekwioletta/githubclient/ui/base/MvpView.java create mode 100644 GithubClient/app/src/main/java/com/kurekwioletta/githubclient/ui/main/MainActivity.java create mode 100644 GithubClient/app/src/main/java/com/kurekwioletta/githubclient/ui/main/MainContract.java create mode 100644 GithubClient/app/src/main/java/com/kurekwioletta/githubclient/ui/main/MainPresenter.java create mode 100644 GithubClient/app/src/main/java/com/kurekwioletta/githubclient/ui/repositories_list/RepositoriesListActivity.java create mode 100644 GithubClient/app/src/main/java/com/kurekwioletta/githubclient/ui/repositories_list/RepositoriesListContract.java create mode 100644 GithubClient/app/src/main/java/com/kurekwioletta/githubclient/ui/repositories_list/RepositoriesListPresenter.java create mode 100644 GithubClient/app/src/main/java/com/kurekwioletta/githubclient/ui/repositories_list/adapter/RepositoriesListAdapter.java create mode 100644 GithubClient/app/src/main/java/com/kurekwioletta/githubclient/ui/repositories_list/adapter/viewholder/RepositoriesListViewHolder.java create mode 100644 GithubClient/app/src/main/java/com/kurekwioletta/githubclient/ui/repository_details/RepositoryDetailsActivity.java create mode 100644 GithubClient/app/src/main/java/com/kurekwioletta/githubclient/ui/repository_details/RepositoryDetailsContract.java create mode 100644 GithubClient/app/src/main/java/com/kurekwioletta/githubclient/ui/repository_details/RepositoryDetailsPresenter.java create mode 100644 GithubClient/app/src/main/java/com/kurekwioletta/githubclient/utils/AppConstants.java create mode 100644 GithubClient/app/src/main/java/com/kurekwioletta/githubclient/utils/Validator.java create mode 100644 GithubClient/app/src/main/java/com/kurekwioletta/githubclient/utils/rx/AppSchedulerProvider.java create mode 100644 GithubClient/app/src/main/java/com/kurekwioletta/githubclient/utils/rx/DisposableObserverWrapper.java create mode 100644 GithubClient/app/src/main/java/com/kurekwioletta/githubclient/utils/rx/SchedulerProvider.java create mode 100644 GithubClient/app/src/main/res/drawable-v24/ic_launcher_foreground.xml create mode 100644 GithubClient/app/src/main/res/drawable/ic_launcher_background.xml create mode 100644 GithubClient/app/src/main/res/drawable/ic_main_github_mark.xml create mode 100644 GithubClient/app/src/main/res/drawable/ic_repository_details_eye.xml create mode 100644 GithubClient/app/src/main/res/drawable/ic_repository_details_fork.xml create mode 100644 GithubClient/app/src/main/res/drawable/ic_repository_details_star.xml create mode 100644 GithubClient/app/src/main/res/drawable/ic_repository_star.xml create mode 100644 GithubClient/app/src/main/res/layout/activity_main.xml create mode 100644 GithubClient/app/src/main/res/layout/activity_repositories_list.xml create mode 100644 GithubClient/app/src/main/res/layout/activity_repository_details.xml create mode 100644 GithubClient/app/src/main/res/layout/dialog_progress.xml create mode 100644 GithubClient/app/src/main/res/layout/item_repository.xml create mode 100644 GithubClient/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 GithubClient/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 GithubClient/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 GithubClient/app/src/main/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 GithubClient/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 GithubClient/app/src/main/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 GithubClient/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 GithubClient/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 GithubClient/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 GithubClient/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 GithubClient/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 GithubClient/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 GithubClient/app/src/main/res/values/colors.xml create mode 100644 GithubClient/app/src/main/res/values/dimens.xml create mode 100644 GithubClient/app/src/main/res/values/strings.xml create mode 100644 GithubClient/app/src/main/res/values/styles.xml create mode 100644 GithubClient/app/src/test/java/com/kurekwioletta/githubclient/utils/ui/main/MainPresenterTest.java create mode 100644 GithubClient/app/src/test/java/com/kurekwioletta/githubclient/utils/ui/main/response/User.java create mode 100644 GithubClient/app/src/test/java/com/kurekwioletta/githubclient/utils/ui/repositories_details/RepositoryDetailsPresenterTest.java create mode 100644 GithubClient/app/src/test/java/com/kurekwioletta/githubclient/utils/ui/repositories_list/RepositoriesListPresenterTest.java create mode 100644 GithubClient/app/src/test/java/com/kurekwioletta/githubclient/utils/utils/TestConstans.java create mode 100644 GithubClient/app/src/test/java/com/kurekwioletta/githubclient/utils/utils/ValidatorTest.java create mode 100644 GithubClient/build.gradle create mode 100644 GithubClient/gradle.properties create mode 100644 GithubClient/gradle/wrapper/gradle-wrapper.properties create mode 100644 GithubClient/gradlew create mode 100644 GithubClient/gradlew.bat create mode 100644 GithubClient/settings.gradle diff --git a/GithubClient/.gitignore b/GithubClient/.gitignore new file mode 100644 index 0000000..7468296 --- /dev/null +++ b/GithubClient/.gitignore @@ -0,0 +1,183 @@ + +# Created by https://www.gitignore.io/api/java,android,androidstudio + +### Android ### +# Built application files +*.apk +*.ap_ + +# Files for the ART/Dalvik VM +*.dex + +# Java class files +*.class + +# Generated files +bin/ +gen/ +out/ + +# Gradle files +.gradle/ +build/ + +# Local configuration file (sdk path, etc) +local.properties + +# Proguard folder generated by Eclipse +proguard/ + +# Log Files +*.log + +# Android Studio Navigation editor temp files +.navigation/ + +# Android Studio captures folder +captures/ + +# Intellij +*.iml +.idea/workspace.xml +.idea/tasks.xml +.idea/gradle.xml +.idea/dictionaries +.idea/libraries + +# External native build folder generated in Android Studio 2.2 and later +.externalNativeBuild + +# Freeline +freeline.py +freeline/ +freeline_project_description.json + +### Android Patch ### +gen-external-apklibs + +### AndroidStudio ### +# Covers files to be ignored for android development using Android Studio. + +# Built application files + +# Files for the ART/Dalvik VM + +# Java class files + +# Generated files + +# Gradle files +.gradle + +# Signing files +.signing/ + +# Local configuration file (sdk path, etc) + +# Proguard folder generated by Eclipse + +# Log Files + +# Android Studio +/*/build/ +/*/local.properties +/*/out +/*/*/build +/*/*/production +*.ipr +*~ +*.swp + +# Android Patch + +# External native build folder generated in Android Studio 2.2 and later + +# NDK +obj/ + +# IntelliJ IDEA +*.iws +/out/ + +# User-specific configurations +.idea/libraries/ +.idea/.name +.idea/compiler.xml +.idea/copyright/profiles_settings.xml +.idea/encodings.xml +.idea/misc.xml +.idea/modules.xml +.idea/scopes/scope_settings.xml +.idea/vcs.xml +.idea/jsLibraryMappings.xml +.idea/datasources.xml +.idea/dataSources.ids +.idea/sqlDataSources.xml +.idea/dynamic.xml +.idea/uiDesigner.xml + +# OS-specific files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Legacy Eclipse project files +.classpath +.project +.cproject +.settings/ + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.war +*.ear + +# virtual machine crash logs (Reference: http://www.java.com/en/download/help/error_hotspot.xml) +hs_err_pid* + +## Plugin-specific files: + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Mongo Explorer plugin +.idea/mongoSettings.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +### AndroidStudio Patch ### + +!/gradle/wrapper/gradle-wrapper.jar + +### Java ### +# Compiled class file + +# Log file + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) + +# Package Files # +*.jar +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml + +# End of https://www.gitignore.io/api/java,android,androidstudio \ No newline at end of file diff --git a/GithubClient/.idea/assetWizardSettings.xml b/GithubClient/.idea/assetWizardSettings.xml new file mode 100644 index 0000000..768e086 --- /dev/null +++ b/GithubClient/.idea/assetWizardSettings.xml @@ -0,0 +1,32 @@ + + + + + + \ No newline at end of file diff --git a/GithubClient/.idea/caches/build_file_checksums.ser b/GithubClient/.idea/caches/build_file_checksums.ser new file mode 100644 index 0000000000000000000000000000000000000000..2d3080d822ad6447723a031dc895a492efda433c GIT binary patch literal 537 zcmZ4UmVvdnh`~NNKUXg?FQq6yGexf?KR>5fFEb@IQ7^qHF(oHeub?PDD>b=9F91S2 zm1gFoxMk*~I%lLNXBU^|7Q2L-Ts|(GuF1r} zK_Ic9ASMaqbf~vVix`-qoz{F7|G~x)@_>nf0c16Ufd3@+K&`yJyQgZOtQY_4UI27r zacW6PW?p(ROk*KK9RnLk?Yx@_`)uyB=jkTsNrgKdURc7wgJJ_*;j&1>39DA|#q3h! zPMtTAr5Wg)oc!d(98k~}q!yKArWTjN6*oV5ue`W()didVKmUflTV7Sdpy6y46Iz^F zR2&nYnV*vw19eCYB7QIoxgy(hv!7uT%gYAN8E<|#)s-*^;!&MC+uQ46?a7em(TXNp J3;I@90040syRZNN literal 0 HcmV?d00001 diff --git a/GithubClient/.idea/caches/gradle_models.ser b/GithubClient/.idea/caches/gradle_models.ser new file mode 100644 index 0000000000000000000000000000000000000000..31633c28d10642f424d11739f4b6869ba767d3fb GIT binary patch literal 178886 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 Q**s+>#o#y6cT30r0shNAwg3PC literal 0 HcmV?d00001 diff --git a/GithubClient/.idea/codeStyles/Project.xml b/GithubClient/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..d5ec3da --- /dev/null +++ b/GithubClient/.idea/codeStyles/Project.xml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/GithubClient/.idea/dbnavigator.xml b/GithubClient/.idea/dbnavigator.xml new file mode 100644 index 0000000..4bea32f --- /dev/null +++ b/GithubClient/.idea/dbnavigator.xml @@ -0,0 +1,456 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/GithubClient/.idea/runConfigurations.xml b/GithubClient/.idea/runConfigurations.xml new file mode 100644 index 0000000..7f68460 --- /dev/null +++ b/GithubClient/.idea/runConfigurations.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/GithubClient/app/.gitignore b/GithubClient/app/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/GithubClient/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/GithubClient/app/build.gradle b/GithubClient/app/build.gradle new file mode 100644 index 0000000..44d5de4 --- /dev/null +++ b/GithubClient/app/build.gradle @@ -0,0 +1,52 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 28 + defaultConfig { + applicationId "com.kurekwioletta.githubclient" + minSdkVersion 24 + targetSdkVersion 28 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +dependencies { + implementation fileTree(include: ['*.jar'], dir: 'libs') + implementation 'org.apache.commons:commons-lang3:3.6' + + implementation 'com.android.support:appcompat-v7:28.0.0' + implementation 'com.android.support.constraint:constraint-layout:1.1.3' + implementation 'com.android.support:design:28.0.0' + + implementation 'com.squareup.retrofit2:retrofit:2.5.0' + implementation 'com.squareup.retrofit2:converter-gson:2.2.0' + implementation 'com.squareup.retrofit2:adapter-rxjava2:2.3.0' + + implementation 'io.reactivex.rxjava2:rxandroid:2.1.0' + implementation 'io.reactivex.rxjava2:rxjava:2.2.0' + + implementation 'com.jakewharton:butterknife:8.8.1' + annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1' + + implementation 'com.google.dagger:dagger-android:2.22' + annotationProcessor 'com.google.dagger:dagger-android-processor:2.22' + annotationProcessor 'com.google.dagger:dagger-compiler:2.22' + testAnnotationProcessor 'com.google.dagger:dagger-compiler:2.22' + + testImplementation 'junit:junit:4.12' + testImplementation "org.robolectric:robolectric:3.1.2" + testImplementation "org.mockito:mockito-core:2.26.0" + testImplementation 'org.mockito:mockito-inline:2.21.0' +} diff --git a/GithubClient/app/proguard-rules.pro b/GithubClient/app/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/GithubClient/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/GithubClient/app/src/androidTest/java/com/kurekwioletta/githubclient/ExampleInstrumentedTest.java b/GithubClient/app/src/androidTest/java/com/kurekwioletta/githubclient/ExampleInstrumentedTest.java new file mode 100644 index 0000000..5b09236 --- /dev/null +++ b/GithubClient/app/src/androidTest/java/com/kurekwioletta/githubclient/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package com.kurekwioletta.githubclient; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getTargetContext(); + + assertEquals("com.kurekwioletta.githubclient", appContext.getPackageName()); + } +} diff --git a/GithubClient/app/src/main/AndroidManifest.xml b/GithubClient/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..89de4ae --- /dev/null +++ b/GithubClient/app/src/main/AndroidManifest.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/GithubClientApp.java b/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/GithubClientApp.java new file mode 100644 index 0000000..8bf63a4 --- /dev/null +++ b/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/GithubClientApp.java @@ -0,0 +1,28 @@ +package com.kurekwioletta.githubclient; + +import android.app.Application; + +import com.kurekwioletta.githubclient.di.components.AppComponent; +import com.kurekwioletta.githubclient.di.components.DaggerAppComponent; +import com.kurekwioletta.githubclient.di.modules.AppModule; + +public class GithubClientApp extends Application { + protected AppComponent mAppComponent; + + @Override + public void onCreate() { + super.onCreate(); + + mAppComponent = DaggerAppComponent + .builder() + .appModule(new AppModule(this)) + .build(); + + mAppComponent.inject(this); + } + + public AppComponent getComponent() { + return mAppComponent; + } + +} diff --git a/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/data/AppGithubApiManager.java b/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/data/AppGithubApiManager.java new file mode 100644 index 0000000..1678536 --- /dev/null +++ b/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/data/AppGithubApiManager.java @@ -0,0 +1,49 @@ +package com.kurekwioletta.githubclient.data; + +import com.kurekwioletta.githubclient.data.model.Repository; +import com.kurekwioletta.githubclient.utils.AppConstants; +import com.kurekwioletta.githubclient.utils.rx.SchedulerProvider; + +import java.util.List; + +import javax.inject.Inject; + +import io.reactivex.Observable; +import okhttp3.OkHttpClient; +import retrofit2.Response; +import retrofit2.Retrofit; +import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; +import retrofit2.converter.gson.GsonConverterFactory; + +public class AppGithubApiManager implements GithubApiManager { + + private final GithubApiManager mGithubApiHelper; + private final SchedulerProvider mSchedulerProvider; + + @Inject + public AppGithubApiManager(OkHttpClient okHttpClient, SchedulerProvider schedulerProvider) { + Retrofit retrofit = new Retrofit.Builder() + .client(okHttpClient) + .baseUrl(AppConstants.API_ENDPOINT) + .addConverterFactory(GsonConverterFactory.create()) + .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) + .build(); + + mGithubApiHelper = retrofit.create(GithubApiManager.class); + mSchedulerProvider = schedulerProvider; + } + + @Override + public Observable> getUserResponse(String username) { + return mGithubApiHelper.getUserResponse(username) + .subscribeOn(mSchedulerProvider.io()) + .observeOn(mSchedulerProvider.ui()); + } + + @Override + public Observable> getUsersRepositoriesList(String username) { + return mGithubApiHelper.getUsersRepositoriesList(username) + .subscribeOn(mSchedulerProvider.io()) + .observeOn(mSchedulerProvider.ui()); + } +} diff --git a/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/data/GithubApiManager.java b/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/data/GithubApiManager.java new file mode 100644 index 0000000..5ae0e51 --- /dev/null +++ b/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/data/GithubApiManager.java @@ -0,0 +1,18 @@ +package com.kurekwioletta.githubclient.data; + +import com.kurekwioletta.githubclient.data.model.Repository; + +import java.util.List; + +import io.reactivex.Observable; +import retrofit2.Response; +import retrofit2.http.GET; +import retrofit2.http.Path; + +public interface GithubApiManager { + @GET("users/{username}") + Observable> getUserResponse(@Path("username") String username); + + @GET("users/{username}/repos") + Observable> getUsersRepositoriesList(@Path("username") String username); +} \ No newline at end of file diff --git a/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/data/model/Repository.java b/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/data/model/Repository.java new file mode 100644 index 0000000..a0e1e66 --- /dev/null +++ b/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/data/model/Repository.java @@ -0,0 +1,120 @@ +package com.kurekwioletta.githubclient.data.model; + +import android.os.Parcel; +import android.os.Parcelable; + +import com.google.gson.annotations.SerializedName; + +public class Repository implements Parcelable { + + @SerializedName("id") + private int id; + + @SerializedName("name") + private String name; + + @SerializedName("html_url") + private String htmlUrl; + + @SerializedName("description") + private String description; + + @SerializedName("created_at") + private String createdAt; + + @SerializedName("updated_at") + private String updatedAt; + + @SerializedName("pushed_at") + private String pushedAt; + + @SerializedName("stargazers_count") + private int stargazersCount; + + @SerializedName("watchers_count") + private int watchersCount; + + @SerializedName("forks_count") + private int forksCount; + + private Repository(Parcel in) { + id = in.readInt(); + name = in.readString(); + htmlUrl = in.readString(); + description = in.readString(); + createdAt = in.readString(); + updatedAt = in.readString(); + pushedAt = in.readString(); + stargazersCount = in.readInt(); + watchersCount = in.readInt(); + forksCount = in.readInt(); + } + + public static final Creator CREATOR = new Creator() { + @Override + public Repository createFromParcel(Parcel in) { + return new Repository(in); + } + + @Override + public Repository[] newArray(int size) { + return new Repository[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(id); + dest.writeString(name); + dest.writeString(htmlUrl); + dest.writeString(description); + dest.writeString(createdAt); + dest.writeString(updatedAt); + dest.writeString(pushedAt); + dest.writeInt(stargazersCount); + dest.writeInt(watchersCount); + dest.writeInt(forksCount); + } + + public String getName() { + return name; + } + + public String getHtmlUrl() { + return htmlUrl; + } + + public String getDescription() { + return description; + } + + public String getCreatedAt() { + return createdAt; + } + + public String getUpdatedAt() { + return updatedAt; + } + + public String getPushedAt() { + return pushedAt; + } + + public int getStargazersCount() { + return stargazersCount; + } + + public int getWatchersCount() { + return watchersCount; + } + + public int getForksCount() { + return forksCount; + } + +} \ No newline at end of file diff --git a/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/di/ApplicationContext.java b/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/di/ApplicationContext.java new file mode 100644 index 0000000..978cb57 --- /dev/null +++ b/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/di/ApplicationContext.java @@ -0,0 +1,11 @@ +package com.kurekwioletta.githubclient.di; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.inject.Qualifier; + +@Qualifier +@Retention(RetentionPolicy.RUNTIME) +public @interface ApplicationContext { +} diff --git a/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/di/PerActivity.java b/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/di/PerActivity.java new file mode 100644 index 0000000..0074991 --- /dev/null +++ b/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/di/PerActivity.java @@ -0,0 +1,11 @@ +package com.kurekwioletta.githubclient.di; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.inject.Scope; + +@Scope +@Retention(RetentionPolicy.RUNTIME) +public @interface PerActivity { +} \ No newline at end of file diff --git a/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/di/components/ActivityComponent.java b/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/di/components/ActivityComponent.java new file mode 100644 index 0000000..c76b641 --- /dev/null +++ b/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/di/components/ActivityComponent.java @@ -0,0 +1,31 @@ +package com.kurekwioletta.githubclient.di.components; + +import com.kurekwioletta.githubclient.di.PerActivity; +import com.kurekwioletta.githubclient.di.modules.activity.BaseActivityModule; +import com.kurekwioletta.githubclient.di.modules.activity.MainActivityModule; +import com.kurekwioletta.githubclient.di.modules.activity.RepositoriesListActivityModule; +import com.kurekwioletta.githubclient.di.modules.activity.RepositoryDetailsActivityModule; +import com.kurekwioletta.githubclient.ui.main.MainActivity; +import com.kurekwioletta.githubclient.ui.repositories_list.RepositoriesListActivity; +import com.kurekwioletta.githubclient.ui.repository_details.RepositoryDetailsActivity; + +import dagger.Component; + +@PerActivity +@Component( + dependencies = AppComponent.class, + modules = { + BaseActivityModule.class, + MainActivityModule.class, + RepositoriesListActivityModule.class, + RepositoryDetailsActivityModule.class + }) +public interface ActivityComponent { + + void inject(MainActivity activity); + + void inject(RepositoryDetailsActivity activity); + + void inject(RepositoriesListActivity activity); + +} \ No newline at end of file diff --git a/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/di/components/AppComponent.java b/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/di/components/AppComponent.java new file mode 100644 index 0000000..f20ebc2 --- /dev/null +++ b/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/di/components/AppComponent.java @@ -0,0 +1,31 @@ +package com.kurekwioletta.githubclient.di.components; + +import android.content.Context; + +import com.kurekwioletta.githubclient.GithubClientApp; +import com.kurekwioletta.githubclient.data.GithubApiManager; +import com.kurekwioletta.githubclient.di.ApplicationContext; +import com.kurekwioletta.githubclient.di.modules.AppModule; +import com.kurekwioletta.githubclient.di.modules.GithubApiModule; + +import javax.inject.Singleton; + +import dagger.Component; +import dagger.android.AndroidInjector; + +@Singleton +@Component( + modules = { + AppModule.class, + GithubApiModule.class + }) +public interface AppComponent extends AndroidInjector { + + void inject(GithubClientApp githubClientApp); + + @ApplicationContext + Context context(); + + GithubApiManager getGithubApiManager(); + +} \ No newline at end of file diff --git a/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/di/modules/AppModule.java b/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/di/modules/AppModule.java new file mode 100644 index 0000000..8cce5df --- /dev/null +++ b/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/di/modules/AppModule.java @@ -0,0 +1,34 @@ +package com.kurekwioletta.githubclient.di.modules; + +import android.app.Application; +import android.content.Context; + +import com.kurekwioletta.githubclient.di.ApplicationContext; + +import javax.inject.Singleton; + +import dagger.Module; +import dagger.Provides; + +@Module +public class AppModule { + + private final Application mApplication; + + public AppModule(Application application) { + mApplication = application; + } + + @Provides + @ApplicationContext + Context provideContext() { + return mApplication; + } + + @Provides + @Singleton + Application provideApplication() { + return mApplication; + } + +} \ No newline at end of file diff --git a/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/di/modules/GithubApiModule.java b/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/di/modules/GithubApiModule.java new file mode 100644 index 0000000..4e3071e --- /dev/null +++ b/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/di/modules/GithubApiModule.java @@ -0,0 +1,40 @@ +package com.kurekwioletta.githubclient.di.modules; + +import com.kurekwioletta.githubclient.data.AppGithubApiManager; +import com.kurekwioletta.githubclient.data.GithubApiManager; +import com.kurekwioletta.githubclient.utils.rx.AppSchedulerProvider; +import com.kurekwioletta.githubclient.utils.rx.SchedulerProvider; + +import javax.inject.Singleton; + +import dagger.Module; +import dagger.Provides; +import okhttp3.OkHttpClient; +@Module +public class GithubApiModule { + + @Provides + @Singleton + AppGithubApiManager provideAppGithubApiManager(OkHttpClient okHttpClient, SchedulerProvider schedulerProvider) { + return new AppGithubApiManager(okHttpClient, schedulerProvider); + } + + @Provides + @Singleton + GithubApiManager provideGithubApiManager(AppGithubApiManager appGithubApiManager) { + return appGithubApiManager; + } + + @Provides + @Singleton + OkHttpClient provideOkHttpClient(){ + return new OkHttpClient.Builder().build(); + } + + @Provides + @Singleton + SchedulerProvider provideSchedulerProvider() { + return new AppSchedulerProvider(); + } + +} diff --git a/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/di/modules/activity/BaseActivityModule.java b/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/di/modules/activity/BaseActivityModule.java new file mode 100644 index 0000000..83fba97 --- /dev/null +++ b/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/di/modules/activity/BaseActivityModule.java @@ -0,0 +1,32 @@ +package com.kurekwioletta.githubclient.di.modules.activity; + +import android.support.v7.app.AppCompatActivity; + +import com.kurekwioletta.githubclient.di.PerActivity; + +import dagger.Module; +import dagger.Provides; +import io.reactivex.disposables.CompositeDisposable; + +@Module +public class BaseActivityModule { + + private AppCompatActivity mActivity; + + public BaseActivityModule(AppCompatActivity activity) { + this.mActivity = activity; + } + + @Provides + @PerActivity + AppCompatActivity provideActivity() { + return mActivity; + } + + @Provides + @PerActivity + CompositeDisposable provideCompositeDisposable() { + return new CompositeDisposable(); + } + +} diff --git a/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/di/modules/activity/MainActivityModule.java b/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/di/modules/activity/MainActivityModule.java new file mode 100644 index 0000000..342b823 --- /dev/null +++ b/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/di/modules/activity/MainActivityModule.java @@ -0,0 +1,20 @@ +package com.kurekwioletta.githubclient.di.modules.activity; + +import com.kurekwioletta.githubclient.di.PerActivity; +import com.kurekwioletta.githubclient.ui.main.MainContract; +import com.kurekwioletta.githubclient.ui.main.MainPresenter; + +import dagger.Module; +import dagger.Provides; + +@Module +public class MainActivityModule { + + @Provides + @PerActivity + MainContract.Presenter provideMainPresenter( + MainPresenter presenter) { + return presenter; + } + +} diff --git a/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/di/modules/activity/RepositoriesListActivityModule.java b/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/di/modules/activity/RepositoriesListActivityModule.java new file mode 100644 index 0000000..06fd043 --- /dev/null +++ b/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/di/modules/activity/RepositoriesListActivityModule.java @@ -0,0 +1,36 @@ +package com.kurekwioletta.githubclient.di.modules.activity; + +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.LinearLayoutManager; + +import com.kurekwioletta.githubclient.di.PerActivity; +import com.kurekwioletta.githubclient.ui.repositories_list.RepositoriesListContract; +import com.kurekwioletta.githubclient.ui.repositories_list.RepositoriesListPresenter; +import com.kurekwioletta.githubclient.ui.repositories_list.adapter.RepositoriesListAdapter; + +import dagger.Module; +import dagger.Provides; + +@Module +public class RepositoriesListActivityModule { + + @Provides + @PerActivity + RepositoriesListContract.Presenter provideRepositoriesPresenter( + RepositoriesListPresenter presenter) { + return presenter; + } + + @Provides + @PerActivity + RepositoriesListAdapter provideRepositoriesListAdapter(AppCompatActivity activity) { + return new RepositoriesListAdapter(activity); + } + + @Provides + @PerActivity + LinearLayoutManager provideLinearLayoutManager(AppCompatActivity activity) { + return new LinearLayoutManager(activity); + } + +} diff --git a/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/di/modules/activity/RepositoryDetailsActivityModule.java b/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/di/modules/activity/RepositoryDetailsActivityModule.java new file mode 100644 index 0000000..2a9773e --- /dev/null +++ b/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/di/modules/activity/RepositoryDetailsActivityModule.java @@ -0,0 +1,20 @@ +package com.kurekwioletta.githubclient.di.modules.activity; + +import com.kurekwioletta.githubclient.di.PerActivity; +import com.kurekwioletta.githubclient.ui.repository_details.RepositoryDetailsContract; +import com.kurekwioletta.githubclient.ui.repository_details.RepositoryDetailsPresenter; + +import dagger.Module; +import dagger.Provides; + +@Module +public class RepositoryDetailsActivityModule { + + @Provides + @PerActivity + RepositoryDetailsContract.Presenter provideRepositoriesPresenter( + RepositoryDetailsPresenter presenter) { + return presenter; + } + +} diff --git a/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/ui/base/BaseActivity.java b/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/ui/base/BaseActivity.java new file mode 100644 index 0000000..2c17eb6 --- /dev/null +++ b/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/ui/base/BaseActivity.java @@ -0,0 +1,49 @@ +package com.kurekwioletta.githubclient.ui.base; + +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; +import android.widget.Toast; + +import com.kurekwioletta.githubclient.GithubClientApp; +import com.kurekwioletta.githubclient.di.components.ActivityComponent; +import com.kurekwioletta.githubclient.di.components.DaggerActivityComponent; +import com.kurekwioletta.githubclient.di.modules.activity.BaseActivityModule; + +import butterknife.Unbinder; + +public class BaseActivity extends AppCompatActivity implements MvpView { + + private Unbinder mUnbinder; + private ActivityComponent mActivityComponent; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mActivityComponent = DaggerActivityComponent.builder() + .baseActivityModule(new BaseActivityModule(this)) + .appComponent(((GithubClientApp) getApplication()).getComponent()) + .build(); + } + + public ActivityComponent getActivityComponent() { + return mActivityComponent; + } + + @Override + protected void onDestroy() { + if (mUnbinder != null) { + mUnbinder.unbind(); + } + super.onDestroy(); + } + + @Override + public void showMessage(int resId) { + Toast.makeText(this, getResources().getString(resId), Toast.LENGTH_SHORT).show(); + } + + public void setUnBinder(Unbinder unBinder) { + mUnbinder = unBinder; + } +} diff --git a/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/ui/base/BasePresenter.java b/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/ui/base/BasePresenter.java new file mode 100644 index 0000000..af5aeb6 --- /dev/null +++ b/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/ui/base/BasePresenter.java @@ -0,0 +1,44 @@ +package com.kurekwioletta.githubclient.ui.base; + +import com.kurekwioletta.githubclient.data.GithubApiManager; + +import javax.inject.Inject; + +import io.reactivex.disposables.CompositeDisposable; + +public class BasePresenter implements MvpPresenter { + + private final GithubApiManager mGithubApiManager; + private final CompositeDisposable mCompositeDisposable; + + private V mMvpView; + + @Inject + public BasePresenter(GithubApiManager githubApiManager, CompositeDisposable compositeDisposable) { + mGithubApiManager = githubApiManager; + mCompositeDisposable = compositeDisposable; + } + + @Override + public void onAttach(V mvpView) { + mMvpView = mvpView; + } + + @Override + public void onDetach() { + mCompositeDisposable.dispose(); + mMvpView = null; + } + + public V getMvpView() { + return mMvpView; + } + + protected GithubApiManager getGithubApiManager() { + return mGithubApiManager; + } + + protected CompositeDisposable getCompositeDisposable() { + return mCompositeDisposable; + } +} \ No newline at end of file diff --git a/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/ui/base/MvpPresenter.java b/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/ui/base/MvpPresenter.java new file mode 100644 index 0000000..5f8d08c --- /dev/null +++ b/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/ui/base/MvpPresenter.java @@ -0,0 +1,9 @@ +package com.kurekwioletta.githubclient.ui.base; + +public interface MvpPresenter { + + void onAttach(V mvpView); + + void onDetach(); + +} \ No newline at end of file diff --git a/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/ui/base/MvpView.java b/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/ui/base/MvpView.java new file mode 100644 index 0000000..9014773 --- /dev/null +++ b/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/ui/base/MvpView.java @@ -0,0 +1,9 @@ +package com.kurekwioletta.githubclient.ui.base; + +import android.support.annotation.StringRes; + +public interface MvpView { + + void showMessage(@StringRes int resId); + +} diff --git a/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/ui/main/MainActivity.java b/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/ui/main/MainActivity.java new file mode 100644 index 0000000..e845357 --- /dev/null +++ b/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/ui/main/MainActivity.java @@ -0,0 +1,65 @@ +package com.kurekwioletta.githubclient.ui.main; + +import android.os.Bundle; +import android.view.View; +import android.widget.EditText; +import android.widget.ProgressBar; + +import com.kurekwioletta.githubclient.R; +import com.kurekwioletta.githubclient.ui.base.BaseActivity; +import com.kurekwioletta.githubclient.ui.repositories_list.RepositoriesListActivity; + +import javax.inject.Inject; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; + +public class MainActivity extends BaseActivity implements MainContract.View { + + @Inject + MainPresenter mPresenter; + + @BindView(R.id.et_main_username) + EditText etUsername; + + @BindView(R.id.pb_main_loading) + ProgressBar pbLoading; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + getActivityComponent().inject(this); + setUnBinder(ButterKnife.bind(this)); + + mPresenter.onAttach(this); + } + + @Override + public void openRepositoriesListActivity(String username) { + startActivity(RepositoriesListActivity.getStartIntent(this, username)); + } + + @Override + public void showLoading() { + pbLoading.setVisibility(View.VISIBLE); + } + + @Override + public void hideLoading() { + pbLoading.setVisibility(View.INVISIBLE); + } + + @Override + protected void onDestroy() { + mPresenter.onDetach(); + super.onDestroy(); + } + + @OnClick(R.id.btn_main_load_repositories) + void onLoadRepositoriesClick(View v) { + mPresenter.onLoadRepositoriesClick(etUsername.getText().toString()); + } +} diff --git a/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/ui/main/MainContract.java b/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/ui/main/MainContract.java new file mode 100644 index 0000000..eddb766 --- /dev/null +++ b/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/ui/main/MainContract.java @@ -0,0 +1,20 @@ +package com.kurekwioletta.githubclient.ui.main; + +import com.kurekwioletta.githubclient.di.PerActivity; +import com.kurekwioletta.githubclient.ui.base.MvpPresenter; +import com.kurekwioletta.githubclient.ui.base.MvpView; + +public interface MainContract { + interface View extends MvpView { + void openRepositoriesListActivity(String username); + + void showLoading(); + + void hideLoading(); + } + + @PerActivity + interface Presenter extends MvpPresenter { + void onLoadRepositoriesClick(String username); + } +} diff --git a/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/ui/main/MainPresenter.java b/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/ui/main/MainPresenter.java new file mode 100644 index 0000000..3d74741 --- /dev/null +++ b/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/ui/main/MainPresenter.java @@ -0,0 +1,59 @@ +package com.kurekwioletta.githubclient.ui.main; + +import com.kurekwioletta.githubclient.R; +import com.kurekwioletta.githubclient.data.GithubApiManager; +import com.kurekwioletta.githubclient.ui.base.BasePresenter; +import com.kurekwioletta.githubclient.utils.AppConstants; +import com.kurekwioletta.githubclient.utils.Validator; +import com.kurekwioletta.githubclient.utils.rx.DisposableObserverWrapper; + +import javax.inject.Inject; + +import io.reactivex.disposables.CompositeDisposable; +import retrofit2.Response; + +public class MainPresenter extends BasePresenter implements MainContract.Presenter{ + + @Inject + public MainPresenter(GithubApiManager githubApiManager, CompositeDisposable compositeDisposable) { + super(githubApiManager, compositeDisposable); + } + + @Override + public void onLoadRepositoriesClick(final String username) { + if (Validator.isUsernameValid(username)) { + getMvpView().showLoading(); + + // if user exists - open RepositoriesListActivity + getCompositeDisposable().add(getGithubApiManager() + .getUserResponse(username) + .subscribeWith(new DisposableObserverWrapper>() { + @Override + public void onNext(Response response) { + getMvpView().hideLoading(); + switch (response.code()) { + case AppConstants.STATUS_CODE_SUCCESS: + getMvpView().openRepositoriesListActivity(username); + break; + case AppConstants.STATUS_CODE_NOT_FOUND: + getMvpView().showMessage(R.string.ERR_NOT_FOUND); + break; + default: + getMvpView().showMessage(R.string.ERR_UNKNOWN); + break; + } + } + + @Override + public void onError(Throwable e) { + getMvpView().hideLoading(); + getMvpView().showMessage(R.string.ERR_NETWORK); + } + }) + ); + } + else { + getMvpView().showMessage(R.string.MSG_INVALID_USERNAME); + } + } +} diff --git a/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/ui/repositories_list/RepositoriesListActivity.java b/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/ui/repositories_list/RepositoriesListActivity.java new file mode 100644 index 0000000..30b0341 --- /dev/null +++ b/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/ui/repositories_list/RepositoriesListActivity.java @@ -0,0 +1,90 @@ +package com.kurekwioletta.githubclient.ui.repositories_list; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.support.constraint.ConstraintLayout; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.View; + +import com.kurekwioletta.githubclient.R; +import com.kurekwioletta.githubclient.data.model.Repository; +import com.kurekwioletta.githubclient.ui.base.BaseActivity; +import com.kurekwioletta.githubclient.ui.repositories_list.adapter.RepositoriesListAdapter; +import com.kurekwioletta.githubclient.ui.repository_details.RepositoryDetailsActivity; +import com.kurekwioletta.githubclient.utils.AppConstants; + +import java.util.List; + +import javax.inject.Inject; + +import butterknife.BindView; +import butterknife.ButterKnife; + +public class RepositoriesListActivity extends BaseActivity implements RepositoriesListContract.View { + + @Inject + RepositoriesListPresenter mPresenter; + + @Inject + RepositoriesListAdapter mAdapter; + + @Inject + LinearLayoutManager mLinearLayoutManager; + + @BindView(R.id.rv_repositories_list) + RecyclerView rvRepositoriesList; + + @BindView(R.id.cl_repositories_list_loading) + ConstraintLayout clLoading; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_repositories_list); + + getActivityComponent().inject(this); + setUnBinder(ButterKnife.bind(this)); + + mPresenter.onAttach(this); + setUpRepositoriesListRecyclerView(); + mPresenter.onViewInitialized(getIntent().getStringExtra(AppConstants.EXTRA_USERNAME)); + } + + @Override + public void updateRepositoriesListRecyclerView(List repositoriesList) { + mAdapter.updateRepositoriesList(repositoriesList); + } + + @Override + public void showLoading() { + clLoading.setVisibility(View.VISIBLE); + } + + @Override + public void hideLoading() { + clLoading.setVisibility(View.GONE); + } + + @Override + protected void onDestroy() { + mPresenter.onDetach(); + super.onDestroy(); + } + + public void onRepositoryClicked(Repository repository) { + startActivity(RepositoryDetailsActivity.getStartIntent(this, repository)); + } + + public static Intent getStartIntent(Context context, String username) { + Intent intent = new Intent(context, RepositoriesListActivity.class); + intent.putExtra(AppConstants.EXTRA_USERNAME, username); + return intent; + } + + private void setUpRepositoriesListRecyclerView() { + rvRepositoriesList.setLayoutManager(mLinearLayoutManager); + rvRepositoriesList.setAdapter(mAdapter); + } +} diff --git a/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/ui/repositories_list/RepositoriesListContract.java b/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/ui/repositories_list/RepositoriesListContract.java new file mode 100644 index 0000000..6785255 --- /dev/null +++ b/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/ui/repositories_list/RepositoriesListContract.java @@ -0,0 +1,23 @@ +package com.kurekwioletta.githubclient.ui.repositories_list; + +import com.kurekwioletta.githubclient.data.model.Repository; +import com.kurekwioletta.githubclient.di.PerActivity; +import com.kurekwioletta.githubclient.ui.base.MvpPresenter; +import com.kurekwioletta.githubclient.ui.base.MvpView; + +import java.util.List; + +public interface RepositoriesListContract { + interface View extends MvpView { + void updateRepositoriesListRecyclerView(List repositoriesList); + + void showLoading(); + + void hideLoading(); + } + + @PerActivity + interface Presenter extends MvpPresenter { + void onViewInitialized(String username); + } +} diff --git a/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/ui/repositories_list/RepositoriesListPresenter.java b/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/ui/repositories_list/RepositoriesListPresenter.java new file mode 100644 index 0000000..892eb70 --- /dev/null +++ b/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/ui/repositories_list/RepositoriesListPresenter.java @@ -0,0 +1,45 @@ +package com.kurekwioletta.githubclient.ui.repositories_list; + +import com.kurekwioletta.githubclient.R; +import com.kurekwioletta.githubclient.data.GithubApiManager; +import com.kurekwioletta.githubclient.data.model.Repository; +import com.kurekwioletta.githubclient.ui.base.BasePresenter; +import com.kurekwioletta.githubclient.utils.rx.DisposableObserverWrapper; + +import java.util.List; + +import javax.inject.Inject; + +import io.reactivex.disposables.CompositeDisposable; + +public class RepositoriesListPresenter extends BasePresenter implements RepositoriesListContract.Presenter { + + @Inject + public RepositoriesListPresenter(GithubApiManager githubApiManager, CompositeDisposable compositeDisposable) { + super(githubApiManager, compositeDisposable); + } + + @Override + public void onViewInitialized(String username) { + getMvpView().showLoading(); + + // download users repositories list + getCompositeDisposable().add(getGithubApiManager() + .getUsersRepositoriesList(username) + .subscribeWith(new DisposableObserverWrapper>() { + @Override + public void onNext(List repositoriesList) { + getMvpView().hideLoading(); + getMvpView().updateRepositoriesListRecyclerView(repositoriesList); + } + + @Override + public void onError(Throwable e) { + getMvpView().hideLoading(); + getMvpView().showMessage(R.string.ERR_NETWORK); + } + }) + ); + } + +} diff --git a/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/ui/repositories_list/adapter/RepositoriesListAdapter.java b/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/ui/repositories_list/adapter/RepositoriesListAdapter.java new file mode 100644 index 0000000..9b01e53 --- /dev/null +++ b/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/ui/repositories_list/adapter/RepositoriesListAdapter.java @@ -0,0 +1,59 @@ +package com.kurekwioletta.githubclient.ui.repositories_list.adapter; + +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import com.kurekwioletta.githubclient.R; +import com.kurekwioletta.githubclient.data.model.Repository; +import com.kurekwioletta.githubclient.ui.repositories_list.RepositoriesListActivity; +import com.kurekwioletta.githubclient.ui.repositories_list.adapter.viewholder.RepositoriesListViewHolder; + +import java.util.ArrayList; +import java.util.List; + +public class RepositoriesListAdapter extends RecyclerView.Adapter { + + private final List mRepositoriesList = new ArrayList<>(); + private Context mContext; + + public RepositoriesListAdapter(Context context) { + mContext = context; + } + + @NonNull + @Override + public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + final View view = LayoutInflater.from(parent.getContext()).inflate(viewType, parent, false); + return new RepositoriesListViewHolder(view); +} + + @Override + public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { + ((RepositoriesListViewHolder) holder).bindData(mRepositoriesList.get(position)); + + holder.itemView.setOnClickListener(v -> onItemClicked(holder.getLayoutPosition())); + } + + @Override + public int getItemCount() { + return mRepositoriesList.size(); + } + + @Override + public int getItemViewType(final int position) { + return R.layout.item_repository; + } + + public void updateRepositoriesList(List repositoriesList) { + mRepositoriesList.addAll(repositoriesList); + notifyDataSetChanged(); + } + + private void onItemClicked(int position) { + ((RepositoriesListActivity) mContext).onRepositoryClicked(mRepositoriesList.get(position)); + } +} diff --git a/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/ui/repositories_list/adapter/viewholder/RepositoriesListViewHolder.java b/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/ui/repositories_list/adapter/viewholder/RepositoriesListViewHolder.java new file mode 100644 index 0000000..a29bb24 --- /dev/null +++ b/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/ui/repositories_list/adapter/viewholder/RepositoriesListViewHolder.java @@ -0,0 +1,32 @@ +package com.kurekwioletta.githubclient.ui.repositories_list.adapter.viewholder; + +import android.support.annotation.NonNull; +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.widget.TextView; + +import com.kurekwioletta.githubclient.R; +import com.kurekwioletta.githubclient.data.model.Repository; + +import butterknife.BindView; +import butterknife.ButterKnife; + +public class RepositoriesListViewHolder extends RecyclerView.ViewHolder { + + @BindView(R.id.tv_repository_name) + TextView tvName; + + @BindView(R.id.tv_repository_stars) + TextView tvStars; + + public RepositoriesListViewHolder(@NonNull View parent) { + super(parent); + ButterKnife.bind(this, parent); + } + + public void bindData(final Repository repository) { + tvName.setText(repository.getName()); + tvStars.setText(String.valueOf(repository.getStargazersCount())); + } + +} diff --git a/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/ui/repository_details/RepositoryDetailsActivity.java b/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/ui/repository_details/RepositoryDetailsActivity.java new file mode 100644 index 0000000..9222644 --- /dev/null +++ b/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/ui/repository_details/RepositoryDetailsActivity.java @@ -0,0 +1,98 @@ +package com.kurekwioletta.githubclient.ui.repository_details; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.view.View; +import android.widget.TextView; + +import com.kurekwioletta.githubclient.R; +import com.kurekwioletta.githubclient.data.model.Repository; +import com.kurekwioletta.githubclient.ui.base.BaseActivity; +import com.kurekwioletta.githubclient.utils.AppConstants; + +import javax.inject.Inject; + +import butterknife.BindView; +import butterknife.ButterKnife; + +public class RepositoryDetailsActivity extends BaseActivity implements RepositoryDetailsContract.View { + + @Inject + RepositoryDetailsPresenter mPresenter; + + @BindView(R.id.tv_repository_details_name) + TextView tvName; + + @BindView(R.id.tv_repository_details_description) + TextView tvDescription; + + @BindView(R.id.tv_repository_details_watchers) + TextView tvWatchers; + + @BindView(R.id.tv_repository_details_stars) + TextView tvStars; + + @BindView(R.id.tv_repository_details_forks) + TextView tvForks; + + @BindView(R.id.tv_repository_details_created) + TextView tvCreated; + + @BindView(R.id.tv_repository_details_updated) + TextView tvUpdated; + + @BindView(R.id.tv_repository_details_pushed) + TextView tvPushed; + + @BindView(R.id.tv_repository_details_hyperlink) + TextView tvHyperlink; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_repository_details); + + getActivityComponent().inject(this); + setUnBinder(ButterKnife.bind(this)); + + mPresenter.onAttach(this); + mPresenter.onViewInitialized(getIntent().getParcelableExtra(AppConstants.EXTRA_REPOSITORY)); + } + + @Override + public void setUpRepositoryDetails(Repository repository) { + tvName.setText(repository.getName()); + if(repository.getDescription() == null) { + tvDescription.setVisibility(View.GONE); + } + else { + tvDescription.setText(repository.getDescription()); + } + tvWatchers.setText(String.valueOf(repository.getWatchersCount())); + tvStars.setText(String.valueOf(repository.getStargazersCount())); + tvForks.setText(String.valueOf(repository.getForksCount())); + tvCreated.setText(repository.getCreatedAt().substring(0,10)); // yyyy-mm-dd + tvUpdated.setText(repository.getUpdatedAt().substring(0,10)); + tvPushed.setText(repository.getPushedAt().substring(0,10)); + tvHyperlink.setText(repository.getHtmlUrl()); + } + + @Override + protected void onStop() { + mPresenter.onDetach(); + super.onStop(); + } + + @Override + protected void onDestroy() { + mPresenter.onDetach(); + super.onDestroy(); + } + + public static Intent getStartIntent(Context context, Repository repository) { + Intent intent = new Intent(context, RepositoryDetailsActivity.class); + intent.putExtra(AppConstants.EXTRA_REPOSITORY,repository); + return intent; + } +} diff --git a/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/ui/repository_details/RepositoryDetailsContract.java b/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/ui/repository_details/RepositoryDetailsContract.java new file mode 100644 index 0000000..9c8b137 --- /dev/null +++ b/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/ui/repository_details/RepositoryDetailsContract.java @@ -0,0 +1,17 @@ +package com.kurekwioletta.githubclient.ui.repository_details; + +import com.kurekwioletta.githubclient.data.model.Repository; +import com.kurekwioletta.githubclient.di.PerActivity; +import com.kurekwioletta.githubclient.ui.base.MvpPresenter; +import com.kurekwioletta.githubclient.ui.base.MvpView; + +public interface RepositoryDetailsContract { + interface View extends MvpView { + void setUpRepositoryDetails(Repository repository); + } + + @PerActivity + interface Presenter extends MvpPresenter { + void onViewInitialized(Repository repository); + } +} \ No newline at end of file diff --git a/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/ui/repository_details/RepositoryDetailsPresenter.java b/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/ui/repository_details/RepositoryDetailsPresenter.java new file mode 100644 index 0000000..676445b --- /dev/null +++ b/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/ui/repository_details/RepositoryDetailsPresenter.java @@ -0,0 +1,22 @@ +package com.kurekwioletta.githubclient.ui.repository_details; + +import com.kurekwioletta.githubclient.data.GithubApiManager; +import com.kurekwioletta.githubclient.data.model.Repository; +import com.kurekwioletta.githubclient.ui.base.BasePresenter; + +import javax.inject.Inject; + +import io.reactivex.disposables.CompositeDisposable; + +public class RepositoryDetailsPresenter extends BasePresenter implements RepositoryDetailsContract.Presenter { + + @Inject + public RepositoryDetailsPresenter(GithubApiManager githubApiManager, CompositeDisposable compositeDisposable) { + super(githubApiManager, compositeDisposable); + } + + @Override + public void onViewInitialized(Repository repository) { + getMvpView().setUpRepositoryDetails(repository); + } +} diff --git a/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/utils/AppConstants.java b/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/utils/AppConstants.java new file mode 100644 index 0000000..67c5e3a --- /dev/null +++ b/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/utils/AppConstants.java @@ -0,0 +1,15 @@ +package com.kurekwioletta.githubclient.utils; + +public class AppConstants { + + // github api constants + public static final int STATUS_CODE_SUCCESS = 200; + public static final int STATUS_CODE_NOT_FOUND = 404; + + public static final String API_ENDPOINT = "https://api.github.com/"; + + // intent extras + public static final String EXTRA_USERNAME = "com.kurekwioletta.githubclient.EXTRA_USERNAME"; + public static final String EXTRA_REPOSITORY = "com.kurekwioletta.githubclient.EXTRA_REPOSITORY"; + +} diff --git a/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/utils/Validator.java b/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/utils/Validator.java new file mode 100644 index 0000000..d400e47 --- /dev/null +++ b/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/utils/Validator.java @@ -0,0 +1,22 @@ +package com.kurekwioletta.githubclient.utils; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class Validator { + + /** + * Validate username with regular expression. + * + * @param username + * may only contain alphanumeric characters or hyphens, + * cannot have multiple consecutive hyphens, + * cannot begin or end with a hyphen, + * max 39 characters. + * */ + public static boolean isUsernameValid(String username) { + Pattern pattern = Pattern.compile("[a-zA-Z0-9](-?[a-zA-Z0-9]){0,38}"); + Matcher matcher = pattern.matcher(username); + return matcher.matches(); + } +} diff --git a/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/utils/rx/AppSchedulerProvider.java b/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/utils/rx/AppSchedulerProvider.java new file mode 100644 index 0000000..3b2506b --- /dev/null +++ b/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/utils/rx/AppSchedulerProvider.java @@ -0,0 +1,19 @@ +package com.kurekwioletta.githubclient.utils.rx; + +import io.reactivex.Scheduler; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.schedulers.Schedulers; + +public class AppSchedulerProvider implements SchedulerProvider { + + @Override + public Scheduler ui() { + return AndroidSchedulers.mainThread(); + } + + @Override + public Scheduler io() { + return Schedulers.io(); + } + +} diff --git a/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/utils/rx/DisposableObserverWrapper.java b/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/utils/rx/DisposableObserverWrapper.java new file mode 100644 index 0000000..447c01b --- /dev/null +++ b/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/utils/rx/DisposableObserverWrapper.java @@ -0,0 +1,16 @@ +package com.kurekwioletta.githubclient.utils.rx; + +import io.reactivex.observers.DisposableObserver; + +public abstract class DisposableObserverWrapper extends DisposableObserver { + + @Override + public void onComplete(){} + + @Override + public void onNext(T t){} + + @Override + public void onError(Throwable e){} + +} \ No newline at end of file diff --git a/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/utils/rx/SchedulerProvider.java b/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/utils/rx/SchedulerProvider.java new file mode 100644 index 0000000..6159b16 --- /dev/null +++ b/GithubClient/app/src/main/java/com/kurekwioletta/githubclient/utils/rx/SchedulerProvider.java @@ -0,0 +1,11 @@ +package com.kurekwioletta.githubclient.utils.rx; + +import io.reactivex.Scheduler; + +public interface SchedulerProvider { + + Scheduler ui(); + + Scheduler io(); + +} \ No newline at end of file diff --git a/GithubClient/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/GithubClient/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..1f6bb29 --- /dev/null +++ b/GithubClient/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/GithubClient/app/src/main/res/drawable/ic_launcher_background.xml b/GithubClient/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..0d025f9 --- /dev/null +++ b/GithubClient/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/GithubClient/app/src/main/res/drawable/ic_main_github_mark.xml b/GithubClient/app/src/main/res/drawable/ic_main_github_mark.xml new file mode 100644 index 0000000..7fd3c5c --- /dev/null +++ b/GithubClient/app/src/main/res/drawable/ic_main_github_mark.xml @@ -0,0 +1,9 @@ + + + diff --git a/GithubClient/app/src/main/res/drawable/ic_repository_details_eye.xml b/GithubClient/app/src/main/res/drawable/ic_repository_details_eye.xml new file mode 100644 index 0000000..af14472 --- /dev/null +++ b/GithubClient/app/src/main/res/drawable/ic_repository_details_eye.xml @@ -0,0 +1,12 @@ + + + + diff --git a/GithubClient/app/src/main/res/drawable/ic_repository_details_fork.xml b/GithubClient/app/src/main/res/drawable/ic_repository_details_fork.xml new file mode 100644 index 0000000..203de55 --- /dev/null +++ b/GithubClient/app/src/main/res/drawable/ic_repository_details_fork.xml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/GithubClient/app/src/main/res/drawable/ic_repository_details_star.xml b/GithubClient/app/src/main/res/drawable/ic_repository_details_star.xml new file mode 100644 index 0000000..f073b1b --- /dev/null +++ b/GithubClient/app/src/main/res/drawable/ic_repository_details_star.xml @@ -0,0 +1,9 @@ + + + diff --git a/GithubClient/app/src/main/res/drawable/ic_repository_star.xml b/GithubClient/app/src/main/res/drawable/ic_repository_star.xml new file mode 100644 index 0000000..2dea2e0 --- /dev/null +++ b/GithubClient/app/src/main/res/drawable/ic_repository_star.xml @@ -0,0 +1,9 @@ + + + diff --git a/GithubClient/app/src/main/res/layout/activity_main.xml b/GithubClient/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..5351e9d --- /dev/null +++ b/GithubClient/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + +