├── .gitignore
├── .idea
├── .gitignore
├── codeStyles
│ ├── Project.xml
│ └── codeStyleConfig.xml
├── compiler.xml
├── deploymentTargetDropDown.xml
├── gradle.xml
├── inspectionProfiles
│ └── Project_Default.xml
├── misc.xml
└── vcs.xml
├── LICENSE
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
├── release
│ ├── app-release.apk
│ └── output-metadata.json
└── src
│ ├── androidTest
│ └── java
│ │ └── dn
│ │ └── marjan
│ │ └── githubapp
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── dn
│ │ │ └── marjan
│ │ │ └── githubapp
│ │ │ ├── Application.kt
│ │ │ ├── MainActivity.kt
│ │ │ ├── base
│ │ │ ├── BaseActivity.kt
│ │ │ ├── BaseViewModel.kt
│ │ │ ├── CoroutineDispatcherProvider.kt
│ │ │ └── ViewModelFactory.kt
│ │ │ ├── data
│ │ │ ├── Error.kt
│ │ │ ├── Resource.kt
│ │ │ ├── Status.kt
│ │ │ ├── local
│ │ │ │ ├── LocalDataService.kt
│ │ │ │ └── LocalDataServiceImp.kt
│ │ │ └── remote
│ │ │ │ ├── RemoteDataService.kt
│ │ │ │ └── RemoteDataServiceImp.kt
│ │ │ ├── di
│ │ │ ├── AppComponent.kt
│ │ │ ├── keys
│ │ │ │ └── ViewModelKey.kt
│ │ │ └── modules
│ │ │ │ ├── ActivityModule.kt
│ │ │ │ ├── AppModule.kt
│ │ │ │ ├── DataModule.kt
│ │ │ │ ├── NetworkModule.kt
│ │ │ │ ├── RepositoryModule.kt
│ │ │ │ └── ViewModelModule.kt
│ │ │ ├── entity
│ │ │ ├── ReceivedEvents.kt
│ │ │ ├── Repository.kt
│ │ │ └── UserInfo.kt
│ │ │ ├── server
│ │ │ └── TaskService.kt
│ │ │ ├── ui
│ │ │ ├── dashboard
│ │ │ │ ├── BottomBarItems.kt
│ │ │ │ ├── DashboardActivity.kt
│ │ │ │ └── DashboardViewModel.kt
│ │ │ ├── home
│ │ │ │ ├── repo
│ │ │ │ │ ├── HomeRepository.kt
│ │ │ │ │ └── HomeRepositoryImp.kt
│ │ │ │ └── ui
│ │ │ │ │ ├── HomePage.kt
│ │ │ │ │ └── HomeViewModel.kt
│ │ │ ├── login
│ │ │ │ ├── repo
│ │ │ │ │ ├── LoginRepository.kt
│ │ │ │ │ └── LoginRepositoryImp.kt
│ │ │ │ └── ui
│ │ │ │ │ ├── LoginActivity.kt
│ │ │ │ │ └── LoginViewModel.kt
│ │ │ ├── profile
│ │ │ │ ├── repo
│ │ │ │ │ ├── ProfileRepository.kt
│ │ │ │ │ └── ProfileRepositoryImp.kt
│ │ │ │ └── ui
│ │ │ │ │ ├── ProfilePage.kt
│ │ │ │ │ └── ProfileViewModel.kt
│ │ │ ├── repository
│ │ │ │ ├── repo
│ │ │ │ │ ├── RepoRepository.kt
│ │ │ │ │ └── RepoRepositoryImp.kt
│ │ │ │ └── ui
│ │ │ │ │ ├── RepositoryPage.kt
│ │ │ │ │ └── RepositoryViewModel.kt
│ │ │ ├── splash
│ │ │ │ ├── repo
│ │ │ │ │ ├── SplashRepository.kt
│ │ │ │ │ └── SplashRepositoryImp.kt
│ │ │ │ └── ui
│ │ │ │ │ ├── SplashActivity.kt
│ │ │ │ │ └── SplashViewModel.kt
│ │ │ └── theme
│ │ │ │ ├── Color.kt
│ │ │ │ ├── Shape.kt
│ │ │ │ ├── Theme.kt
│ │ │ │ └── Type.kt
│ │ │ └── utils
│ │ │ ├── Config.kt
│ │ │ └── Utility.kt
│ └── res
│ │ ├── drawable-v24
│ │ └── ic_launcher_foreground.xml
│ │ ├── drawable
│ │ ├── circle_shape.xml
│ │ ├── fork.png
│ │ ├── ic_account.xml
│ │ ├── ic_eye.xml
│ │ ├── ic_folder.xml
│ │ ├── ic_github.png
│ │ ├── ic_home.xml
│ │ ├── ic_launcher_background.xml
│ │ ├── ic_link.xml
│ │ ├── ic_location.xml
│ │ ├── ic_people.xml
│ │ ├── ic_star.xml
│ │ └── ic_time.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── values-night
│ │ └── themes.xml
│ │ └── values
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ └── themes.xml
│ ├── sharedTest
│ └── java
│ │ └── dn.marjan.githubapp
│ │ └── ApplicationTest.kt
│ └── test
│ ├── java
│ └── dn
│ │ └── marjan
│ │ └── githubapp
│ │ ├── ExampleUnitTest.kt
│ │ ├── di
│ │ ├── component
│ │ │ └── TestAppComponent.kt
│ │ ├── dispatchers
│ │ │ └── TestCoroutineDispatcherProvider.kt
│ │ └── modules
│ │ │ ├── AppModuleTest.kt
│ │ │ ├── DataModuleTest.kt
│ │ │ └── RepositoryModuleTest.kt
│ │ └── ui
│ │ ├── home
│ │ ├── HomeRepositoryTest.kt
│ │ └── HomeViewModelTest.kt
│ │ ├── login
│ │ ├── LoginRepositoryTest.kt
│ │ └── LoginViewModelTest.kt
│ │ ├── repository
│ │ └── RepositoryViewModelTest.kt
│ │ └── splash
│ │ ├── SplashActivityTest.kt
│ │ ├── SplashRepositoryTest.kt
│ │ └── SplashViewModelTest.kt
│ └── resources
│ └── mockito-extensions
│ └── org.mockito.plugins.MockMaker
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | .DS_Store
11 | /build
12 | /captures
13 | .externalNativeBuild
14 | .cxx
15 | local.properties
16 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 |
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | xmlns:android
15 |
16 | ^$
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | xmlns:.*
26 |
27 | ^$
28 |
29 |
30 | BY_NAME
31 |
32 |
33 |
34 |
35 |
36 |
37 | .*:id
38 |
39 | http://schemas.android.com/apk/res/android
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | .*:name
49 |
50 | http://schemas.android.com/apk/res/android
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | name
60 |
61 | ^$
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 | style
71 |
72 | ^$
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 | .*
82 |
83 | ^$
84 |
85 |
86 | BY_NAME
87 |
88 |
89 |
90 |
91 |
92 |
93 | .*
94 |
95 | http://schemas.android.com/apk/res/android
96 |
97 |
98 | ANDROID_ATTRIBUTE_ORDER
99 |
100 |
101 |
102 |
103 |
104 |
105 | .*
106 |
107 | .*
108 |
109 |
110 | BY_NAME
111 |
112 |
113 |
114 |
115 |
116 |
117 |
--------------------------------------------------------------------------------
/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/deploymentTargetDropDown.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## GitHub application using
2 | - [GitHub REST API](https://docs.github.com/en/rest)
3 | - [Dagger](https://github.com/google/dagger)
4 | - MVVM architecture
5 | - [Mockk](https://mockk.io)
6 | - [Jetpack Compose](developer.android.com/jetpack/compose)
7 | - [Kotlin Coroutines](developer.android.com/kotlin/coroutines)
8 |
9 | ## Application pages
10 |
15 |
16 | ## Attention
17 | If you want to use it, put your [GitHub Access Token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token) in Config.kt file.
18 |
19 | ## License
20 |
21 | Copyright 2022 marjandn
22 |
23 | Licensed under the Apache License, Version 2.0 (the "License");
24 | you may not use this file except in compliance with the License.
25 | You may obtain a copy of the License at
26 |
27 | http://www.apache.org/licenses/LICENSE-2.0
28 |
29 | Unless required by applicable law or agreed to in writing, software
30 | distributed under the License is distributed on an "AS IS" BASIS,
31 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
32 | See the License for the specific language governing permissions and
33 | limitations under the License.
34 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.application'
3 | id 'kotlin-android'
4 | id 'kotlin-kapt'
5 | }
6 |
7 | android {
8 | compileSdk 31
9 |
10 | defaultConfig {
11 | applicationId "dn.marjan.githubapp"
12 | minSdk 21
13 | targetSdk 31
14 | versionCode 1
15 | versionName "1.0"
16 |
17 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
18 | vectorDrawables {
19 | useSupportLibrary true
20 | }
21 | }
22 | buildTypes {
23 | release {
24 | minifyEnabled false
25 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
26 | }
27 | }
28 | compileOptions {
29 | sourceCompatibility JavaVersion.VERSION_11
30 | targetCompatibility JavaVersion.VERSION_11
31 | }
32 | kotlinOptions {
33 | jvmTarget = '11'
34 | useIR = true
35 | freeCompilerArgs += [
36 | "-Xjvm-default=all",
37 | ]
38 | }
39 | buildFeatures {
40 | compose true
41 | }
42 | composeOptions {
43 | kotlinCompilerExtensionVersion composeVersion
44 | // kotlinCompilerVersion kotlinVersion
45 | }
46 | packagingOptions {
47 | resources {
48 | excludes += '/META-INF/{AL2.0,LGPL2.1}'
49 | }
50 | }
51 | sourceSets {
52 | String sharedTestDir = 'src/sharedTest/java'
53 | test {
54 | java.srcDir sharedTestDir
55 | }
56 | androidTest {
57 | java.srcDir sharedTestDir
58 | }
59 | }
60 | testOptions{
61 | unitTests{
62 | includeAndroidResources true
63 | }
64 | }
65 | }
66 |
67 |
68 | dependencies {
69 | implementation 'androidx.core:core-ktx:1.7.0'
70 | implementation 'androidx.appcompat:appcompat:1.4.1'
71 | implementation 'com.google.android.material:material:1.6.0'
72 | implementation "androidx.compose.ui:ui:$composeVersion"
73 | implementation "androidx.compose.material:material:$composeVersion"
74 | implementation "androidx.compose.ui:ui-tooling-preview:$composeVersion"
75 | implementation "androidx.compose.runtime:runtime-livedata:$composeVersion"
76 |
77 | implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion"
78 | implementation 'androidx.activity:activity-compose:1.4.0'
79 | implementation "androidx.navigation:navigation-compose:2.5.0-rc01"
80 |
81 | // Unit Test
82 | // Required -- JUnit 4 framework
83 | testImplementation "junit:junit:$jUnitVersion"
84 | // Robolectric environment
85 | testImplementation "androidx.test:core:$androidXTestVersion"
86 | testImplementation 'org.robolectric:robolectric:4.8'
87 | // Mockk framework
88 | testImplementation "io.mockk:mockk:$mockkVersion"
89 | // For runBlockingTest, CoroutineDispatcher etc.
90 | testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.1"
91 | testImplementation 'org.json:json:20211205'
92 | testImplementation 'androidx.arch.core:core-testing:2.1.0'
93 |
94 | implementation "androidx.preference:preference-ktx:1.2.0"
95 |
96 | // ViewModel
97 | implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion"
98 | implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion"
99 | implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycleVersion"
100 | implementation "android.arch.lifecycle:extensions:1.1.1"
101 |
102 | // Retrofit
103 | implementation 'com.google.code.gson:gson:2.8.9'
104 | implementation 'com.squareup.retrofit2:retrofit:2.9.0'
105 | implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
106 |
107 | // Dagger
108 | implementation "com.google.dagger:dagger:$daggerVersion"
109 | implementation "com.google.dagger:dagger-android:$daggerVersion"
110 | implementation "com.google.dagger:dagger-android-support:$daggerVersion"
111 | kapt "com.google.dagger:dagger-compiler:$daggerVersion"
112 | kapt "com.google.dagger:dagger-android-processor:$daggerVersion"
113 |
114 | // Using Dagger in androidTest and Robolectric too
115 | kaptTest "com.google.dagger:dagger-compiler:$daggerVersion"
116 | kaptAndroidTest "com.google.dagger:dagger-compiler:$daggerVersion"
117 |
118 |
119 | implementation "io.coil-kt:coil-compose:1.4.0"
120 |
121 | }
--------------------------------------------------------------------------------
/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
--------------------------------------------------------------------------------
/app/release/app-release.apk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marjandn/GitHub/4ea5a59c4d5e38ad7b2c0c1dd0ba4e19693b8186/app/release/app-release.apk
--------------------------------------------------------------------------------
/app/release/output-metadata.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 3,
3 | "artifactType": {
4 | "type": "APK",
5 | "kind": "Directory"
6 | },
7 | "applicationId": "dn.marjan.githubapp",
8 | "variantName": "release",
9 | "elements": [
10 | {
11 | "type": "SINGLE",
12 | "filters": [],
13 | "attributes": [],
14 | "versionCode": 1,
15 | "versionName": "1.0",
16 | "outputFile": "app-release.apk"
17 | }
18 | ],
19 | "elementType": "File"
20 | }
--------------------------------------------------------------------------------
/app/src/androidTest/java/dn/marjan/githubapp/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package dn.marjan.githubapp
2 |
3 | /*import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.**/
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | /*
17 | @RunWith(AndroidJUnit4::class)
18 | class ExampleInstrumentedTest {
19 | @Test
20 | fun useAppContext() {
21 | // Context of the app under test.
22 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
23 | assertEquals("dn.marjan.githubapp", appContext.packageName)
24 | }
25 | }*/
26 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
18 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
33 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/app/src/main/java/dn/marjan/githubapp/Application.kt:
--------------------------------------------------------------------------------
1 | package dn.marjan.githubapp
2 |
3 | import dagger.android.AndroidInjector
4 | import dagger.android.DaggerApplication
5 | import dn.marjan.githubapp.di.DaggerAppComponent
6 |
7 |
8 | open class Application : DaggerApplication() {
9 |
10 | open val applicationInjector= DaggerAppComponent.builder().application(this).build()
11 |
12 | override fun applicationInjector(): AndroidInjector = applicationInjector
13 |
14 | }
--------------------------------------------------------------------------------
/app/src/main/java/dn/marjan/githubapp/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package dn.marjan.githubapp
2 |
3 | import android.os.Bundle
4 | import androidx.activity.ComponentActivity
5 | import androidx.activity.compose.setContent
6 | import androidx.compose.material.MaterialTheme
7 | import androidx.compose.material.Surface
8 | import androidx.compose.material.Text
9 | import androidx.compose.runtime.Composable
10 | import androidx.compose.ui.tooling.preview.Preview
11 | import dn.marjan.githubapp.ui.theme.GithubAppTheme
12 |
13 | class MainActivity : ComponentActivity() {
14 | override fun onCreate(savedInstanceState: Bundle?) {
15 | super.onCreate(savedInstanceState)
16 | setContent {
17 | GithubAppTheme {
18 | // A surface container using the 'background' color from the theme
19 | Surface(color = MaterialTheme.colors.background) {
20 | Greeting("Android")
21 | }
22 | }
23 | }
24 | }
25 | }
26 |
27 | @Composable
28 | fun Greeting(name: String) {
29 | Text(text = "Hello $name!")
30 | }
31 |
32 | @Preview(showBackground = true)
33 | @Composable
34 | fun DefaultPreview() {
35 | GithubAppTheme {
36 | Greeting("Android")
37 | }
38 | }
--------------------------------------------------------------------------------
/app/src/main/java/dn/marjan/githubapp/base/BaseActivity.kt:
--------------------------------------------------------------------------------
1 | package dn.marjan.githubapp.base
2 |
3 | import android.os.Bundle
4 | import androidx.activity.compose.setContent
5 | import androidx.compose.foundation.background
6 | import androidx.compose.foundation.layout.Box
7 | import androidx.compose.foundation.layout.size
8 | import androidx.compose.foundation.shape.RoundedCornerShape
9 | import androidx.compose.material.CircularProgressIndicator
10 | import androidx.compose.runtime.Composable
11 | import androidx.compose.runtime.livedata.observeAsState
12 | import androidx.compose.ui.Alignment
13 | import androidx.compose.ui.Modifier
14 | import androidx.compose.ui.graphics.Color
15 | import androidx.compose.ui.unit.dp
16 | import androidx.compose.ui.window.Dialog
17 | import androidx.compose.ui.window.DialogProperties
18 | import androidx.lifecycle.ViewModelProvider
19 | import androidx.lifecycle.ViewModelProviders
20 | import dagger.android.support.DaggerAppCompatActivity
21 | import dn.marjan.githubapp.ui.theme.GithubAppTheme
22 | import javax.inject.Inject
23 |
24 | abstract class BaseActivity : DaggerAppCompatActivity() {
25 |
26 | @Inject
27 | lateinit var viewModelFactory: ViewModelProvider.Factory
28 |
29 |
30 |
31 | protected lateinit var viewModel: V
32 |
33 | protected abstract fun getViewModel(): Class
34 |
35 | @Composable
36 | protected abstract fun ProvideCompose()
37 |
38 |
39 | override fun onCreate(savedInstanceState: Bundle?) {
40 | super.onCreate(savedInstanceState)
41 | viewModel = ViewModelProviders.of(this, viewModelFactory)[getViewModel()]
42 |
43 | setContent {
44 | GithubAppTheme {
45 | ProvideCompose()
46 | ProvideLoadingView()
47 | }
48 | }
49 | }
50 |
51 |
52 | @Composable
53 | fun ProvideLoadingView() {
54 | val loading = viewModel.showLoadingView.observeAsState()
55 |
56 | loading.value?.let {
57 | if (it)
58 | ShowLoading()
59 | }
60 | }
61 |
62 | @Composable
63 | protected open fun ShowLoading() {
64 |
65 | Dialog(
66 | onDismissRequest = { viewModel.hideLoading() },
67 | DialogProperties(dismissOnBackPress = false, dismissOnClickOutside = false)
68 | ) {
69 | Box(
70 | contentAlignment = Alignment.Center,
71 | modifier = Modifier
72 | .size(100.dp)
73 | .background(Color.White, shape = RoundedCornerShape(8.dp))
74 | ) {
75 | CircularProgressIndicator()
76 | }
77 | }
78 | }
79 |
80 | }
--------------------------------------------------------------------------------
/app/src/main/java/dn/marjan/githubapp/base/BaseViewModel.kt:
--------------------------------------------------------------------------------
1 | package dn.marjan.githubapp.base
2 |
3 | import androidx.annotation.MainThread
4 | import androidx.lifecycle.LiveData
5 | import androidx.lifecycle.MutableLiveData
6 | import androidx.lifecycle.ViewModel
7 | import dn.marjan.githubapp.data.Resource
8 | import kotlinx.coroutines.flow.MutableStateFlow
9 | import kotlinx.coroutines.flow.StateFlow
10 |
11 | open class BaseViewModel: ViewModel() {
12 |
13 | private val _showLoadingView = MutableLiveData()
14 | val showLoadingView:LiveData get() = _showLoadingView
15 |
16 |
17 | fun showLoading(){
18 | _showLoadingView.postValue(true)
19 | }
20 | fun hideLoading(){
21 | _showLoadingView.postValue(false)
22 | }
23 |
24 |
25 | }
--------------------------------------------------------------------------------
/app/src/main/java/dn/marjan/githubapp/base/CoroutineDispatcherProvider.kt:
--------------------------------------------------------------------------------
1 | package dn.marjan.githubapp.base
2 |
3 | import kotlinx.coroutines.CoroutineDispatcher
4 | import kotlinx.coroutines.Dispatchers
5 | import javax.inject.Inject
6 |
7 |
8 | class AppCoroutineDispatcherProvider @Inject constructor() : CoroutineDispatcherProvider
9 |
10 | interface CoroutineDispatcherProvider {
11 |
12 | fun IO(): CoroutineDispatcher = Dispatchers.IO
13 |
14 | fun Main(): CoroutineDispatcher = Dispatchers.Main
15 |
16 | }
--------------------------------------------------------------------------------
/app/src/main/java/dn/marjan/githubapp/base/ViewModelFactory.kt:
--------------------------------------------------------------------------------
1 | package dn.marjan.githubapp.base
2 |
3 | import androidx.lifecycle.ViewModel
4 | import androidx.lifecycle.ViewModelProvider
5 | import javax.inject.Inject
6 | import javax.inject.Provider
7 | import javax.inject.Singleton
8 |
9 | /*
10 | We can make use of that Map by creating a ViewModelFactory by extending ViewModelProvider.Factory and passing the map into it
11 | for more information about "How to inject ViewModels" read this article:
12 | https://www.techyourchance.com/dependency-injection-viewmodel-with-dagger-2
13 | */
14 |
15 |
16 | @Singleton
17 | class ViewModelFactory @Inject constructor(
18 | private val creators: Map, @JvmSuppressWildcards Provider>
19 | ) : ViewModelProvider.Factory {
20 |
21 | override fun create(modelClass: Class): T {
22 | val creator = creators[modelClass] ?: creators.entries.firstOrNull {
23 | modelClass.isAssignableFrom(it.key)
24 | }?.value ?: throw IllegalArgumentException("Hey! unknown model class $modelClass")
25 | @Suppress("UNCHECKED_CAST")
26 | return creator.get() as T
27 | }
28 | }
--------------------------------------------------------------------------------
/app/src/main/java/dn/marjan/githubapp/data/Error.kt:
--------------------------------------------------------------------------------
1 | package dn.marjan.githubapp.data
2 |
3 |
4 | sealed class Error: Throwable() {
5 | object NetworkErrors: Error()
6 | object EmptyInputError: Error()
7 | object EmptyResultError: Error()
8 | object SingleError: Error()
9 | }
--------------------------------------------------------------------------------
/app/src/main/java/dn/marjan/githubapp/data/Resource.kt:
--------------------------------------------------------------------------------
1 | package dn.marjan.githubapp.data
2 |
3 | sealed class Resource(
4 | val status: Status = Status.UNKNOWN,
5 | val data: T? = null,
6 | val message: String? = null,
7 | val error: Error? = null
8 | ) {
9 |
10 | }
11 |
12 |
13 | class SuccessResource(data: T) : Resource(status = Status.SUCCESS, data = data)
14 |
15 | class ErrorResource(error: Error, message: String = "") :
16 | Resource(status = Status.ERROR, error = error, message = message)
17 |
18 | /*fun loading(): Resource =
19 | Resource(status = Status.LOADING)*/
20 |
21 |
22 | /*data class Resource(
23 | val status: Status = Status.UNKNOWN,
24 | val data: T? = null,
25 | val message: String? = null,
26 | val error: Error? = null
27 | ) {
28 |
29 | companion object {
30 |
31 | fun success(data: Any): Resource =
32 | Resource(status = Status.SUCCESS, data = data)
33 |
34 | fun error(error: Error , message: String=""): Resource =
35 | Resource(status = Status.ERROR , error= error, message = message )
36 |
37 | fun loading(): Resource =
38 | Resource(status = Status.LOADING)
39 | }
40 | }*/
41 |
--------------------------------------------------------------------------------
/app/src/main/java/dn/marjan/githubapp/data/Status.kt:
--------------------------------------------------------------------------------
1 | package dn.marjan.githubapp.data
2 |
3 |
4 | enum class Status {
5 | UNKNOWN,
6 | LOADING,
7 | SUCCESS,
8 | ERROR,
9 | INFO,
10 | }
11 |
--------------------------------------------------------------------------------
/app/src/main/java/dn/marjan/githubapp/data/local/LocalDataService.kt:
--------------------------------------------------------------------------------
1 | package dn.marjan.githubapp.data.local
2 |
3 | import dn.marjan.githubapp.entity.UserInfo
4 |
5 | interface LocalDataService {
6 |
7 | fun saveUserData(user: UserInfo)
8 |
9 | fun getFullName():String
10 | fun getUsername():String
11 | fun getFollowers():String
12 | fun getFollowing():String
13 | fun getBlog():String
14 | fun getLocation():String
15 | fun getReposCount():String
16 | fun getUserImage():String
17 |
18 | fun isUserLogin(): Boolean
19 |
20 | }
--------------------------------------------------------------------------------
/app/src/main/java/dn/marjan/githubapp/data/local/LocalDataServiceImp.kt:
--------------------------------------------------------------------------------
1 | package dn.marjan.githubapp.data.local
2 |
3 | import android.content.Context
4 | import android.content.SharedPreferences
5 | import androidx.preference.PreferenceManager
6 | import dn.marjan.githubapp.data.local.LocalDataServiceImp.PreferenceHelper.get
7 | import dn.marjan.githubapp.data.local.LocalDataServiceImp.PreferenceHelper.set
8 | import dn.marjan.githubapp.entity.UserInfo
9 | import javax.inject.Inject
10 |
11 | class LocalDataServiceImp @Inject constructor(val context: Context) : LocalDataService {
12 |
13 | private var session: SharedPreferences
14 |
15 | init {
16 | session = PreferenceHelper.defaultPrefs(context)
17 | }
18 |
19 |
20 | object PreferenceHelper {
21 |
22 | fun defaultPrefs(context: Context): SharedPreferences =
23 | PreferenceManager.getDefaultSharedPreferences(context)
24 |
25 | fun customPrefs(context: Context, name: String): SharedPreferences =
26 | context.getSharedPreferences(name, Context.MODE_PRIVATE)
27 |
28 | inline fun SharedPreferences.edit(operation: (SharedPreferences.Editor) -> Unit) {
29 | val editor = this.edit()
30 | operation(editor)
31 | editor.apply()
32 | }
33 |
34 | /**
35 | * puts a key value pair in shared prefs if doesn't exists, otherwise updates value on given [key]
36 | *
37 | * .AccountSession.PreferenceHelper.set
38 | * and
39 | * .AccountSession.PreferenceHelper.get
40 | * in which activity you want to use it
41 | */
42 | operator fun SharedPreferences.set(key: String, value: Any) {
43 | when (value) {
44 | is String -> edit { it.putString(key, value) }
45 | is Int -> edit { it.putInt(key, value) }
46 | is Boolean -> edit { it.putBoolean(key, value) }
47 | is Float -> edit { it.putFloat(key, value) }
48 | is Long -> edit { it.putLong(key, value) }
49 | else -> throw UnsupportedOperationException("Not yet implemented")
50 | }
51 | }
52 |
53 | /**
54 | * finds value on given key.
55 | * [T] is the type of value
56 | * @param defaultValue optional default value - will take null for strings, false for bool and -1 for numeric values if [defaultValue] is not specified
57 | */
58 | inline operator fun SharedPreferences.get(
59 | key: String,
60 | defaultValue: T
61 | ): T {
62 | return when (T::class) {
63 | String::class -> getString(key, defaultValue as String) as T
64 | Int::class -> getInt(key, defaultValue as Int) as T
65 | Boolean::class -> getBoolean(key, defaultValue as Boolean) as T
66 | Float::class -> getFloat(key, defaultValue as Float) as T
67 | Long::class -> getLong(key, defaultValue as Long) as T
68 | else -> throw UnsupportedOperationException("Not yet implemented")
69 | }
70 | }
71 | }
72 |
73 | private val prefUsername = "pref_username"
74 | private val prefUserImage = "pref_user_image"
75 | private val prefUserGitHubUrl = "pref_user_github_url"
76 | private val prefUserFullName = "pref_user_fullname"
77 | private val prefUserBlog = "pref_user_blog"
78 | private val prefUserLocation = "pref_user_location"
79 | private val prefUserEmail = "pref_user_email"
80 | private val prefUserFollowers = "pref_user_followers"
81 | private val prefUserFollowing = "pref_user_following"
82 | private val prefUserReposCount = "pref_user_repos_count"
83 |
84 | override fun saveUserData(user: UserInfo) {
85 | session[prefUsername] = user.login.toString()
86 | session[prefUserImage] = user.avatarUrl.toString()
87 | session[prefUserGitHubUrl] = user.url.toString()
88 | session[prefUserFullName] = user.name.toString()
89 | session[prefUserBlog] = user.blog.toString()
90 | session[prefUserLocation] = user.location.toString()
91 | session[prefUserEmail] = user.email.toString()
92 | session[prefUserFollowers] = user.followers.toString()
93 | session[prefUserFollowing] = user.following.toString()
94 | session[prefUserReposCount] = user.publicRepos.toString()
95 | }
96 |
97 | override fun getFullName(): String = session[prefUserFullName, ""]
98 |
99 | override fun getUsername(): String = session[prefUsername, ""]
100 |
101 | override fun getFollowers(): String = session[prefUserFollowers, ""]
102 |
103 | override fun getFollowing(): String = session[prefUserFollowing, ""]
104 |
105 | override fun getBlog(): String = session[prefUserBlog, ""]
106 |
107 | override fun getLocation(): String = session[prefUserLocation, ""]
108 |
109 | override fun getReposCount(): String = session[prefUserReposCount, ""]
110 |
111 | override fun getUserImage(): String = session[prefUserImage, ""]
112 |
113 | override fun isUserLogin(): Boolean = session.contains(prefUsername)
114 |
115 | }
--------------------------------------------------------------------------------
/app/src/main/java/dn/marjan/githubapp/data/remote/RemoteDataService.kt:
--------------------------------------------------------------------------------
1 | package dn.marjan.githubapp.data.remote
2 |
3 | import dn.marjan.githubapp.entity.ReceivedEvents
4 | import dn.marjan.githubapp.entity.Repository
5 | import dn.marjan.githubapp.entity.UserInfo
6 |
7 | interface RemoteDataService {
8 | suspend fun doLogin(): UserInfo
9 |
10 | suspend fun getReceivedEvents(username: String): List
11 |
12 | suspend fun getRepositories(username: String): List
13 | }
--------------------------------------------------------------------------------
/app/src/main/java/dn/marjan/githubapp/data/remote/RemoteDataServiceImp.kt:
--------------------------------------------------------------------------------
1 | package dn.marjan.githubapp.data.remote
2 |
3 | import dn.marjan.githubapp.entity.ReceivedEvents
4 | import dn.marjan.githubapp.entity.Repository
5 | import dn.marjan.githubapp.entity.UserInfo
6 | import dn.marjan.githubapp.server.TaskService
7 | import dn.marjan.githubapp.utils.Config
8 | import javax.inject.Inject
9 |
10 | class RemoteDataServiceImp @Inject constructor(val taskService: TaskService): RemoteDataService {
11 |
12 | override suspend fun doLogin(): UserInfo {
13 | return taskService.login("Bearer ${Config.GITHUB_ACCESS_TOKEN}")
14 | }
15 |
16 | override suspend fun getReceivedEvents(username: String): List {
17 | return taskService.getReceivedEvents(username = username , page = "1") // TODO: dynamic page value with list Lazy Load
18 | }
19 |
20 | override suspend fun getRepositories(username: String): List {
21 | return taskService.getRepositories(username = username , page = "1")
22 | }
23 | }
--------------------------------------------------------------------------------
/app/src/main/java/dn/marjan/githubapp/di/AppComponent.kt:
--------------------------------------------------------------------------------
1 | package dn.marjan.githubapp.di
2 |
3 | import dagger.BindsInstance
4 | import dagger.Component
5 | import dagger.android.AndroidInjectionModule
6 | import dagger.android.AndroidInjector
7 | import dn.marjan.githubapp.Application
8 | import dn.marjan.githubapp.di.modules.AppModule
9 | import dn.marjan.githubapp.di.modules.NetworkModule
10 | import javax.inject.Singleton
11 |
12 |
13 | @Singleton
14 | @Component(modules = [
15 | AndroidInjectionModule::class,
16 | AppModule::class,
17 | ])
18 | interface AppComponent : AndroidInjector {
19 |
20 | @Component.Builder
21 | interface Builder {
22 | @BindsInstance
23 | fun application(application: android.app.Application): Builder
24 |
25 | fun build(): AppComponent
26 | }
27 |
28 | override fun inject(app: Application)
29 | }
--------------------------------------------------------------------------------
/app/src/main/java/dn/marjan/githubapp/di/keys/ViewModelKey.kt:
--------------------------------------------------------------------------------
1 | package dn.marjan.githubapp.di.keys
2 |
3 | import androidx.lifecycle.ViewModel
4 | import dagger.MapKey
5 | import kotlin.reflect.KClass
6 |
7 |
8 | /*
9 | ViewModelKey annotation, when used on methods annotated with @Provides (“provider methods”),
10 | basically says that the services returned by these methods should be inserted into Map.
11 | The keys in this Map will be of type Class extends ViewModel> and the values will be of type extends ViewModel> (subclass of ViewModel).
12 | As a result, Dagger will create an implicit Map filled with Provider objects and put it onto the objects graph
13 | */
14 |
15 | @MustBeDocumented
16 | @Target(
17 | AnnotationTarget.FUNCTION,
18 | AnnotationTarget.PROPERTY_GETTER,
19 | AnnotationTarget.PROPERTY_SETTER
20 | )
21 | @Retention(AnnotationRetention.RUNTIME)
22 | @MapKey
23 | annotation class ViewModelKey(val value: KClass)
--------------------------------------------------------------------------------
/app/src/main/java/dn/marjan/githubapp/di/modules/ActivityModule.kt:
--------------------------------------------------------------------------------
1 | package dn.marjan.githubapp.di.modules
2 |
3 | import dagger.Module
4 | import dagger.android.ContributesAndroidInjector
5 | import dn.marjan.githubapp.ui.dashboard.DashboardActivity
6 | import dn.marjan.githubapp.ui.login.ui.LoginActivity
7 | import dn.marjan.githubapp.ui.splash.ui.SplashActivity
8 |
9 | @Module
10 | abstract class ActivityModule {
11 |
12 | @ContributesAndroidInjector
13 | abstract fun bindLoginActivity(): LoginActivity
14 |
15 | @ContributesAndroidInjector
16 | abstract fun bindDashboardActivity(): DashboardActivity
17 |
18 | @ContributesAndroidInjector
19 | abstract fun bindSplashAcitvity(): SplashActivity
20 | }
21 |
--------------------------------------------------------------------------------
/app/src/main/java/dn/marjan/githubapp/di/modules/AppModule.kt:
--------------------------------------------------------------------------------
1 | package dn.marjan.githubapp.di.modules
2 |
3 | import android.content.Context
4 | import dagger.Module
5 | import dagger.Provides
6 | import dn.marjan.githubapp.base.AppCoroutineDispatcherProvider
7 | import dn.marjan.githubapp.base.CoroutineDispatcherProvider
8 | import kotlinx.coroutines.CoroutineDispatcher
9 | import kotlinx.coroutines.Dispatchers
10 | import javax.inject.Singleton
11 |
12 | @Module(
13 | includes = [
14 | DataModule::class,
15 | RepositoryModule::class,
16 | NetworkModule::class,
17 | ViewModelModule::class,
18 | ActivityModule::class
19 | ]
20 | )
21 | object AppModule {
22 |
23 | @Singleton
24 | @Provides
25 | fun provideContext(application: android.app.Application):Context = application.applicationContext
26 |
27 | @Singleton
28 | @Provides
29 | fun provideDispatchers(dispatcher: AppCoroutineDispatcherProvider): CoroutineDispatcherProvider = dispatcher
30 | }
--------------------------------------------------------------------------------
/app/src/main/java/dn/marjan/githubapp/di/modules/DataModule.kt:
--------------------------------------------------------------------------------
1 | package dn.marjan.githubapp.di.modules
2 |
3 | import android.content.Context
4 | import dagger.Module
5 | import dagger.Provides
6 | import dn.marjan.githubapp.data.local.LocalDataService
7 | import dn.marjan.githubapp.data.local.LocalDataServiceImp
8 | import dn.marjan.githubapp.data.remote.RemoteDataService
9 | import dn.marjan.githubapp.data.remote.RemoteDataServiceImp
10 | import dn.marjan.githubapp.server.TaskService
11 | import javax.inject.Singleton
12 |
13 | @Module
14 | object DataModule {
15 |
16 | @Singleton
17 | @Provides
18 | fun provideRemoteDataService(taskService: TaskService): RemoteDataService= RemoteDataServiceImp(taskService)
19 |
20 | @Singleton
21 | @Provides
22 | fun provideLocalDataService(context: Context): LocalDataService = LocalDataServiceImp(context)
23 | }
--------------------------------------------------------------------------------
/app/src/main/java/dn/marjan/githubapp/di/modules/NetworkModule.kt:
--------------------------------------------------------------------------------
1 | package dn.marjan.githubapp.di.modules
2 |
3 | import com.google.gson.Gson
4 | import com.google.gson.GsonBuilder
5 | import dagger.Module
6 | import dagger.Provides
7 | import dn.marjan.githubapp.server.TaskService
8 | import dn.marjan.githubapp.utils.Config
9 | import okhttp3.OkHttpClient
10 | import retrofit2.Retrofit
11 | import retrofit2.converter.gson.GsonConverterFactory
12 | import java.util.concurrent.TimeUnit
13 | import javax.inject.Singleton
14 |
15 | @Module
16 | object NetworkModule {
17 |
18 | @Singleton
19 | @Provides
20 | fun provideOkHttp(): OkHttpClient = OkHttpClient.Builder()
21 | .connectTimeout(1, TimeUnit.MINUTES)
22 | .readTimeout(1, TimeUnit.MINUTES)
23 | .build()
24 |
25 |
26 | @Singleton
27 | @Provides
28 | fun provideGson(): Gson = GsonBuilder()
29 | .setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
30 | .setLenient()
31 | .create()
32 |
33 | @Singleton
34 | @Provides
35 | fun provideRetrofit(gson: Gson, okHttpClient: OkHttpClient): Retrofit = Retrofit.Builder()
36 | .baseUrl(Config.BASE_URL)
37 | .addConverterFactory(GsonConverterFactory.create(gson))
38 | .client(okHttpClient)
39 | .build()
40 |
41 | @Singleton
42 | @Provides
43 | fun provideTaskService(retrofit: Retrofit): TaskService = retrofit.create(TaskService::class.java)
44 | }
--------------------------------------------------------------------------------
/app/src/main/java/dn/marjan/githubapp/di/modules/RepositoryModule.kt:
--------------------------------------------------------------------------------
1 | package dn.marjan.githubapp.di.modules
2 |
3 | import dagger.Module
4 | import dagger.Provides
5 | import dn.marjan.githubapp.data.local.LocalDataService
6 | import dn.marjan.githubapp.data.remote.RemoteDataService
7 | import dn.marjan.githubapp.ui.home.repo.HomeRepository
8 | import dn.marjan.githubapp.ui.home.repo.HomeRepositoryImp
9 | import dn.marjan.githubapp.ui.login.repo.LoginRepository
10 | import dn.marjan.githubapp.ui.login.repo.LoginRepositoryImp
11 | import dn.marjan.githubapp.ui.profile.repo.ProfileRepository
12 | import dn.marjan.githubapp.ui.profile.repo.ProfileRepositoryImp
13 | import dn.marjan.githubapp.ui.repository.repo.RepoRepository
14 | import dn.marjan.githubapp.ui.repository.repo.RepoRepositoryImp
15 | import dn.marjan.githubapp.ui.splash.repo.SplashRepository
16 | import dn.marjan.githubapp.ui.splash.repo.SplashRepositoryImp
17 | import javax.inject.Singleton
18 |
19 | @Module
20 | class RepositoryModule {
21 |
22 | @Singleton
23 | @Provides
24 | fun provideProfileRepository(localDataService: LocalDataService): ProfileRepository =
25 | ProfileRepositoryImp(localDataService = localDataService)
26 |
27 | @Singleton
28 | @Provides
29 | fun provideLoginRepository(
30 | localDataService: LocalDataService,
31 | remoteDataService: RemoteDataService
32 | ): LoginRepository =
33 | LoginRepositoryImp(remoteDataService, localDataService)
34 |
35 | @Singleton
36 | @Provides
37 | fun provideHomeRepository(
38 | localDataService: LocalDataService,
39 | remoteDataService: RemoteDataService
40 | ): HomeRepository =
41 | HomeRepositoryImp(remoteDataService, localDataService)
42 |
43 | @Singleton
44 | @Provides
45 | fun provideRepoRepository(
46 | localDataService: LocalDataService,
47 | remoteDataService: RemoteDataService
48 | ): RepoRepository =
49 | RepoRepositoryImp(remoteDataService, localDataService)
50 |
51 | @Singleton
52 | @Provides
53 | fun provideSplashRepository(localDataService: LocalDataService): SplashRepository =
54 | SplashRepositoryImp(localDataService = localDataService)
55 |
56 | }
--------------------------------------------------------------------------------
/app/src/main/java/dn/marjan/githubapp/di/modules/ViewModelModule.kt:
--------------------------------------------------------------------------------
1 | package dn.marjan.githubapp.di.modules
2 |
3 | import androidx.lifecycle.ViewModel
4 | import androidx.lifecycle.ViewModelProvider
5 | import dagger.Binds
6 | import dagger.Module
7 | import dagger.Provides
8 | import dagger.multibindings.IntoMap
9 | import dn.marjan.githubapp.base.ViewModelFactory
10 | import dn.marjan.githubapp.di.keys.ViewModelKey
11 | import dn.marjan.githubapp.ui.dashboard.DashboardViewModel
12 | import dn.marjan.githubapp.ui.home.ui.HomeViewModel
13 | import dn.marjan.githubapp.ui.login.ui.LoginViewModel
14 | import dn.marjan.githubapp.ui.profile.ui.ProfileViewModel
15 | import dn.marjan.githubapp.ui.repository.ui.RepositoryViewModel
16 | import dn.marjan.githubapp.ui.splash.ui.SplashViewModel
17 | import javax.inject.Provider
18 |
19 |
20 | /**
21 | Note that the return type of the provider method is ViewModel, not ViewModel1. It’s intentional.
22 | @IntoMap annotation says that Provider object for this service will be inserted into Map, and @ViewModelKey annotation specifies under which
23 | key it will reside.
24 | The net result of the above code will be that Dagger will create Map data structure
25 | filled with Provider objects and then provide it implicitly to other services.
26 | **/
27 |
28 |
29 | @Module
30 | abstract class ViewModelModule {
31 |
32 |
33 | @Binds
34 | @IntoMap
35 | @ViewModelKey(DashboardViewModel::class)
36 | abstract fun bindDashboardActivityVM(dashboardViewModel: DashboardViewModel): ViewModel
37 |
38 | @Binds
39 | @IntoMap
40 | @ViewModelKey(HomeViewModel::class)
41 | abstract fun bindHomeVM(homeViewModel: HomeViewModel): ViewModel
42 |
43 | @Binds
44 | @IntoMap
45 | @ViewModelKey(LoginViewModel::class)
46 | abstract fun bindLoginActivityVM(loginViewModel: LoginViewModel): ViewModel
47 |
48 | @Binds
49 | @IntoMap
50 | @ViewModelKey(SplashViewModel::class)
51 | abstract fun bindSplashActivityVM(splashViewModel: SplashViewModel): ViewModel
52 |
53 | @Binds
54 | @IntoMap
55 | @ViewModelKey(RepositoryViewModel::class)
56 | abstract fun bindRepositoryVM(repositoryViewModel: RepositoryViewModel): ViewModel
57 |
58 | @Binds
59 | @IntoMap
60 | @ViewModelKey(ProfileViewModel::class)
61 | abstract fun bindProfileVM(profileViewModel: ProfileViewModel): ViewModel
62 |
63 | @Binds
64 | internal abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
65 |
66 | }
67 |
--------------------------------------------------------------------------------
/app/src/main/java/dn/marjan/githubapp/entity/ReceivedEvents.kt:
--------------------------------------------------------------------------------
1 | package dn.marjan.githubapp.entity
2 |
3 | class ReceivedEvents {
4 | var id = ""
5 | var type = ""
6 | var actor: Actor? = null
7 | var repo: Repository? = null
8 | var payload: Payload? = null
9 |
10 |
11 | class Payload {
12 | var action = ""
13 | }
14 |
15 | class Repository {
16 | var name = ""
17 | var url = ""
18 | }
19 |
20 | class Actor {
21 | var login = ""
22 | var avatar_url = ""
23 | var url = ""
24 | }
25 | }
--------------------------------------------------------------------------------
/app/src/main/java/dn/marjan/githubapp/entity/Repository.kt:
--------------------------------------------------------------------------------
1 | package dn.marjan.githubapp.entity
2 |
3 | import com.google.gson.annotations.SerializedName
4 |
5 | class Repository {
6 | var name: String? = null
7 | var description: String? = null
8 | var language: String? = null
9 |
10 | @SerializedName("updated_at")
11 | var lastUpdate: String? = null
12 |
13 | @SerializedName("stargazers_count")
14 | var starsCount: String? = null
15 |
16 | @SerializedName("fork")
17 | var isFork = false
18 |
19 | @SerializedName("forks_count")
20 | var forkCounts: String? = null
21 |
22 | @SerializedName("watchers_count")
23 | var watchersCount: String? = null
24 |
25 | var topics: List? = null
26 | }
--------------------------------------------------------------------------------
/app/src/main/java/dn/marjan/githubapp/entity/UserInfo.kt:
--------------------------------------------------------------------------------
1 | package dn.marjan.githubapp.entity
2 |
3 | import com.google.gson.annotations.Expose
4 |
5 | import com.google.gson.annotations.SerializedName
6 |
7 |
8 | class UserInfo {
9 | @SerializedName("login")
10 | @Expose
11 | val login: String? = null
12 |
13 | @SerializedName("id")
14 | @Expose
15 | val id: Int? = null
16 |
17 | @SerializedName("node_id")
18 | @Expose
19 | val nodeId: String? = null
20 |
21 | @SerializedName("avatar_url")
22 | @Expose
23 | val avatarUrl: String? = null
24 |
25 | @SerializedName("gravatar_id")
26 | @Expose
27 | val gravatarId: String? = null
28 |
29 | @SerializedName("url")
30 | @Expose
31 | val url: String? = null
32 |
33 | @SerializedName("html_url")
34 | @Expose
35 | val htmlUrl: String? = null
36 |
37 | @SerializedName("followers_url")
38 | @Expose
39 | val followersUrl: String? = null
40 |
41 | @SerializedName("following_url")
42 | @Expose
43 | val followingUrl: String? = null
44 |
45 | @SerializedName("gists_url")
46 | @Expose
47 | val gistsUrl: String? = null
48 |
49 | @SerializedName("starred_url")
50 | @Expose
51 | val starredUrl: String? = null
52 |
53 | @SerializedName("subscriptions_url")
54 | @Expose
55 | val subscriptionsUrl: String? = null
56 |
57 | @SerializedName("organizations_url")
58 | @Expose
59 | val organizationsUrl: String? = null
60 |
61 | @SerializedName("repos_url")
62 | @Expose
63 | val reposUrl: String? = null
64 |
65 | @SerializedName("events_url")
66 | @Expose
67 | val eventsUrl: String? = null
68 |
69 | @SerializedName("received_events_url")
70 | @Expose
71 | val receivedEventsUrl: String? = null
72 |
73 | @SerializedName("type")
74 | @Expose
75 | val type: String? = null
76 |
77 | @SerializedName("site_admin")
78 | @Expose
79 | val siteAdmin: Boolean? = null
80 |
81 | @SerializedName("name")
82 | @Expose
83 | val name: String? = null
84 |
85 | @SerializedName("company")
86 | @Expose
87 | val company: Any? = null
88 |
89 | @SerializedName("blog")
90 | @Expose
91 | val blog: String? = null
92 |
93 | @SerializedName("location")
94 | @Expose
95 | val location: String? = null
96 |
97 | @SerializedName("email")
98 | @Expose
99 | val email: String? = null
100 |
101 | @SerializedName("hireable")
102 | @Expose
103 | val hireable: Any? = null
104 |
105 | @SerializedName("bio")
106 | @Expose
107 | val bio: String? = null
108 |
109 | @SerializedName("twitter_username")
110 | @Expose
111 | val twitterUsername: Any? = null
112 |
113 | @SerializedName("public_repos")
114 | @Expose
115 | val publicRepos: Int? = null
116 |
117 | @SerializedName("public_gists")
118 | @Expose
119 | val publicGists: Int? = null
120 |
121 | @SerializedName("followers")
122 | @Expose
123 | val followers: Int? = null
124 |
125 | @SerializedName("following")
126 | @Expose
127 | val following: Int? = null
128 |
129 | @SerializedName("created_at")
130 | @Expose
131 | val createdAt: String? = null
132 |
133 | @SerializedName("updated_at")
134 | @Expose
135 | val updatedAt: String? = null
136 |
137 | @SerializedName("_gists")
138 | @Expose
139 | val Gists: Int? = null
140 |
141 | @SerializedName("total__repos")
142 | @Expose
143 | val totalRepos: Int? = null
144 |
145 | @SerializedName("owned__repos")
146 | @Expose
147 | val ownedRepos: Int? = null
148 |
149 | @SerializedName("disk_usage")
150 | @Expose
151 | val diskUsage: Int? = null
152 |
153 | @SerializedName("collaborators")
154 | @Expose
155 | val collaborators: Int? = null
156 |
157 | @SerializedName("two_factor_authentication")
158 | @Expose
159 | val twoFactorAuthentication: Boolean? = null
160 |
161 | @SerializedName("plan")
162 | @Expose
163 | val plan: Plan? = null
164 |
165 | class Plan{
166 |
167 | @SerializedName("name")
168 | @Expose
169 | val name: String? = null
170 |
171 | @SerializedName("space")
172 | @Expose
173 | val space: Int? = null
174 |
175 | @SerializedName("collaborators")
176 | @Expose
177 | val collaborators: Int? = null
178 |
179 | @SerializedName("_repos")
180 | @Expose
181 | val Repos: Int? = null
182 | }
183 | }
184 |
--------------------------------------------------------------------------------
/app/src/main/java/dn/marjan/githubapp/server/TaskService.kt:
--------------------------------------------------------------------------------
1 | package dn.marjan.githubapp.server
2 |
3 | import dn.marjan.githubapp.entity.ReceivedEvents
4 | import dn.marjan.githubapp.entity.Repository
5 | import dn.marjan.githubapp.entity.UserInfo
6 | import okhttp3.ResponseBody
7 | import retrofit2.Call
8 | import retrofit2.http.*
9 |
10 |
11 | interface TaskService {
12 |
13 | @GET("user")
14 | suspend fun login(
15 | @Header("Authorization") auth: String
16 | ) : UserInfo
17 |
18 | @GET("users/{username}/received_events?")
19 | suspend fun getReceivedEvents(
20 | @Path("username") username: String,
21 | @Query("page") page: String
22 | ) : List
23 |
24 | @GET("users/{username}/repos?")
25 | suspend fun getRepositories(
26 | @Path("username") username: String,
27 | @Query("page") page: String
28 | ) : List
29 |
30 | }
--------------------------------------------------------------------------------
/app/src/main/java/dn/marjan/githubapp/ui/dashboard/BottomBarItems.kt:
--------------------------------------------------------------------------------
1 | package dn.marjan.githubapp.ui.dashboard
2 |
3 | import android.graphics.drawable.Drawable
4 | import dn.marjan.githubapp.R
5 |
6 | sealed class BottomBarItems(val name: String , val icon: Int, val route: String ) {
7 | object Home: BottomBarItems("Home" , R.drawable.ic_home , "home")
8 | object Repository: BottomBarItems("Repositories" , R.drawable.ic_folder , "repo")
9 | object Profile: BottomBarItems("Profile" , R.drawable.ic_account , "profile")
10 | }
--------------------------------------------------------------------------------
/app/src/main/java/dn/marjan/githubapp/ui/dashboard/DashboardActivity.kt:
--------------------------------------------------------------------------------
1 | package dn.marjan.githubapp.ui.dashboard
2 |
3 | import androidx.compose.material.*
4 | import androidx.compose.runtime.*
5 | import androidx.compose.ui.graphics.Color
6 | import androidx.compose.ui.res.colorResource
7 | import androidx.compose.ui.res.painterResource
8 | import androidx.core.content.ContextCompat
9 | import androidx.navigation.NavController
10 | import androidx.navigation.NavHostController
11 | import androidx.navigation.compose.NavHost
12 | import androidx.navigation.compose.composable
13 | import androidx.navigation.compose.currentBackStackEntryAsState
14 | import androidx.navigation.compose.rememberNavController
15 | import dn.marjan.githubapp.R
16 | import dn.marjan.githubapp.base.BaseActivity
17 | import dn.marjan.githubapp.ui.home.ui.HomePage
18 | import dn.marjan.githubapp.ui.home.ui.HomeViewModel
19 | import dn.marjan.githubapp.ui.profile.ui.ProfilePage
20 | import dn.marjan.githubapp.ui.profile.ui.ProfileViewModel
21 | import dn.marjan.githubapp.ui.repository.ui.RepositoryPage
22 | import dn.marjan.githubapp.ui.repository.ui.RepositoryViewModel
23 | import javax.inject.Inject
24 |
25 | class DashboardActivity : BaseActivity() {
26 |
27 | @Inject
28 | lateinit var homeViewModel: HomeViewModel
29 | @Inject
30 | lateinit var repositoryViewModel: RepositoryViewModel
31 | @Inject
32 | lateinit var profiViewModel: ProfileViewModel
33 |
34 | private lateinit var pageTitle: MutableState
35 |
36 |
37 | override fun getViewModel(): Class = DashboardViewModel::class.java
38 |
39 | private val items = listOf(
40 | BottomBarItems.Home,
41 | BottomBarItems.Repository,
42 | BottomBarItems.Profile
43 | )
44 |
45 |
46 | @Composable
47 | override fun ProvideCompose() {
48 | val navController = rememberNavController()
49 | pageTitle = remember { mutableStateOf("") }
50 |
51 | Scaffold(
52 | topBar = {
53 | TopAppBar(
54 | title = { Text(text = pageTitle.value, color = Color.White) },
55 | backgroundColor = colorResource(R.color.black),
56 | )
57 | },
58 | bottomBar = { BottomBar(navController = navController) },
59 | backgroundColor =colorResource(R.color.black)
60 | ) {
61 | CurrentPage(navController)
62 | }
63 | }
64 |
65 |
66 | @Composable
67 | fun BottomBar(navController: NavController) {
68 | BottomNavigation(
69 | backgroundColor = colorResource(R.color.black),
70 | contentColor = Color.White
71 | ) {
72 | val navBackStackEntry by navController.currentBackStackEntryAsState()
73 | val currentRoute = navBackStackEntry?.destination?.route
74 |
75 | items.forEach { item ->
76 | BottomNavigationItem(
77 | selected = currentRoute == item.route,
78 | icon = {
79 | Icon(
80 | painter = painterResource(id = item.icon),
81 | contentDescription = item.name
82 | )
83 | },
84 | label = { Text(text = item.name) },
85 | selectedContentColor = Color.White,
86 | unselectedContentColor = Color.Gray,
87 | alwaysShowLabel = true,
88 | onClick = {
89 | navController.navigate(item.route) {
90 |
91 | navController.graph.startDestinationRoute?.let {
92 | popUpTo(it) {
93 | saveState = true
94 | }
95 | }
96 |
97 | launchSingleTop = true
98 | restoreState = true
99 | }
100 | }
101 | )
102 | }
103 | }
104 | }
105 |
106 | @Composable
107 | fun CurrentPage(navController: NavHostController) {
108 | NavHost(
109 | navController = navController,
110 | startDestination = BottomBarItems.Home.route,
111 | builder = {
112 |
113 | composable(BottomBarItems.Home.route) {
114 | pageTitle.value = BottomBarItems.Home.name
115 | HomePage(this@DashboardActivity, homeViewModel)
116 | }
117 | composable(BottomBarItems.Repository.route) {
118 | pageTitle.value = BottomBarItems.Repository.name
119 | RepositoryPage(this@DashboardActivity, repositoryViewModel)
120 | }
121 | composable(BottomBarItems.Profile.route) {
122 | pageTitle.value = BottomBarItems.Profile.name
123 | ProfilePage( profiViewModel)
124 | }
125 | })
126 | }
127 | }
--------------------------------------------------------------------------------
/app/src/main/java/dn/marjan/githubapp/ui/dashboard/DashboardViewModel.kt:
--------------------------------------------------------------------------------
1 | package dn.marjan.githubapp.ui.dashboard
2 |
3 | import dn.marjan.githubapp.base.BaseViewModel
4 | import dn.marjan.githubapp.base.CoroutineDispatcherProvider
5 | import dn.marjan.githubapp.ui.login.repo.LoginRepository
6 | import javax.inject.Inject
7 |
8 | class DashboardViewModel @Inject constructor(
9 | val repository: LoginRepository,
10 | val dispatcher: CoroutineDispatcherProvider
11 | ) : BaseViewModel() {
12 | }
--------------------------------------------------------------------------------
/app/src/main/java/dn/marjan/githubapp/ui/home/repo/HomeRepository.kt:
--------------------------------------------------------------------------------
1 | package dn.marjan.githubapp.ui.home.repo
2 |
3 | import dn.marjan.githubapp.entity.ReceivedEvents
4 |
5 | interface HomeRepository {
6 | suspend fun getReceivedEvents(username:String) : List
7 |
8 | fun getUsername():String
9 | }
--------------------------------------------------------------------------------
/app/src/main/java/dn/marjan/githubapp/ui/home/repo/HomeRepositoryImp.kt:
--------------------------------------------------------------------------------
1 | package dn.marjan.githubapp.ui.home.repo
2 |
3 | import dn.marjan.githubapp.data.local.LocalDataService
4 | import dn.marjan.githubapp.data.remote.RemoteDataService
5 | import dn.marjan.githubapp.entity.ReceivedEvents
6 | import javax.inject.Inject
7 |
8 | class HomeRepositoryImp @Inject constructor(
9 | val remoteDataService: RemoteDataService,
10 | val localDataService: LocalDataService
11 | ) : HomeRepository {
12 |
13 | override suspend fun getReceivedEvents(username: String): List {
14 | return remoteDataService.getReceivedEvents(username)
15 | }
16 |
17 | override fun getUsername(): String {
18 | return localDataService.getUsername()
19 | }
20 | }
--------------------------------------------------------------------------------
/app/src/main/java/dn/marjan/githubapp/ui/home/ui/HomePage.kt:
--------------------------------------------------------------------------------
1 | package dn.marjan.githubapp.ui.home.ui
2 |
3 | import androidx.compose.foundation.Image
4 | import androidx.compose.foundation.layout.*
5 | import androidx.compose.foundation.lazy.LazyColumn
6 | import androidx.compose.foundation.shape.CircleShape
7 | import androidx.compose.material.Text
8 | import androidx.compose.runtime.*
9 | import androidx.compose.ui.Modifier
10 | import androidx.compose.ui.draw.clip
11 | import androidx.compose.ui.graphics.Color
12 | import androidx.compose.ui.text.SpanStyle
13 | import androidx.compose.ui.text.buildAnnotatedString
14 | import androidx.compose.ui.text.font.FontWeight
15 | import androidx.compose.ui.text.withStyle
16 | import androidx.compose.ui.tooling.preview.Preview
17 | import androidx.compose.ui.unit.dp
18 | import androidx.compose.ui.unit.sp
19 | import coil.compose.rememberImagePainter
20 | import dn.marjan.githubapp.data.Status
21 | import dn.marjan.githubapp.entity.ReceivedEvents
22 | import dn.marjan.githubapp.ui.dashboard.DashboardActivity
23 | import dn.marjan.githubapp.utils.log
24 | import androidx.compose.foundation.lazy.items
25 | import dn.marjan.githubapp.utils.getAction
26 |
27 | val receivedEvents: MutableState> = mutableStateOf(ArrayList())
28 |
29 | @Composable
30 | fun HomePage(dashboard: DashboardActivity, viewModel: HomeViewModel) {
31 | viewModel. getReceivedEvents()
32 |
33 | PageContent()
34 |
35 |
36 | viewModel.homeResponse.observe(dashboard) {
37 | when (it.status) {
38 | Status.SUCCESS -> {
39 | it.data?.let { list ->
40 | val items: List = (list)
41 | receivedEvents.value = items
42 | }
43 | }
44 | else -> {
45 | log("some error")
46 | }
47 | }
48 | }
49 | }
50 |
51 |
52 | @Preview
53 | @Composable
54 | fun PageContent() {
55 | LazyColumn(
56 | contentPadding = PaddingValues(
57 | top = 16.dp,
58 | bottom = 40.dp,
59 | start = 16.dp,
60 | end = 16.dp
61 | )
62 | ) {
63 | items(receivedEvents.value){item ->
64 | EventItem(item)
65 | }
66 | }
67 | }
68 |
69 | // TODO: change UI of Event items
70 | @Composable
71 | fun EventItem(item: ReceivedEvents) {
72 | return Row(
73 | modifier = Modifier
74 | .fillMaxSize()
75 | .padding(bottom = 16.dp)
76 | ) {
77 | Image(
78 | painter = rememberImagePainter(
79 | data = item.actor?.avatar_url,
80 | builder = {
81 | crossfade(true)
82 | },
83 | ), contentDescription = "User avatar image",
84 | modifier = Modifier
85 | .size(45.dp)
86 | .clip(CircleShape)
87 | )
88 | Text(
89 | buildAnnotatedString {
90 | withStyle(
91 | style = SpanStyle(
92 | color = Color.White,
93 | fontSize = 16.sp,
94 | fontWeight = FontWeight.Bold
95 | )
96 | ) {
97 | append(item.actor?.login.toString())
98 | }
99 | withStyle(style = SpanStyle(color = Color.White, fontSize = 15.sp)) {
100 | append(" ${getAction(item)} ")
101 | }
102 | withStyle(
103 | style = SpanStyle(
104 | color = Color.White,
105 | fontSize = 16.sp,
106 | fontWeight = FontWeight.Bold
107 | )
108 | ) {
109 | append(item.repo?.name.toString())
110 | }
111 | },
112 | modifier = Modifier
113 | .padding(top = 10.dp, bottom = 8.dp, start = 16.dp, end = 16.dp)
114 | )
115 | }
116 | }
--------------------------------------------------------------------------------
/app/src/main/java/dn/marjan/githubapp/ui/home/ui/HomeViewModel.kt:
--------------------------------------------------------------------------------
1 | package dn.marjan.githubapp.ui.home.ui
2 |
3 | import androidx.compose.runtime.MutableState
4 | import androidx.compose.runtime.mutableStateOf
5 | import androidx.lifecycle.LiveData
6 | import androidx.lifecycle.MutableLiveData
7 | import androidx.lifecycle.viewModelScope
8 | import dn.marjan.githubapp.base.BaseViewModel
9 | import dn.marjan.githubapp.base.CoroutineDispatcherProvider
10 | import dn.marjan.githubapp.data.*
11 | import dn.marjan.githubapp.entity.ReceivedEvents
12 | import dn.marjan.githubapp.ui.home.repo.HomeRepository
13 | import dn.marjan.githubapp.ui.login.repo.LoginRepository
14 | import dn.marjan.githubapp.utils.log
15 | import kotlinx.coroutines.launch
16 | import retrofit2.HttpException
17 | import javax.inject.Inject
18 |
19 | class HomeViewModel @Inject constructor(
20 | val repository: HomeRepository,
21 | val dispatcher: CoroutineDispatcherProvider
22 | ) : BaseViewModel() {
23 |
24 |
25 | private val _homeResponse = MutableLiveData>>()
26 |
27 |
28 | val homeResponse: LiveData>>
29 | get() = _homeResponse
30 |
31 | fun getReceivedEvents(){
32 | viewModelScope.launch(dispatcher.IO()){
33 | showLoading()
34 | try{
35 | _homeResponse.postValue(SuccessResource(data = repository.getReceivedEvents(username = repository.getUsername())))
36 | hideLoading()
37 |
38 | }catch (ex: HttpException){
39 | _homeResponse.postValue(ErrorResource(error = Error.NetworkErrors, message = ex.message().toString()))
40 | }
41 | }
42 | }
43 |
44 | fun justLog() {
45 | log("WE RE RENDER HOME PAGE MARJAN :)")
46 | }
47 | }
--------------------------------------------------------------------------------
/app/src/main/java/dn/marjan/githubapp/ui/login/repo/LoginRepository.kt:
--------------------------------------------------------------------------------
1 | package dn.marjan.githubapp.ui.login.repo
2 |
3 | import dn.marjan.githubapp.entity.UserInfo
4 |
5 |
6 | interface LoginRepository {
7 |
8 | suspend fun loginReq(username: String , password: String): UserInfo
9 |
10 | fun saveUserData(user: UserInfo)
11 |
12 | }
--------------------------------------------------------------------------------
/app/src/main/java/dn/marjan/githubapp/ui/login/repo/LoginRepositoryImp.kt:
--------------------------------------------------------------------------------
1 | package dn.marjan.githubapp.ui.login.repo
2 |
3 | import dn.marjan.githubapp.data.remote.RemoteDataService
4 | import dn.marjan.githubapp.data.local.LocalDataService
5 | import dn.marjan.githubapp.entity.UserInfo
6 | import javax.inject.Inject
7 |
8 | class LoginRepositoryImp @Inject constructor(
9 | val remoteDataService: RemoteDataService,
10 | val localDataService: LocalDataService
11 | ) : LoginRepository {
12 |
13 | override suspend fun loginReq(username: String, password: String): UserInfo {
14 | return remoteDataService.doLogin()
15 | }
16 |
17 | override fun saveUserData(user: UserInfo) {
18 | localDataService.saveUserData(user)
19 | }
20 |
21 | }
--------------------------------------------------------------------------------
/app/src/main/java/dn/marjan/githubapp/ui/login/ui/LoginActivity.kt:
--------------------------------------------------------------------------------
1 | package dn.marjan.githubapp.ui.login.ui
2 |
3 | import android.content.Intent
4 | import android.widget.ScrollView
5 | import androidx.compose.foundation.Image
6 | import androidx.compose.foundation.gestures.Orientation
7 | import androidx.compose.foundation.gestures.scrollable
8 | import androidx.compose.foundation.layout.*
9 | import androidx.compose.foundation.lazy.LazyColumn
10 | import androidx.compose.foundation.rememberScrollState
11 | import androidx.compose.foundation.shape.RoundedCornerShape
12 | import androidx.compose.foundation.verticalScroll
13 | import androidx.compose.material.*
14 | import androidx.compose.runtime.*
15 | import androidx.compose.ui.Alignment
16 | import androidx.compose.ui.Modifier
17 | import androidx.compose.ui.draw.clip
18 | import androidx.compose.ui.graphics.Color
19 | import androidx.compose.ui.res.colorResource
20 | import androidx.compose.ui.res.painterResource
21 | import androidx.compose.ui.text.input.PasswordVisualTransformation
22 | import androidx.compose.ui.text.input.VisualTransformation
23 | import androidx.compose.ui.unit.dp
24 | import androidx.compose.ui.unit.sp
25 | import dn.marjan.githubapp.R
26 | import dn.marjan.githubapp.base.BaseActivity
27 | import dn.marjan.githubapp.data.Status
28 | import dn.marjan.githubapp.ui.dashboard.DashboardActivity
29 | import kotlinx.coroutines.launch
30 |
31 | /*
32 | Google: If you use another observable type such as LiveData in Compose, you should convert it to State before reading it in a composable
33 | using a composable extension function like LiveData.observeAsState().
34 | */
35 |
36 | class LoginActivity : BaseActivity() {
37 |
38 |
39 | override fun getViewModel(): Class = LoginViewModel::class.java
40 |
41 | @Composable
42 | override fun ProvideCompose() {
43 | val scaffoldState = rememberScaffoldState()
44 | val coroutineScope = rememberCoroutineScope()
45 |
46 |
47 | var username by remember { mutableStateOf("") }
48 | var password by remember { mutableStateOf("") }
49 |
50 |
51 |
52 | viewModel.loginResponse.observe(this) {
53 | when (it.status) {
54 | Status.SUCCESS -> {
55 | startActivity(
56 | Intent(
57 | this,
58 | DashboardActivity::class.java
59 | ).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
60 | )
61 | }
62 | else -> {
63 | coroutineScope.launch {
64 | scaffoldState.snackbarHostState.showSnackbar(
65 | message = it.message.toString(),
66 | actionLabel = "OK"
67 | )
68 | }
69 | }
70 | }
71 | }
72 |
73 |
74 |
75 |
76 | Scaffold(
77 | scaffoldState = scaffoldState,
78 | ) {
79 | MainView(
80 | username = username,
81 | onUsernameChange = { username = it },
82 | password = password,
83 | onPasswordChange = { password = it },
84 | onLoginButtonClick = { viewModel.validateLoginReq(username, password) })
85 | }
86 |
87 | }
88 |
89 |
90 | @Composable
91 | fun MainView(
92 | username: String,
93 | onUsernameChange: (String) -> Unit,
94 | password: String,
95 | onPasswordChange: (String) -> Unit,
96 | onLoginButtonClick: () -> Unit
97 | ) {
98 | LazyColumn(content = {
99 | item {
100 | Column(
101 | modifier = Modifier
102 | .fillMaxSize() ,
103 | horizontalAlignment = Alignment.CenterHorizontally,
104 | ) {
105 | Image(
106 | painter = painterResource(id = R.drawable.ic_github),
107 | contentDescription = "Github",
108 | Modifier
109 | .padding(end = 32.dp, start = 32.dp, top = 50.dp, bottom = 40.dp)
110 | )
111 | OutlinedTextField(
112 | value = username,
113 | onValueChange = onUsernameChange,
114 | label = { Text(text = "Username / email") },
115 | modifier = Modifier
116 | .padding(end = 32.dp, start = 32.dp, bottom = 12.dp)
117 | .fillMaxWidth()
118 | )
119 | OutlinedTextField(
120 | value = password,
121 | onValueChange = onPasswordChange,
122 | visualTransformation = PasswordVisualTransformation(),
123 | label = { Text(text = "Password") },
124 | modifier = Modifier
125 | .padding(end = 32.dp, start = 32.dp, bottom = 32.dp)
126 | .fillMaxWidth()
127 | )
128 | Button(
129 | onClick = onLoginButtonClick,
130 | modifier = Modifier
131 | .padding(end = 32.dp, start = 32.dp, bottom = 12.dp)
132 | .fillMaxWidth()
133 | .clip(RoundedCornerShape(9.dp)),
134 | colors = ButtonDefaults.buttonColors(backgroundColor = colorResource(R.color.black))
135 | ) {
136 | Text(
137 | text = "Sign in", color = Color.White, fontSize = 18.sp, modifier = Modifier
138 | .padding(4.dp)
139 | )
140 | }
141 | }
142 | }
143 | })
144 |
145 | }
146 | }
--------------------------------------------------------------------------------
/app/src/main/java/dn/marjan/githubapp/ui/login/ui/LoginViewModel.kt:
--------------------------------------------------------------------------------
1 | package dn.marjan.githubapp.ui.login.ui
2 |
3 | import androidx.annotation.VisibleForTesting
4 | import androidx.lifecycle.*
5 | import dn.marjan.githubapp.base.BaseViewModel
6 | import dn.marjan.githubapp.base.CoroutineDispatcherProvider
7 | import dn.marjan.githubapp.data.Resource
8 | import dn.marjan.githubapp.data.Error
9 | import dn.marjan.githubapp.data.ErrorResource
10 | import dn.marjan.githubapp.data.SuccessResource
11 | import dn.marjan.githubapp.entity.UserInfo
12 | import dn.marjan.githubapp.ui.login.repo.LoginRepository
13 | import kotlinx.coroutines.*
14 | import org.json.JSONObject
15 | import retrofit2.HttpException
16 | import javax.inject.Inject
17 |
18 | /**
19 | MutableLiveData should never be exposed outside the class, as the data flow is always from VM -> View which is beauty of MVVM pattern
20 | and we should encapsulate access to MutableLiveData.
21 | **/
22 |
23 | class LoginViewModel @Inject constructor(
24 | val repository: LoginRepository,
25 | val dispatcher: CoroutineDispatcherProvider
26 | ) : BaseViewModel() {
27 |
28 | private val _loginResponse = MutableLiveData>()
29 |
30 | val loginResponse: LiveData>
31 | get() = _loginResponse
32 |
33 |
34 | fun validateLoginReq(username: String, password: String) {
35 | if (username.isEmpty() || password.isEmpty()) {
36 | _loginResponse.postValue(ErrorResource(error = Error.EmptyInputError))
37 | } else {
38 | doLogin(username = username, password = password)
39 | }
40 | }
41 |
42 |
43 | fun doLogin(username: String, password: String) {
44 | viewModelScope.launch(dispatcher.IO()) {
45 | showLoading()
46 | try {
47 | val res = SuccessResource(data = repository.loginReq(username, password))
48 | repository.saveUserData(res.data as UserInfo)
49 | _loginResponse.postValue(res)
50 |
51 | hideLoading()
52 | } catch (ex: HttpException) {
53 |
54 | ex.response()?.let { response ->
55 | response.errorBody()?.let { responseBody ->
56 | val msg = JSONObject(responseBody.string()).get("message")
57 | _loginResponse.postValue(
58 | (ErrorResource(
59 | error = Error.NetworkErrors,
60 | message = msg.toString()
61 | ))
62 | )
63 | }
64 | }
65 | hideLoading()
66 | }
67 | }
68 | }
69 |
70 |
71 | }
--------------------------------------------------------------------------------
/app/src/main/java/dn/marjan/githubapp/ui/profile/repo/ProfileRepository.kt:
--------------------------------------------------------------------------------
1 | package dn.marjan.githubapp.ui.profile.repo
2 |
3 |
4 | interface ProfileRepository {
5 |
6 | fun getFullName():String
7 | fun getUsername():String
8 | fun getFollowers():String
9 | fun getFollowing():String
10 | fun getBlog():String
11 | fun getLocation():String
12 | fun getReposCount():String
13 | fun getUserImage():String
14 | }
--------------------------------------------------------------------------------
/app/src/main/java/dn/marjan/githubapp/ui/profile/repo/ProfileRepositoryImp.kt:
--------------------------------------------------------------------------------
1 | package dn.marjan.githubapp.ui.profile.repo
2 |
3 | import dn.marjan.githubapp.data.local.LocalDataService
4 | import dn.marjan.githubapp.entity.UserInfo
5 | import javax.inject.Inject
6 |
7 | class ProfileRepositoryImp @Inject constructor(
8 | var localDataService: LocalDataService
9 | ) : ProfileRepository{
10 | override fun getFullName(): String = localDataService.getFullName()
11 |
12 | override fun getUsername(): String = localDataService.getUsername()
13 |
14 | override fun getFollowers(): String = localDataService.getFollowers()
15 |
16 | override fun getFollowing(): String = localDataService.getFollowing()
17 |
18 | override fun getBlog(): String = localDataService.getBlog()
19 |
20 | override fun getLocation(): String = localDataService.getLocation()
21 |
22 | override fun getReposCount(): String = localDataService.getReposCount()
23 |
24 | override fun getUserImage(): String = localDataService.getUserImage()
25 |
26 |
27 | }
--------------------------------------------------------------------------------
/app/src/main/java/dn/marjan/githubapp/ui/profile/ui/ProfilePage.kt:
--------------------------------------------------------------------------------
1 | package dn.marjan.githubapp.ui.profile.ui
2 |
3 | import androidx.compose.foundation.Image
4 | import androidx.compose.foundation.layout.*
5 | import androidx.compose.foundation.shape.CircleShape
6 | import androidx.compose.material.Text
7 | import androidx.compose.runtime.*
8 | import androidx.compose.ui.Alignment
9 | import androidx.compose.ui.Modifier
10 | import androidx.compose.ui.draw.clip
11 | import androidx.compose.ui.graphics.Color
12 | import androidx.compose.ui.graphics.ColorFilter
13 | import androidx.compose.ui.res.colorResource
14 | import androidx.compose.ui.res.painterResource
15 | import androidx.compose.ui.text.font.FontWeight
16 | import androidx.compose.ui.text.style.TextAlign
17 | import androidx.compose.ui.tooling.preview.Preview
18 | import androidx.compose.ui.unit.dp
19 | import androidx.compose.ui.unit.sp
20 | import coil.compose.rememberImagePainter
21 | import dn.marjan.githubapp.R
22 |
23 |
24 | @Composable
25 | fun ProfilePage(profileViewModel: ProfileViewModel) {
26 |
27 | Column(
28 | horizontalAlignment = Alignment.CenterHorizontally,
29 | modifier = Modifier
30 | .fillMaxSize()
31 | .padding(bottom = 16.dp, top = 32.dp)
32 | ) {
33 | Image(
34 | painter = rememberImagePainter(
35 | data = profileViewModel.getUserImage(),
36 | builder = { crossfade(true) }), contentDescription = "Profile Image",
37 | modifier = Modifier
38 | .size(200.dp)
39 | .fillMaxWidth()
40 | .clip(CircleShape)
41 | )
42 | Text(
43 | text = profileViewModel.getFullName(),
44 | color = Color.White,
45 | fontSize = 16.sp,
46 | fontWeight = FontWeight.Bold,
47 | textAlign = TextAlign.Center,
48 | modifier = Modifier
49 | .padding(start = 16.dp, end = 16.dp, bottom = 16.dp, top = 32.dp)
50 | .fillMaxWidth()
51 |
52 | )
53 | Text(
54 | text = profileViewModel.getUsername(),
55 | color = Color.White,
56 | fontSize = 14.sp,
57 | textAlign = TextAlign.Center,
58 | modifier = Modifier
59 | .padding(start = 16.dp, end = 16.dp, bottom = 16.dp)
60 | .fillMaxWidth()
61 | )
62 | Row(
63 | modifier = Modifier
64 | .fillMaxWidth()
65 | .padding(start = 32.dp, top = 12.dp)
66 | ) {
67 | Image(
68 | painter = painterResource(id = R.drawable.ic_people),
69 | colorFilter = ColorFilter.tint(colorResource(id = R.color.gray)),
70 | contentDescription = "Followers and Following",
71 | modifier = Modifier
72 | .size(20.dp)
73 | )
74 | Text(
75 | text = "${profileViewModel.getFollowers()} followers . ${profileViewModel.getFollowing()} following",
76 | fontSize = 15.sp,
77 | color = Color.White,
78 | modifier = Modifier
79 | .padding(start = 10.dp)
80 | )
81 | }
82 | Row(
83 | modifier = Modifier
84 | .fillMaxWidth()
85 | .padding(start = 32.dp, top = 12.dp)
86 | ) {
87 | Image(
88 | painter = painterResource(id = R.drawable.ic_link),
89 | colorFilter = ColorFilter.tint(colorResource(id = R.color.gray)),
90 | contentDescription = "Blog",
91 | modifier = Modifier
92 | .size(20.dp)
93 | )
94 | Text(
95 | text = profileViewModel.getBlog(),
96 | fontSize = 15.sp,
97 | color = Color.White,
98 | modifier = Modifier
99 | .padding(start = 10.dp)
100 | )
101 | }
102 | Row(
103 | modifier = Modifier
104 | .fillMaxWidth()
105 | .padding(start = 32.dp, top = 12.dp)
106 | ) {
107 | Image(
108 | painter = painterResource(id = R.drawable.ic_location),
109 | colorFilter = ColorFilter.tint(colorResource(id = R.color.gray)),
110 | contentDescription = "Location",
111 | modifier = Modifier
112 | .size(20.dp)
113 | )
114 | Text(
115 | text = profileViewModel.getLocation(),
116 | fontSize = 15.sp,
117 | color = Color.White,
118 | modifier = Modifier
119 | .padding(start = 10.dp)
120 | )
121 | }
122 | Row(
123 | modifier = Modifier
124 | .fillMaxWidth()
125 | .padding(start = 32.dp, top = 12.dp)
126 | ) {
127 | Image(
128 | painter = painterResource(id = R.drawable.ic_folder),
129 | colorFilter = ColorFilter.tint(colorResource(id = R.color.gray)),
130 | contentDescription = "Repos count",
131 | modifier = Modifier
132 | .size(20.dp)
133 | )
134 | Text(
135 | text = profileViewModel.getReposCount(),
136 | fontSize = 15.sp,
137 | color = Color.White,
138 | modifier = Modifier
139 | .padding(start = 10.dp)
140 | )
141 | }
142 |
143 | }
144 |
145 | }
146 |
147 | @Preview
148 | @Composable
149 | fun PageContent() {
150 |
151 | }
--------------------------------------------------------------------------------
/app/src/main/java/dn/marjan/githubapp/ui/profile/ui/ProfileViewModel.kt:
--------------------------------------------------------------------------------
1 | package dn.marjan.githubapp.ui.profile.ui
2 |
3 | import dn.marjan.githubapp.base.BaseViewModel
4 | import dn.marjan.githubapp.base.CoroutineDispatcherProvider
5 | import dn.marjan.githubapp.ui.profile.repo.ProfileRepository
6 | import javax.inject.Inject
7 |
8 | class ProfileViewModel @Inject constructor(
9 | val repository: ProfileRepository,
10 | val dispatcherProvider: CoroutineDispatcherProvider
11 | ) : BaseViewModel(){
12 |
13 | fun getFullName(): String = repository.getFullName()
14 |
15 | fun getUsername(): String = repository.getUsername()
16 |
17 | fun getFollowers(): String = repository.getFollowers()
18 |
19 | fun getFollowing(): String = repository.getFollowing()
20 |
21 | fun getBlog(): String = repository.getBlog()
22 |
23 | fun getLocation(): String = repository.getLocation()
24 |
25 | fun getReposCount(): String = repository.getReposCount()
26 |
27 | fun getUserImage(): String = repository.getUserImage()
28 |
29 | }
--------------------------------------------------------------------------------
/app/src/main/java/dn/marjan/githubapp/ui/repository/repo/RepoRepository.kt:
--------------------------------------------------------------------------------
1 | package dn.marjan.githubapp.ui.repository.repo
2 |
3 | import dn.marjan.githubapp.entity.Repository
4 |
5 | interface RepoRepository {
6 |
7 | fun getUsername():String
8 |
9 | suspend fun getRepositories(username: String): List
10 | }
--------------------------------------------------------------------------------
/app/src/main/java/dn/marjan/githubapp/ui/repository/repo/RepoRepositoryImp.kt:
--------------------------------------------------------------------------------
1 | package dn.marjan.githubapp.ui.repository.repo
2 |
3 | import dn.marjan.githubapp.data.local.LocalDataService
4 | import dn.marjan.githubapp.data.remote.RemoteDataService
5 | import dn.marjan.githubapp.entity.Repository
6 | import javax.inject.Inject
7 |
8 | class RepoRepositoryImp @Inject constructor(
9 | val remoteDataService: RemoteDataService,
10 | val localDataService: LocalDataService
11 | ) : RepoRepository{
12 |
13 | override fun getUsername(): String = localDataService.getUsername()
14 |
15 | override suspend fun getRepositories(username: String): List {
16 | return remoteDataService.getRepositories(username)
17 | }
18 |
19 | }
--------------------------------------------------------------------------------
/app/src/main/java/dn/marjan/githubapp/ui/repository/ui/RepositoryPage.kt:
--------------------------------------------------------------------------------
1 | package dn.marjan.githubapp.ui.repository.ui
2 |
3 | import androidx.compose.foundation.Image
4 | import androidx.compose.foundation.background
5 | import androidx.compose.foundation.layout.*
6 | import androidx.compose.foundation.lazy.LazyColumn
7 | import androidx.compose.foundation.lazy.items
8 | import androidx.compose.foundation.shape.CircleShape
9 | import androidx.compose.material.Divider
10 | import androidx.compose.material.Text
11 | import androidx.compose.runtime.Composable
12 | import androidx.compose.runtime.MutableState
13 | import androidx.compose.runtime.mutableStateOf
14 | import androidx.compose.ui.Alignment
15 | import androidx.compose.ui.Modifier
16 | import androidx.compose.ui.draw.clip
17 | import androidx.compose.ui.graphics.Color
18 | import androidx.compose.ui.graphics.ColorFilter
19 | import androidx.compose.ui.res.colorResource
20 | import androidx.compose.ui.res.painterResource
21 | import androidx.compose.ui.text.font.FontWeight
22 | import androidx.compose.ui.tooling.preview.Preview
23 | import androidx.compose.ui.unit.dp
24 | import androidx.compose.ui.unit.sp
25 | import dn.marjan.githubapp.R
26 | import dn.marjan.githubapp.data.Status
27 | import dn.marjan.githubapp.entity.Repository
28 | import dn.marjan.githubapp.ui.dashboard.DashboardActivity
29 | import dn.marjan.githubapp.utils.getLanguageColor
30 | import dn.marjan.githubapp.utils.log
31 |
32 | val repositories: MutableState> = mutableStateOf(ArrayList())
33 |
34 |
35 | @Composable
36 | fun RepositoryPage(dashboard: DashboardActivity, viewModel: RepositoryViewModel) {
37 | viewModel.getRepositories()
38 |
39 | PageContent()
40 |
41 | viewModel.repoResponse.observe(dashboard) {
42 | when (it.status) {
43 | Status.SUCCESS -> {
44 | it.data?.let { list ->
45 | repositories.value = list
46 | }
47 | }
48 | else -> {
49 | log("sth has error")
50 | }
51 | }
52 | }
53 | }
54 |
55 | @Preview
56 | @Composable
57 | fun PageContent() {
58 |
59 | LazyColumn(
60 | contentPadding = PaddingValues(
61 | top = 16.dp,
62 | bottom = 40.dp,
63 | start = 16.dp,
64 | end = 16.dp
65 | )
66 | ) {
67 | items(repositories.value) { item ->
68 | RepositoryItem(item)
69 | }
70 |
71 | }
72 |
73 | }
74 |
75 | // TODO: reDesign repositories item base of github
76 | @Composable
77 | fun RepositoryItem(item: Repository) {
78 | Column(
79 | modifier = Modifier
80 | .fillMaxSize()
81 | .padding(bottom = 16.dp, end = 16.dp, start = 16.dp)
82 | ) {
83 | Text(
84 | text = item.name ?: "",
85 | color = colorResource(id = R.color.purple_200),
86 | fontSize = 20.sp,
87 | fontWeight = FontWeight.Bold,
88 | modifier = Modifier
89 | .fillMaxWidth()
90 | .padding(bottom = 16.dp),
91 |
92 | )
93 | item.description?.let {
94 | Text(
95 | text = it,
96 | color = Color.White,
97 | fontSize = 14.sp,
98 | modifier = Modifier
99 | .fillMaxWidth()
100 | .padding(bottom = 16.dp),
101 | )
102 | }
103 | Row(modifier = Modifier.padding(bottom = 16.dp) , verticalAlignment = Alignment.CenterVertically) {
104 | item.language?.let {
105 | Box(
106 | modifier = Modifier
107 | .size(17.dp)
108 | .clip(CircleShape)
109 | .background(colorResource(id = getLanguageColor(it)))
110 | )
111 | Text(
112 | text = it,
113 | color = Color.White,
114 | fontSize = 14.sp,
115 | modifier = Modifier
116 | .padding(start = 5.dp, end = 16.dp),
117 |
118 | )
119 | }
120 | Image(
121 | painter = painterResource(id = R.drawable.ic_star),
122 | contentDescription = "star",
123 | colorFilter = ColorFilter.tint(colorResource(id = R.color.gray)),
124 | modifier = Modifier
125 | .width(20.dp)
126 | .height(20.dp)
127 | )
128 | Text(
129 | text = item.starsCount ?: "",
130 | color = Color.White,
131 | fontSize = 14.sp,
132 | modifier = Modifier
133 | .padding(start = 5.dp, end = 16.dp),
134 |
135 | )
136 |
137 | Image(
138 | painter = painterResource(id = R.drawable.fork),
139 | contentDescription = "fork",
140 | colorFilter = ColorFilter.tint(colorResource(id = R.color.gray)),
141 | modifier = Modifier
142 | .width(20.dp)
143 | .height(20.dp)
144 | )
145 | Text(
146 | text = item.forkCounts ?: "",
147 | color = Color.White,
148 | fontSize = 14.sp,
149 | modifier = Modifier
150 | .padding(start = 5.dp, end = 16.dp),
151 | )
152 | Image(
153 | painter = painterResource(id = R.drawable.ic_eye),
154 | contentDescription = "watch",
155 | colorFilter = ColorFilter.tint(colorResource(id = R.color.gray)),
156 | modifier = Modifier
157 | .width(20.dp)
158 | .height(20.dp)
159 | )
160 | Text(
161 | text = item.watchersCount ?: "",
162 | color = Color.White,
163 | fontSize = 14.sp,
164 | modifier = Modifier
165 | .padding(start = 5.dp, end = 16.dp),
166 |
167 | )
168 | }
169 | Divider(color = colorResource(id = R.color.gray), thickness = 0.5.dp)
170 |
171 | }
172 | }
--------------------------------------------------------------------------------
/app/src/main/java/dn/marjan/githubapp/ui/repository/ui/RepositoryViewModel.kt:
--------------------------------------------------------------------------------
1 | package dn.marjan.githubapp.ui.repository.ui
2 |
3 | import androidx.lifecycle.LiveData
4 | import androidx.lifecycle.MutableLiveData
5 | import androidx.lifecycle.viewModelScope
6 | import dn.marjan.githubapp.base.BaseViewModel
7 | import dn.marjan.githubapp.base.CoroutineDispatcherProvider
8 | import dn.marjan.githubapp.data.Error
9 | import dn.marjan.githubapp.data.ErrorResource
10 | import dn.marjan.githubapp.data.Resource
11 | import dn.marjan.githubapp.data.SuccessResource
12 | import dn.marjan.githubapp.entity.Repository
13 | import dn.marjan.githubapp.ui.repository.repo.RepoRepository
14 | import kotlinx.coroutines.launch
15 | import retrofit2.HttpException
16 | import javax.inject.Inject
17 |
18 | class RepositoryViewModel @Inject constructor(
19 | val repoRepository: RepoRepository,
20 | val dispatcherProvider: CoroutineDispatcherProvider
21 | ) : BaseViewModel() {
22 |
23 | private val _repoResponse = MutableLiveData>>()
24 |
25 | val repoResponse: LiveData>>
26 | get() = _repoResponse
27 |
28 | fun getRepositories(){
29 | viewModelScope.launch(dispatcherProvider.IO()) {
30 | try{
31 | val username = repoRepository.getUsername()
32 | val dd = repoRepository.getRepositories(username = username)
33 | _repoResponse.postValue(SuccessResource(data =dd ))
34 | }catch (e: HttpException){
35 | _repoResponse.postValue(ErrorResource(error = Error.NetworkErrors , message = e.message()))
36 | }
37 | }
38 | }
39 | }
--------------------------------------------------------------------------------
/app/src/main/java/dn/marjan/githubapp/ui/splash/repo/SplashRepository.kt:
--------------------------------------------------------------------------------
1 | package dn.marjan.githubapp.ui.splash.repo
2 |
3 | interface SplashRepository {
4 | fun checkUserLogin():Boolean
5 | }
--------------------------------------------------------------------------------
/app/src/main/java/dn/marjan/githubapp/ui/splash/repo/SplashRepositoryImp.kt:
--------------------------------------------------------------------------------
1 | package dn.marjan.githubapp.ui.splash.repo
2 |
3 | import dn.marjan.githubapp.data.local.LocalDataService
4 | import javax.inject.Inject
5 |
6 | class SplashRepositoryImp @Inject constructor(
7 | val localDataService: LocalDataService
8 | ) : SplashRepository{
9 |
10 | override fun checkUserLogin(): Boolean {
11 | return localDataService.isUserLogin()
12 | }
13 |
14 | }
--------------------------------------------------------------------------------
/app/src/main/java/dn/marjan/githubapp/ui/splash/ui/SplashActivity.kt:
--------------------------------------------------------------------------------
1 | package dn.marjan.githubapp.ui.splash.ui
2 |
3 | import android.content.Intent
4 | import android.os.Bundle
5 | import androidx.activity.ComponentActivity
6 | import androidx.activity.compose.setContent
7 | import androidx.compose.foundation.Image
8 | import androidx.compose.foundation.layout.Arrangement
9 | import androidx.compose.foundation.layout.Column
10 | import androidx.compose.foundation.layout.fillMaxSize
11 | import androidx.compose.foundation.layout.padding
12 | import androidx.compose.material.Surface
13 | import androidx.compose.runtime.Composable
14 | import androidx.compose.ui.Alignment
15 | import androidx.compose.ui.Modifier
16 | import androidx.compose.ui.graphics.Color
17 | import androidx.compose.ui.res.painterResource
18 | import androidx.compose.ui.tooling.preview.Preview
19 | import androidx.compose.ui.unit.dp
20 | import dn.marjan.githubapp.R
21 | import dn.marjan.githubapp.base.BaseActivity
22 | import dn.marjan.githubapp.data.Resource
23 | import dn.marjan.githubapp.data.Status
24 | import dn.marjan.githubapp.ui.dashboard.DashboardActivity
25 | import dn.marjan.githubapp.ui.login.ui.LoginActivity
26 |
27 | class SplashActivity : BaseActivity() {
28 |
29 |
30 | override fun getViewModel(): Class = SplashViewModel::class.java
31 |
32 | @Composable
33 | override fun ProvideCompose() {
34 | viewModel.checkUserLogin()
35 | ContentUI()
36 |
37 | viewModel.splashResponse.observe(this) {
38 | when (it.status) {
39 | Status.SUCCESS -> startActivity(
40 | Intent(
41 | this,
42 | DashboardActivity::class.java
43 | ).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
44 | )
45 | else -> startActivity(
46 | Intent(
47 | this,
48 | LoginActivity::class.java
49 | ).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
50 | )
51 | }
52 |
53 | }
54 | }
55 |
56 | @Preview
57 | @Composable
58 | fun ContentUI() {
59 | Column(
60 | verticalArrangement = Arrangement.Center,
61 | horizontalAlignment = Alignment.CenterHorizontally,
62 | modifier = Modifier.fillMaxSize()
63 | ) {
64 | Image(
65 | painter = painterResource(id = R.drawable.ic_github),
66 | contentDescription = "Github",
67 | modifier = Modifier.padding(all = 32.dp)
68 | )
69 | }
70 | }
71 | }
--------------------------------------------------------------------------------
/app/src/main/java/dn/marjan/githubapp/ui/splash/ui/SplashViewModel.kt:
--------------------------------------------------------------------------------
1 | package dn.marjan.githubapp.ui.splash.ui
2 |
3 | import androidx.lifecycle.LiveData
4 | import androidx.lifecycle.MutableLiveData
5 | import androidx.lifecycle.viewModelScope
6 | import dn.marjan.githubapp.base.BaseViewModel
7 | import dn.marjan.githubapp.base.CoroutineDispatcherProvider
8 | import dn.marjan.githubapp.data.Error
9 | import dn.marjan.githubapp.data.ErrorResource
10 | import dn.marjan.githubapp.data.Resource
11 | import dn.marjan.githubapp.data.SuccessResource
12 | import dn.marjan.githubapp.ui.splash.repo.SplashRepository
13 | import kotlinx.coroutines.launch
14 | import javax.inject.Inject
15 |
16 | class SplashViewModel @Inject constructor(
17 | val repository: SplashRepository,
18 | val dispatcherProvider: CoroutineDispatcherProvider
19 | ) : BaseViewModel() {
20 |
21 | private var _splashResponse = MutableLiveData>()
22 |
23 | val splashResponse: LiveData>
24 | get() = _splashResponse
25 |
26 | fun checkUserLogin(){
27 |
28 | viewModelScope.launch(dispatcherProvider.Main()) {
29 | val isUserLogin = repository.checkUserLogin()
30 |
31 | if(isUserLogin) _splashResponse.postValue(SuccessResource(data = true))
32 | else _splashResponse.postValue(ErrorResource(error = Error.EmptyResultError))
33 | }
34 |
35 | }
36 | }
--------------------------------------------------------------------------------
/app/src/main/java/dn/marjan/githubapp/ui/theme/Color.kt:
--------------------------------------------------------------------------------
1 | package dn.marjan.githubapp.ui.theme
2 |
3 | import androidx.compose.ui.graphics.Color
4 |
5 | val Purple200 = Color(0xFFBB86FC)
6 | val Purple500 = Color(0xFF6200EE)
7 | val Purple700 = Color(0xFF3700B3)
8 | val Teal200 = Color(0xFF03DAC5)
--------------------------------------------------------------------------------
/app/src/main/java/dn/marjan/githubapp/ui/theme/Shape.kt:
--------------------------------------------------------------------------------
1 | package dn.marjan.githubapp.ui.theme
2 |
3 | import androidx.compose.foundation.shape.RoundedCornerShape
4 | import androidx.compose.material.Shapes
5 | import androidx.compose.ui.unit.dp
6 |
7 | val Shapes = Shapes(
8 | small = RoundedCornerShape(4.dp),
9 | medium = RoundedCornerShape(4.dp),
10 | large = RoundedCornerShape(0.dp)
11 | )
--------------------------------------------------------------------------------
/app/src/main/java/dn/marjan/githubapp/ui/theme/Theme.kt:
--------------------------------------------------------------------------------
1 | package dn.marjan.githubapp.ui.theme
2 |
3 | import androidx.compose.foundation.isSystemInDarkTheme
4 | import androidx.compose.material.MaterialTheme
5 | import androidx.compose.material.darkColors
6 | import androidx.compose.material.lightColors
7 | import androidx.compose.runtime.Composable
8 |
9 | private val DarkColorPalette = darkColors(
10 | primary = Purple200,
11 | primaryVariant = Purple700,
12 | secondary = Teal200
13 | )
14 |
15 | private val LightColorPalette = lightColors(
16 | primary = Purple500,
17 | primaryVariant = Purple700,
18 | secondary = Teal200
19 |
20 | /* Other default colors to override
21 | background = Color.White,
22 | surface = Color.White,
23 | onPrimary = Color.White,
24 | onSecondary = Color.Black,
25 | onBackground = Color.Black,
26 | onSurface = Color.Black,
27 | */
28 | )
29 |
30 | @Composable
31 | fun GithubAppTheme(darkTheme: Boolean = isSystemInDarkTheme() , content: @Composable() () -> Unit) {
32 | val colors = if (darkTheme) {
33 | DarkColorPalette
34 | } else {
35 | LightColorPalette
36 | }
37 |
38 | MaterialTheme(
39 | colors = colors,
40 | typography = Typography,
41 | shapes = Shapes,
42 | content = content
43 | )
44 | }
--------------------------------------------------------------------------------
/app/src/main/java/dn/marjan/githubapp/ui/theme/Type.kt:
--------------------------------------------------------------------------------
1 | package dn.marjan.githubapp.ui.theme
2 |
3 | import androidx.compose.material.Typography
4 | import androidx.compose.ui.text.TextStyle
5 | import androidx.compose.ui.text.font.FontFamily
6 | import androidx.compose.ui.text.font.FontWeight
7 | import androidx.compose.ui.unit.sp
8 |
9 | // Set of Material typography styles to start with
10 | val Typography = Typography(
11 | body1 = TextStyle(
12 | fontFamily = FontFamily.Default,
13 | fontWeight = FontWeight.Normal,
14 | fontSize = 16.sp
15 | )
16 | /* Other default text styles to override
17 | button = TextStyle(
18 | fontFamily = FontFamily.Default,
19 | fontWeight = FontWeight.W500,
20 | fontSize = 14.sp
21 | ),
22 | caption = TextStyle(
23 | fontFamily = FontFamily.Default,
24 | fontWeight = FontWeight.Normal,
25 | fontSize = 12.sp
26 | )
27 | */
28 | )
--------------------------------------------------------------------------------
/app/src/main/java/dn/marjan/githubapp/utils/Config.kt:
--------------------------------------------------------------------------------
1 | package dn.marjan.githubapp.utils
2 |
3 | object Config {
4 | const val GITHUB_ACCESS_TOKEN ="XXXXXXXX"
5 | const val BASE_URL = "https://api.github.com/"
6 | }
--------------------------------------------------------------------------------
/app/src/main/java/dn/marjan/githubapp/utils/Utility.kt:
--------------------------------------------------------------------------------
1 | package dn.marjan.githubapp.utils
2 |
3 | import android.util.Log
4 | import androidx.compose.material.Snackbar
5 | import androidx.compose.ui.graphics.Color
6 | import androidx.compose.ui.res.colorResource
7 | import dn.marjan.githubapp.R
8 | import dn.marjan.githubapp.entity.ReceivedEvents
9 |
10 | fun log(msg: String){
11 | Log.d("MARJANTAG", msg);
12 | }
13 |
14 |
15 | fun getAction(item: ReceivedEvents): String {
16 | if (item.payload!!.action.isNotEmpty()) {
17 | return item.payload!!.action
18 | } else {
19 | when (item.type) {
20 | "ForkEvent" -> return "forked"
21 | "CreateEvent" -> return "created a repository"
22 | "PublicEvent" -> return "made"
23 | }
24 | }
25 | return ""
26 | }
27 |
28 | fun getLanguageColor(lang: String): Int {
29 | return when(lang){
30 | "Java" -> R.color.JavaColor
31 | "Kotlin" -> R.color.KotlinColor
32 | "Dart" -> R.color.DartColor
33 | else -> R.color.HtmlColor
34 | }
35 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/circle_shape.xml:
--------------------------------------------------------------------------------
1 |
4 |
6 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/fork.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marjandn/GitHub/4ea5a59c4d5e38ad7b2c0c1dd0ba4e19693b8186/app/src/main/res/drawable/fork.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_account.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_eye.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_folder.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_github.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marjandn/GitHub/4ea5a59c4d5e38ad7b2c0c1dd0ba4e19693b8186/app/src/main/res/drawable/ic_github.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_home.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/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/ic_link.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_location.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_people.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_star.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_time.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
13 |
14 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marjandn/GitHub/4ea5a59c4d5e38ad7b2c0c1dd0ba4e19693b8186/app/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marjandn/GitHub/4ea5a59c4d5e38ad7b2c0c1dd0ba4e19693b8186/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marjandn/GitHub/4ea5a59c4d5e38ad7b2c0c1dd0ba4e19693b8186/app/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marjandn/GitHub/4ea5a59c4d5e38ad7b2c0c1dd0ba4e19693b8186/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marjandn/GitHub/4ea5a59c4d5e38ad7b2c0c1dd0ba4e19693b8186/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marjandn/GitHub/4ea5a59c4d5e38ad7b2c0c1dd0ba4e19693b8186/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marjandn/GitHub/4ea5a59c4d5e38ad7b2c0c1dd0ba4e19693b8186/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marjandn/GitHub/4ea5a59c4d5e38ad7b2c0c1dd0ba4e19693b8186/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marjandn/GitHub/4ea5a59c4d5e38ad7b2c0c1dd0ba4e19693b8186/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marjandn/GitHub/4ea5a59c4d5e38ad7b2c0c1dd0ba4e19693b8186/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/values-night/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFBB86FC
4 | #FF6200EE
5 | #FF3700B3
6 | #FF03DAC5
7 | #FF018786
8 | #101010
9 | #FFFFFFFF
10 | #8B949E
11 | #F44336
12 | #58A6FF
13 |
14 | #00B4AB
15 | #B07219
16 | #E34C26
17 | #AA7DFF
18 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | GithubApp
3 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
17 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/app/src/sharedTest/java/dn.marjan.githubapp/ApplicationTest.kt:
--------------------------------------------------------------------------------
1 | package dn.marjan.githubapp
2 |
3 | import dn.marjan.githubapp.di.AppComponent
4 | import dn.marjan.githubapp.di.component.DaggerTestAppComponent
5 |
6 | class ApplicationTest: Application() {
7 |
8 | override val applicationInjector: AppComponent
9 | get() = DaggerTestAppComponent.builder().application(this).build()
10 | }
--------------------------------------------------------------------------------
/app/src/test/java/dn/marjan/githubapp/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package dn.marjan.githubapp
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------
/app/src/test/java/dn/marjan/githubapp/di/component/TestAppComponent.kt:
--------------------------------------------------------------------------------
1 | package dn.marjan.githubapp.di.component
2 |
3 | import dagger.BindsInstance
4 | import dagger.Component
5 | import dagger.android.AndroidInjectionModule
6 | import dn.marjan.githubapp.ApplicationTest
7 | import dn.marjan.githubapp.di.AppComponent
8 | import dn.marjan.githubapp.di.modules.AppModuleTest
9 | import dn.marjan.githubapp.ui.home.HomeRepositoryTest
10 | import dn.marjan.githubapp.ui.login.LoginRepositoryTest
11 | import dn.marjan.githubapp.ui.home.HomeViewModelTest
12 | import dn.marjan.githubapp.ui.login.LoginViewModelTest
13 | import dn.marjan.githubapp.ui.repository.RepositoryViewModelTest
14 | import dn.marjan.githubapp.ui.splash.SplashRepositoryTest
15 | import dn.marjan.githubapp.ui.splash.SplashViewModelTest
16 | import javax.inject.Singleton
17 |
18 |
19 | @Singleton
20 | @Component(
21 | modules = [
22 | AndroidInjectionModule::class,
23 | AppModuleTest::class
24 | ]
25 | )
26 | interface TestAppComponent: AppComponent {
27 |
28 | @Component.Builder
29 | interface Builder{
30 | @BindsInstance
31 | fun application(application: android.app.Application): Builder
32 |
33 | fun build(): TestAppComponent
34 | }
35 |
36 | fun inject(app: ApplicationTest)
37 | fun inject(test: LoginViewModelTest)
38 | fun inject(test: LoginRepositoryTest)
39 | fun inject(test: HomeRepositoryTest)
40 | fun inject(test: HomeViewModelTest)
41 | fun inject(test: SplashViewModelTest)
42 | fun inject(test: SplashRepositoryTest)
43 | fun inject(test: RepositoryViewModelTest)
44 |
45 | }
--------------------------------------------------------------------------------
/app/src/test/java/dn/marjan/githubapp/di/dispatchers/TestCoroutineDispatcherProvider.kt:
--------------------------------------------------------------------------------
1 | package dn.marjan.githubapp.di.dispatchers
2 |
3 | import dn.marjan.githubapp.base.CoroutineDispatcherProvider
4 | import kotlinx.coroutines.CoroutineDispatcher
5 | import kotlinx.coroutines.ExperimentalCoroutinesApi
6 | import kotlinx.coroutines.test.UnconfinedTestDispatcher
7 | import javax.inject.Inject
8 |
9 | @ExperimentalCoroutinesApi
10 | class TestCoroutineDispatcherProvider @Inject constructor() : CoroutineDispatcherProvider {
11 | val testDispatcher = UnconfinedTestDispatcher()
12 |
13 | override fun IO(): CoroutineDispatcher = testDispatcher
14 |
15 | override fun Main(): CoroutineDispatcher = testDispatcher
16 | }
--------------------------------------------------------------------------------
/app/src/test/java/dn/marjan/githubapp/di/modules/AppModuleTest.kt:
--------------------------------------------------------------------------------
1 | package dn.marjan.githubapp.di.modules
2 |
3 | import android.content.Context
4 | import dagger.Module
5 | import dagger.Provides
6 | import dn.marjan.githubapp.base.CoroutineDispatcherProvider
7 | import dn.marjan.githubapp.di.dispatchers.TestCoroutineDispatcherProvider
8 | import io.mockk.mockk
9 | import kotlinx.coroutines.ExperimentalCoroutinesApi
10 | import javax.inject.Singleton
11 |
12 | @Module(
13 | includes = [
14 | DataModuleTest::class,
15 | RepositoryModuleTest::class,
16 | NetworkModule::class,
17 | ViewModelModule::class,
18 | ActivityModule::class
19 | ]
20 | )
21 | class AppModuleTest {
22 | @Singleton
23 | @Provides
24 | fun provideContext(application: android.app.Application):Context = mockk()
25 |
26 |
27 | @ExperimentalCoroutinesApi
28 | @Singleton
29 | @Provides
30 | fun provideDispatchers(dispatcher: TestCoroutineDispatcherProvider): CoroutineDispatcherProvider =
31 | dispatcher
32 |
33 | }
--------------------------------------------------------------------------------
/app/src/test/java/dn/marjan/githubapp/di/modules/DataModuleTest.kt:
--------------------------------------------------------------------------------
1 | package dn.marjan.githubapp.di.modules
2 |
3 | import android.content.Context
4 | import dagger.Module
5 | import dagger.Provides
6 | import dn.marjan.githubapp.data.local.LocalDataService
7 | import dn.marjan.githubapp.data.local.LocalDataServiceImp
8 | import dn.marjan.githubapp.data.remote.RemoteDataService
9 | import dn.marjan.githubapp.server.TaskService
10 | import io.mockk.mockk
11 | import io.mockk.spyk
12 | import javax.inject.Singleton
13 |
14 | @Module
15 | object DataModuleTest {
16 |
17 | @Singleton
18 | @Provides
19 | fun provideRemoteDataService(taskService: TaskService): RemoteDataService = mockk()
20 |
21 |
22 | @Singleton
23 | @Provides
24 | fun provideLocalDataService(): LocalDataService = mockk( )
25 |
26 | }
--------------------------------------------------------------------------------
/app/src/test/java/dn/marjan/githubapp/di/modules/RepositoryModuleTest.kt:
--------------------------------------------------------------------------------
1 | package dn.marjan.githubapp.di.modules
2 |
3 | import dagger.Module
4 | import dagger.Provides
5 | import dn.marjan.githubapp.data.local.LocalDataService
6 | import dn.marjan.githubapp.data.remote.RemoteDataService
7 | import dn.marjan.githubapp.ui.home.repo.HomeRepository
8 | import dn.marjan.githubapp.ui.login.repo.LoginRepository
9 | import dn.marjan.githubapp.ui.profile.repo.ProfileRepository
10 | import dn.marjan.githubapp.ui.repository.repo.RepoRepository
11 | import dn.marjan.githubapp.ui.splash.repo.SplashRepository
12 | import dn.marjan.githubapp.ui.splash.repo.SplashRepositoryImp
13 | import io.mockk.mockk
14 | import io.mockk.spyk
15 | import javax.inject.Singleton
16 |
17 | @Module
18 | class RepositoryModuleTest {
19 |
20 | @Singleton
21 | @Provides
22 | fun provideProfileRepository(
23 | localDataService: LocalDataService,
24 | ): ProfileRepository = mockk()
25 |
26 | @Singleton
27 | @Provides
28 | fun provideLoginRepository(
29 | localDataService: LocalDataService,
30 | remoteDataService: RemoteDataService
31 | ): LoginRepository = mockk()
32 |
33 | @Singleton
34 | @Provides
35 | fun provideHomeRepository(
36 | remoteDataService: RemoteDataService ,
37 | localDataService: LocalDataService
38 | ): HomeRepository = mockk()
39 |
40 |
41 | @Singleton
42 | @Provides
43 | fun provideRepoRepository(
44 | remoteDataService: RemoteDataService ,
45 | localDataService: LocalDataService
46 | ): RepoRepository = mockk()
47 |
48 | @Singleton
49 | @Provides
50 | fun provideSplashRepository(
51 | localDataService: LocalDataService
52 | ): SplashRepository = mockk( )
53 | }
--------------------------------------------------------------------------------
/app/src/test/java/dn/marjan/githubapp/ui/home/HomeRepositoryTest.kt:
--------------------------------------------------------------------------------
1 | package dn.marjan.githubapp.ui.home
2 |
3 | import android.content.Context
4 | import androidx.arch.core.executor.testing.InstantTaskExecutorRule
5 | import dn.marjan.githubapp.ApplicationTest
6 | import dn.marjan.githubapp.data.local.LocalDataService
7 | import dn.marjan.githubapp.di.component.DaggerTestAppComponent
8 | import dn.marjan.githubapp.di.component.TestAppComponent
9 | import dn.marjan.githubapp.ui.home.repo.HomeRepository
10 | import io.mockk.coEvery
11 | import org.junit.Assert.assertEquals
12 | import org.junit.Before
13 | import org.junit.Rule
14 | import org.junit.Test
15 | import javax.inject.Inject
16 |
17 | class HomeRepositoryTest {
18 |
19 | /*
20 | 1- Test that username gave successfully
21 | */
22 |
23 | @get:Rule
24 | val instantTaskExecutorRule= InstantTaskExecutorRule()
25 |
26 | @Inject
27 | lateinit var repository: HomeRepository
28 |
29 | @Inject
30 | lateinit var localDataService: LocalDataService
31 |
32 | @Inject
33 | lateinit var context: Context
34 |
35 | @Before
36 | fun setup(){
37 | val component: TestAppComponent = DaggerTestAppComponent.builder().application(ApplicationTest()).build()
38 | component.inject(this)
39 | }
40 |
41 | @Test
42 | fun `getUsername, getTrueUsername`(){
43 | // Given
44 | val fakeUsername = "some.username"
45 | coEvery { localDataService.getUsername() } returns fakeUsername
46 | coEvery { repository.getUsername() } returns fakeUsername
47 |
48 | // When
49 | val username = repository.getUsername()
50 |
51 | // Then
52 | assertEquals(fakeUsername , username)
53 | }
54 | }
--------------------------------------------------------------------------------
/app/src/test/java/dn/marjan/githubapp/ui/home/HomeViewModelTest.kt:
--------------------------------------------------------------------------------
1 | package dn.marjan.githubapp.ui.home
2 |
3 | import androidx.arch.core.executor.testing.InstantTaskExecutorRule
4 | import androidx.lifecycle.Observer
5 | import dn.marjan.githubapp.ApplicationTest
6 | import dn.marjan.githubapp.data.Error
7 | import dn.marjan.githubapp.data.ErrorResource
8 | import dn.marjan.githubapp.data.Resource
9 | import dn.marjan.githubapp.data.SuccessResource
10 | import dn.marjan.githubapp.di.component.DaggerTestAppComponent
11 | import dn.marjan.githubapp.di.component.TestAppComponent
12 | import dn.marjan.githubapp.entity.ReceivedEvents
13 | import dn.marjan.githubapp.ui.home.repo.HomeRepository
14 | import dn.marjan.githubapp.ui.home.ui.HomeViewModel
15 | import io.mockk.coEvery
16 | import io.mockk.impl.annotations.MockK
17 | import io.mockk.mockk
18 | import okhttp3.MediaType
19 | import okhttp3.ResponseBody
20 | import org.junit.Assert
21 | import org.junit.Before
22 | import org.junit.Rule
23 | import org.junit.Test
24 | import retrofit2.HttpException
25 | import retrofit2.Response
26 | import javax.inject.Inject
27 |
28 |
29 | class HomeViewModelTest {
30 |
31 | /*
32 | 1- Test for get success events response
33 | 2- Test for get failed events response
34 | */
35 |
36 | @get:Rule
37 | var instantExecutorRule = InstantTaskExecutorRule()
38 |
39 | @Inject
40 | lateinit var repository: HomeRepository
41 |
42 | @Inject
43 | lateinit var homeViewModel: HomeViewModel
44 |
45 | @Before
46 | fun setup() {
47 | val component: TestAppComponent =
48 | DaggerTestAppComponent.builder().application(ApplicationTest()).build()
49 | component.inject(this)
50 |
51 | }
52 |
53 | @Test
54 | fun `getReceivedEvents, whenSuccessResponse`() {
55 | // Given
56 | val events = mockk< List>()
57 |
58 | val fakeUsername = "some.username"
59 |
60 | // repository is mockk so we have to use coEvery, not Mockito.when
61 | coEvery { repository.getUsername() } returns fakeUsername
62 | coEvery { repository.getReceivedEvents(fakeUsername) } returns events
63 |
64 | // When
65 | homeViewModel.getReceivedEvents()
66 |
67 | //Then
68 | Assert.assertTrue(homeViewModel.homeResponse.value!! is SuccessResource)
69 | Assert.assertEquals(events , homeViewModel.homeResponse.value!!.data)
70 | Assert.assertEquals(null, homeViewModel.homeResponse.value!!.error)
71 |
72 | // Mockito.verify(homeResponse).onChanged(Mockito.eq(SuccessResource(data = events)))
73 | }
74 |
75 | @Test
76 | fun `getReceivedEvents, whenFailedResponse`() {
77 | // Given
78 | val fakeUsername = "some.username"
79 | val httpException =HttpException(
80 | Response.error(
81 | 500,
82 | ResponseBody.create(MediaType.parse("plain/text"), "{}")
83 | )
84 | )
85 |
86 | coEvery { repository.getUsername() } returns fakeUsername
87 | coEvery { repository.getReceivedEvents(fakeUsername) } throws httpException
88 |
89 | // When
90 | homeViewModel.getReceivedEvents()
91 |
92 | //Then
93 | Assert.assertTrue(homeViewModel.homeResponse.value!! is ErrorResource)
94 | Assert.assertEquals(null , homeViewModel.homeResponse.value!!.data)
95 | Assert.assertEquals(Error.NetworkErrors, homeViewModel.homeResponse.value!!.error)
96 |
97 | // Mockito.verify(homeResponse).onChanged(Mockito.eq(ErrorResource(error = Error.NetworkErrors , message = httpException.message())))
98 |
99 | }
100 | }
--------------------------------------------------------------------------------
/app/src/test/java/dn/marjan/githubapp/ui/login/LoginRepositoryTest.kt:
--------------------------------------------------------------------------------
1 | package dn.marjan.githubapp.ui.login
2 |
3 | import android.content.Context
4 | import android.content.SharedPreferences
5 | import androidx.arch.core.executor.testing.InstantTaskExecutorRule
6 | import androidx.preference.PreferenceManager
7 | import dn.marjan.githubapp.ApplicationTest
8 | import dn.marjan.githubapp.data.local.LocalDataServiceImp
9 | import dn.marjan.githubapp.data.remote.RemoteDataService
10 | import dn.marjan.githubapp.di.component.DaggerTestAppComponent
11 | import dn.marjan.githubapp.di.component.TestAppComponent
12 | import dn.marjan.githubapp.entity.UserInfo
13 | import dn.marjan.githubapp.ui.login.repo.LoginRepositoryImp
14 | import io.mockk.*
15 | import org.junit.Before
16 | import org.junit.Rule
17 | import org.junit.Test
18 | import org.mockito.ArgumentMatchers.anyString
19 | import javax.inject.Inject
20 |
21 |
22 | class LoginRepositoryTest {
23 | @get:Rule
24 | var instantExecutorRule = InstantTaskExecutorRule()
25 |
26 | @Inject
27 | lateinit var remoteDataService: RemoteDataService
28 |
29 | @Before
30 | fun setup() {
31 | val component: TestAppComponent =
32 | DaggerTestAppComponent.builder().application(ApplicationTest()).build()
33 | component.inject(this)
34 | }
35 |
36 |
37 | @Test
38 | fun `saveUserData, should save User info`() {
39 | // Given
40 | val userInfo: UserInfo = UserInfo()
41 | val sharedPreferences: SharedPreferences = mockkClass(SharedPreferences::class)
42 | val sharedPreferencesEditor: SharedPreferences.Editor = mockkClass(SharedPreferences.Editor::class)
43 | val context = mockk()
44 | every { context.packageName } returns "dn.marjan.githubapp"
45 | every { context.getSharedPreferences("dn.marjan.githubapp_preferences" , 0) } returns sharedPreferences
46 |
47 | every { PreferenceManager.getDefaultSharedPreferences(context) } returns sharedPreferences
48 | every { sharedPreferences.edit() } returns sharedPreferencesEditor
49 | justRun { sharedPreferencesEditor.putString(anyString() , anyString()) }
50 |
51 |
52 | // When
53 | val localD = LocalDataServiceImp(context)
54 | val rep = LoginRepositoryImp(remoteDataService, localD)
55 | rep.saveUserData(userInfo)
56 |
57 | // Then
58 | verify(exactly = 10) { sharedPreferencesEditor.putString(any(), any()) }
59 | verify(exactly = 10) { sharedPreferencesEditor.apply() }
60 |
61 | }
62 |
63 |
64 | /* @Test
65 | fun testtest(){
66 | val userInfo = mockk()
67 | val sharedPreferences:SharedPreferences = mockk()
68 | val sharedPreferencesEditor:SharedPreferences.Editor = mockk()
69 | val context = mockk()
70 |
71 | every { context.packageName } returns "somePackageName"
72 | every { context.getSharedPreferences("somePackageName_preferences" , 0) } returns sharedPreferences
73 | every { PreferenceManager.getDefaultSharedPreferences(context) } returns sharedPreferences
74 | every { sharedPreferences.edit() } returns sharedPreferencesEditor
75 | every { userInfo.login } returns "mm"
76 | justRun { sharedPreferencesEditor.putString(anyString() , anyString()) }
77 |
78 |
79 | val localD = LocalDataServiceImp(context)
80 | val rep = LoginRepositoryImp(remoteDataService, localD)
81 | val spyRepo = spyk(rep)
82 |
83 | spyRepo.saveUserData(userInfo)
84 | io.mockk.verify { sharedPreferencesEditor.putString(any(), "mm") }
85 | io.mockk.verify { sharedPreferencesEditor.apply() }
86 |
87 | }*/
88 | }
--------------------------------------------------------------------------------
/app/src/test/java/dn/marjan/githubapp/ui/login/LoginViewModelTest.kt:
--------------------------------------------------------------------------------
1 | package dn.marjan.githubapp.ui.login
2 |
3 | import androidx.arch.core.executor.testing.InstantTaskExecutorRule
4 | import androidx.lifecycle.Observer
5 | import dn.marjan.githubapp.ApplicationTest
6 | import dn.marjan.githubapp.data.Error
7 | import dn.marjan.githubapp.data.ErrorResource
8 | import dn.marjan.githubapp.data.Resource
9 | import dn.marjan.githubapp.data.SuccessResource
10 | import dn.marjan.githubapp.di.component.DaggerTestAppComponent
11 | import dn.marjan.githubapp.di.component.TestAppComponent
12 | import dn.marjan.githubapp.entity.UserInfo
13 | import dn.marjan.githubapp.ui.login.repo.LoginRepository
14 | import dn.marjan.githubapp.ui.login.ui.LoginViewModel
15 | import io.mockk.*
16 | import io.mockk.impl.annotations.MockK
17 | import io.mockk.junit4.MockKRule
18 | import okhttp3.MediaType
19 | import okhttp3.ResponseBody
20 | import org.junit.Assert
21 | import org.junit.Before
22 | import org.junit.Rule
23 | import org.junit.Test
24 | import retrofit2.HttpException
25 | import retrofit2.Response
26 | import javax.inject.Inject
27 |
28 | class LoginViewModelTest {
29 |
30 | /**
31 | You may have noticed @get:Rule. This is a test rule. A test rule is a tool to change
32 | the way tests run, sometimes adding additional checks or running code before and
33 | after your tests. Android Architecture Components uses a background executor that
34 | is asynchronous to do its magic. InstantTaskExecutorRule is a rule that swaps out
35 | that executor and replaces it with synchronous one. This will make sure that, when
36 | you're using LiveData with the ViewModel, it's all run synchronously in the tests.
37 | more information:
38 | https://medium.com/@rlawlgns077/java-lang-runtimeexception-method-getmainlooper-in-android-os-looper-not-mocked-dbfe75ccabca
39 | **/
40 |
41 | @get:Rule
42 | var instantExecutorRule = InstantTaskExecutorRule()
43 |
44 | @Inject
45 | lateinit var loginViewModel: LoginViewModel
46 |
47 | @Inject
48 | lateinit var repository: LoginRepository
49 |
50 | @Before
51 | public fun setup() {
52 | val component: TestAppComponent =
53 | DaggerTestAppComponent.builder().application(ApplicationTest()).build()
54 | component.inject(this)
55 |
56 |
57 | /*
58 | You need a few mocked observers because the Activity will observe LiveData
59 | objects exposed by the ViewModel. In the UI, you'll show a loading view when
60 | retrieving the cocktails from the API and an error view if there's an error
61 | retrieving the cocktails, score updates and questions. Because there's no lifecycle
62 | here, you can use the observeForever() method.
63 | */
64 | }
65 |
66 |
67 | @Test
68 | fun `onSignInButtonClick , empty username and empty password, return EmptyInputError`() {
69 | // Given
70 | val username = ""
71 | val password = ""
72 |
73 | // When
74 | loginViewModel.validateLoginReq(username = username, password = password)
75 |
76 | // Then
77 | Assert.assertTrue(loginViewModel.loginResponse.value!! is ErrorResource)
78 | Assert.assertEquals(null , loginViewModel.loginResponse.value!!.data)
79 | Assert.assertEquals(Error.EmptyInputError, loginViewModel.loginResponse.value!!.error)
80 |
81 | // verify(loginResponse).onChanged(Mockito.eq(ErrorResource(error = Error.EmptyInputError)))
82 | }
83 |
84 | @Test
85 | fun `onSignInButtonClick, empty username and fill password, return EmptyInputError`() {
86 | //Given
87 | val username = ""
88 | val password = "123456"
89 |
90 | // When
91 | loginViewModel.validateLoginReq(username = username, password = password)
92 |
93 | // Then
94 | Assert.assertTrue(loginViewModel.loginResponse.value!! is ErrorResource)
95 | Assert.assertEquals(null , loginViewModel.loginResponse.value!!.data)
96 | Assert.assertEquals(Error.EmptyInputError, loginViewModel.loginResponse.value!!.error)
97 |
98 | // verify(loginResponse).onChanged(Mockito.eq(ErrorResource(error = Error.EmptyInputError)))
99 | }
100 |
101 | @Test
102 | fun `onSignInButtonClick, fill username and empty password, return EmptyInputError`() {
103 | // Given
104 | val username = "some.username"
105 | val password = ""
106 |
107 | // When
108 | loginViewModel.validateLoginReq(username = username, password = password)
109 |
110 | // Then
111 | Assert.assertTrue(loginViewModel.loginResponse.value!! is ErrorResource)
112 | Assert.assertEquals(null , loginViewModel.loginResponse.value!!.data)
113 | Assert.assertEquals(Error.EmptyInputError, loginViewModel.loginResponse.value!!.error)
114 |
115 | // verify(loginResponse).onChanged(Mockito.eq(ErrorResource(error = Error.EmptyInputError)))
116 | }
117 |
118 | @Test
119 | fun `onSignInButtonClick, fill username and fill password, return EmptyInputError`() {
120 | // Given
121 | val username = "some.username"
122 | val password = "123456"
123 |
124 | // When
125 | loginViewModel.validateLoginReq(username = username, password = password)
126 |
127 | // Then TODO: repair all showLoading process as show with pass DataLoading class
128 | // verify { loginViewModel.doLogin(username = username, password = password) }
129 | }
130 |
131 |
132 | /*
133 | If our app has any suspend functions, we have to call it from another suspend function or launch it from a Coroutine. We can mitigate the problem by using the runBlocking()function.
134 | for more information: https://medium.com/swlh/unit-testing-with-kotlin-coroutines-the-android-way-19289838d257#id_token=eyJhbGciOiJSUzI1NiIsImtpZCI6IjE4MmU0NTBhMzVhMjA4MWZhYTFkOWFlMWQyZDc1YTBmMjNkOTFkZjgiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJuYmYiOjE2NDM3MDY4OTUsImF1ZCI6IjIxNjI5NjAzNTgzNC1rMWs2cWUwNjBzMnRwMmEyamFtNGxqZGNtczAwc3R0Zy5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSIsInN1YiI6IjEwODEwMjE3NDc4MDEzMzQzODc0NyIsImVtYWlsIjoibWFyamFuLmRuNzU5NkBnbWFpbC5jb20iLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiYXpwIjoiMjE2Mjk2MDM1ODM0LWsxazZxZTA2MHMydHAyYTJqYW00bGpkY21zMDBzdHRnLmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29tIiwibmFtZSI6Ik1hcmphbiBEbiIsInBpY3R1cmUiOiJodHRwczovL2xoMy5nb29nbGV1c2VyY29udGVudC5jb20vYS9BQVRYQUp6QVhublVpMTFWR2UtdVVFTUY2bHJRdzFkckZZRUhHN1dwZndRPXM5Ni1jIiwiZ2l2ZW5fbmFtZSI6Ik1hcmphbiIsImZhbWlseV9uYW1lIjoiRG4iLCJpYXQiOjE2NDM3MDcxOTUsImV4cCI6MTY0MzcxMDc5NSwianRpIjoiMzA2MzgxMDMyOWI5NjhkYTAyNDUyZDc0NmI5NDMyNmQxY2I2MzJlNyJ9.J--3S7Qet3FEbNOF6fanyxuFygPTAEJC-Yk4cYFo59nLpCWVJbGwPzyE8WWhuefpllkrVmLxv_X26a6YFzhTxr7CWQQYRwQxSkBLjrs6kH0rJVCxsEYYs_OvjMn02zRRfq9s6EW164bK7ikCelF9qYiPh2CQrzG3_tnO7VWOlSt-Msmu_uNfxD_ltlJASHc34Ce28OGBa120mxWAUXxxAdpTngL2Q9ap9__OjNFf8WoJU-lYJoYFQZWXz8JxIMS517RlAN3W_u4Qn4eW3IsMgz-yyUdzzA6SF69L7EBM6SsFAzVTgfibs5GXJcVgUpxJUz0W7WIEY7l3VPFekCZtUA
135 | */
136 | @Test
137 | fun `onSignInButtonClick, whenSuccessResponse`() {
138 | // Given
139 | val userInfo = mockk()
140 |
141 | coEvery { repository.loginReq("", "") } returns userInfo
142 | coJustRun { repository.saveUserData(userInfo) }
143 |
144 | // When
145 | loginViewModel.doLogin("", "")
146 |
147 |
148 | // Then
149 | coVerifyOrder {
150 | repository.loginReq("", "")
151 | repository.saveUserData(userInfo)
152 | }
153 |
154 |
155 | Assert.assertTrue(loginViewModel.loginResponse.value!! is SuccessResource)
156 | Assert.assertEquals(userInfo , loginViewModel.loginResponse.value!!.data)
157 | Assert.assertEquals(null, loginViewModel.loginResponse.value!!.error)
158 |
159 | // verify(loginResponse).onChanged(eq(SuccessResource(data = userInfo)))
160 | }
161 |
162 |
163 | fun log(msg: String) = println("[${Thread.currentThread().name}] $msg")
164 |
165 |
166 | @Test
167 | fun `onSignInButtonClick, whenFailedResponse`() {
168 |
169 | // Given
170 | val error: HttpException = HttpException(
171 | Response.error(
172 | 500, ResponseBody.create(
173 | MediaType.parse("plain/text"), "{\"message\":\"testMessage\"}"
174 | )
175 | )
176 | )
177 |
178 | coEvery { repository.loginReq("", "") } throws error
179 |
180 | // When
181 | loginViewModel.doLogin("", "")
182 |
183 | // Then
184 | Assert.assertTrue(loginViewModel.loginResponse.value!! is ErrorResource)
185 | Assert.assertEquals(null , loginViewModel.loginResponse.value!!.data)
186 | Assert.assertEquals(Error.NetworkErrors, loginViewModel.loginResponse.value!!.error)
187 |
188 | /* verify(loginResponse).onChanged(
189 | ErrorResource(
190 | error = Error.NetworkErrors,
191 | message = "testMessage"
192 | )
193 | )*/
194 |
195 |
196 | }
197 | }
--------------------------------------------------------------------------------
/app/src/test/java/dn/marjan/githubapp/ui/repository/RepositoryViewModelTest.kt:
--------------------------------------------------------------------------------
1 | package dn.marjan.githubapp.ui.repository
2 |
3 | import androidx.arch.core.executor.testing.InstantTaskExecutorRule
4 | import androidx.lifecycle.Observer
5 | import dn.marjan.githubapp.ApplicationTest
6 | import dn.marjan.githubapp.data.Error
7 | import dn.marjan.githubapp.data.ErrorResource
8 | import dn.marjan.githubapp.data.Resource
9 | import dn.marjan.githubapp.data.SuccessResource
10 | import dn.marjan.githubapp.di.component.DaggerTestAppComponent
11 | import dn.marjan.githubapp.di.component.TestAppComponent
12 | import dn.marjan.githubapp.entity.Repository
13 | import dn.marjan.githubapp.ui.repository.repo.RepoRepository
14 | import dn.marjan.githubapp.ui.repository.ui.RepositoryViewModel
15 | import io.mockk.coEvery
16 | import io.mockk.every
17 | import io.mockk.impl.annotations.MockK
18 | import io.mockk.junit4.MockKRule
19 | import io.mockk.justRun
20 | import io.mockk.mockk
21 | import okhttp3.MediaType
22 | import okhttp3.ResponseBody
23 | import org.junit.Assert
24 | import org.junit.Before
25 | import org.junit.Rule
26 | import org.junit.Test
27 | import retrofit2.HttpException
28 | import retrofit2.Response
29 | import javax.inject.Inject
30 |
31 | class RepositoryViewModelTest {
32 |
33 | /*
34 | 1- Test success response from fetch repositories
35 | 2- Test failed response from fetch repositories
36 | */
37 |
38 | @get:Rule
39 | val instantTaskExecutorRule= InstantTaskExecutorRule()
40 |
41 | @Inject
42 | lateinit var repoViewModel: RepositoryViewModel
43 |
44 | @Inject
45 | lateinit var repoRepository: RepoRepository
46 |
47 | @Before
48 | fun setup() {
49 | val component: TestAppComponent =
50 | DaggerTestAppComponent.builder().application(ApplicationTest()).build()
51 | component.inject(this)
52 | // repoViewModel.repoResponse.observeForever(repoResponse)
53 | }
54 |
55 | @Test
56 | fun `onGetRepositories, SuccessResponse`(){
57 | // Given
58 | val fakeUsername = "fake.username"
59 | val reposList = mockk>()
60 | every { repoRepository.getUsername() } returns fakeUsername
61 | coEvery { repoRepository.getRepositories(fakeUsername) } returns reposList
62 |
63 | // When
64 | repoViewModel.getRepositories()
65 |
66 | // Then
67 | Assert.assertTrue(repoViewModel.repoResponse.value is SuccessResource)
68 | Assert.assertEquals(null , repoViewModel.repoResponse.value!!.error)
69 | Assert.assertEquals(reposList , repoViewModel.repoResponse.value!!.data)
70 | }
71 |
72 | @Test
73 | fun `onGetRepositories, FailedResponse`() {
74 | // Given
75 | val fakeUsername = "fake.username"
76 | every { repoRepository.getUsername() } returns fakeUsername
77 | coEvery { repoRepository.getRepositories(fakeUsername) } throws HttpException(
78 | Response.error(
79 | 500,
80 | ResponseBody.create(MediaType.parse("plain/text"), "{}")
81 | )
82 | )
83 |
84 | // When
85 | repoViewModel.getRepositories()
86 |
87 | // Then
88 | Assert.assertTrue(repoViewModel.repoResponse.value is ErrorResource)
89 | Assert.assertEquals(null , repoViewModel.repoResponse.value!!.data)
90 | Assert.assertEquals(Error.NetworkErrors , repoViewModel.repoResponse.value!!.error)
91 | }
92 |
93 | }
--------------------------------------------------------------------------------
/app/src/test/java/dn/marjan/githubapp/ui/splash/SplashActivityTest.kt:
--------------------------------------------------------------------------------
1 | package dn.marjan.githubapp.ui.splash
2 |
3 | import org.junit.Test
4 | import org.junit.runner.RunWith
5 | import org.robolectric.RobolectricTestRunner
6 |
7 | //@RunWith(::class)
8 | class SplashActivityTest {
9 |
10 | @Test
11 | fun `launch corresponding activity according to user login status`(){
12 |
13 | }
14 | }
--------------------------------------------------------------------------------
/app/src/test/java/dn/marjan/githubapp/ui/splash/SplashRepositoryTest.kt:
--------------------------------------------------------------------------------
1 | package dn.marjan.githubapp.ui.splash
2 |
3 | import dn.marjan.githubapp.ApplicationTest
4 | import dn.marjan.githubapp.data.local.LocalDataService
5 | import dn.marjan.githubapp.di.component.DaggerTestAppComponent
6 | import dn.marjan.githubapp.di.component.TestAppComponent
7 | import dn.marjan.githubapp.ui.splash.repo.SplashRepository
8 | import io.mockk.coEvery
9 | import io.mockk.coJustRun
10 | import org.junit.Assert.assertTrue
11 | import org.junit.Before
12 | import org.junit.Test
13 | import javax.inject.Inject
14 |
15 | class SplashRepositoryTest {
16 |
17 | @Inject
18 | lateinit var repository: SplashRepository
19 |
20 | @Inject
21 | lateinit var localDataService: LocalDataService
22 |
23 | @Before
24 | fun setup(){
25 | val component: TestAppComponent = DaggerTestAppComponent.builder().application(ApplicationTest()).build()
26 | component.inject(this)
27 |
28 | }
29 |
30 | @Test
31 | fun `checkUserLogin, whenTrueResponse`(){
32 | // Given
33 | coEvery { localDataService.isUserLogin() } returns true
34 | coEvery { repository.checkUserLogin() } returns true
35 |
36 | // When
37 | val isUserLogin = repository.checkUserLogin()
38 |
39 | // Then
40 | assertTrue(isUserLogin)
41 | }
42 |
43 | }
--------------------------------------------------------------------------------
/app/src/test/java/dn/marjan/githubapp/ui/splash/SplashViewModelTest.kt:
--------------------------------------------------------------------------------
1 | package dn.marjan.githubapp.ui.splash
2 |
3 | import androidx.arch.core.executor.testing.InstantTaskExecutorRule
4 | import androidx.lifecycle.Observer
5 | import dn.marjan.githubapp.ApplicationTest
6 | import dn.marjan.githubapp.data.Error
7 | import dn.marjan.githubapp.data.ErrorResource
8 | import dn.marjan.githubapp.data.Resource
9 | import dn.marjan.githubapp.data.SuccessResource
10 | import dn.marjan.githubapp.data.local.LocalDataService
11 | import dn.marjan.githubapp.di.component.DaggerTestAppComponent
12 | import dn.marjan.githubapp.di.component.TestAppComponent
13 | import dn.marjan.githubapp.ui.splash.repo.SplashRepository
14 | import dn.marjan.githubapp.ui.splash.ui.SplashViewModel
15 | import io.mockk.*
16 | import io.mockk.impl.annotations.MockK
17 | import io.mockk.junit4.MockKRule
18 | import org.junit.Assert
19 | import org.junit.Before
20 | import org.junit.Rule
21 | import org.junit.Test
22 | import org.junit.runner.RunWith
23 | import javax.inject.Inject
24 |
25 | class SplashViewModelTest {
26 |
27 | @get:Rule
28 | val instantTaskExecutorRule = InstantTaskExecutorRule()
29 |
30 | @Inject
31 | lateinit var splashViewmodel: SplashViewModel
32 |
33 | @Inject
34 | lateinit var repository: SplashRepository
35 |
36 | @Before
37 | fun setup() {
38 | val component: TestAppComponent =
39 | DaggerTestAppComponent.builder().application(ApplicationTest()).build()
40 | component.inject(this)
41 | }
42 |
43 | @Test
44 | fun `checkUserLogin, whenTrueResponse`() {
45 | // Given
46 | every { repository.checkUserLogin( ) } returns true
47 |
48 | // When
49 | splashViewmodel.checkUserLogin()
50 |
51 | // Then
52 | Assert.assertTrue(splashViewmodel.splashResponse.value!! is SuccessResource)
53 | Assert.assertEquals(true , splashViewmodel.splashResponse.value!!.data)
54 | Assert.assertEquals(null, splashViewmodel.splashResponse.value!!.error)
55 |
56 | // Mockito.verify(splashResponse).onChanged(Mockito.eq(SuccessResource(data = true)))
57 | }
58 |
59 | @Test
60 | fun `checkUserLogin, whenFalseResponse`() {
61 | // Given
62 | every { repository.checkUserLogin() } returns false
63 |
64 | // When
65 | splashViewmodel.checkUserLogin()
66 |
67 | // Then
68 | Assert.assertTrue(splashViewmodel.splashResponse.value!! is ErrorResource)
69 | Assert.assertEquals(null , splashViewmodel.splashResponse.value!!.data)
70 | Assert.assertEquals(Error.EmptyResultError, splashViewmodel.splashResponse.value!!.error)
71 |
72 | // Mockito.verify(splashResponse).onChanged(Mockito.eq(ErrorResource(error = Error.EmptyResultError)))
73 | }
74 | }
--------------------------------------------------------------------------------
/app/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker:
--------------------------------------------------------------------------------
1 | mock-maker-inline
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | buildscript {
3 | ext {
4 | kotlinVersion = '1.6.10'
5 | lifecycleVersion = '2.4.1'
6 | composeVersion = '1.1.1'
7 | daggerVersion = '2.40.5'
8 | jUnitVersion = '4.13.2'
9 | mockkVersion = '1.12.4'
10 | androidXTestVersion = '1.4.0'
11 | }
12 | repositories {
13 | google()
14 | mavenCentral()
15 | }
16 | dependencies {
17 | classpath 'com.android.tools.build:gradle:7.2.0'
18 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
19 | }
20 | }
21 |
22 | task clean(type: Delete) {
23 | delete rootProject.buildDir
24 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app"s APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Automatically convert third-party libraries to use AndroidX
19 | android.enableJetifier=true
20 | # Kotlin code style for this project: "official" or "obsolete":
21 | kotlin.code.style=official
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marjandn/GitHub/4ea5a59c4d5e38ad7b2c0c1dd0ba4e19693b8186/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sun Jan 16 09:39:12 IRST 2022
2 | distributionBase=GRADLE_USER_HOME
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
4 | distributionPath=wrapper/dists
5 | zipStorePath=wrapper/dists
6 | zipStoreBase=GRADLE_USER_HOME
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
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 | # https://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 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | dependencyResolutionManagement {
2 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
3 | repositories {
4 | google()
5 | mavenCentral()
6 | jcenter() // Warning: this repository is going to shut down soon
7 | }
8 | }
9 | rootProject.name = "GithubApp"
10 | include ':app'
11 |
--------------------------------------------------------------------------------