├── .gitignore
├── AndroidArchitectureStudy
├── .gitignore
├── .idea
│ ├── codeStyles
│ │ ├── Project.xml
│ │ └── codeStyleConfig.xml
│ ├── jarRepositories.xml
│ ├── runConfigurations.xml
│ ├── sonarIssues.xml
│ └── sonarlint
│ │ └── issuestore
│ │ ├── 2
│ │ └── 5
│ │ │ └── 252852c50f0155c32283ca0c6d8c22374a1fdb5f
│ │ ├── 4
│ │ └── 6
│ │ │ └── 4649a81fe7d1f6b0a7facdac49d4f4baca4204c3
│ │ ├── 6
│ │ ├── 1
│ │ │ └── 61cf1b62acdc3845092bb22a12b6311aff6ad50f
│ │ ├── 8
│ │ │ └── 686f2cda820f4c5a20385115f1a8df6eaafde5ce
│ │ ├── c
│ │ │ └── 6c3f1c2b32030ec0610286578b94e4e62276493a
│ │ └── f
│ │ │ └── 6f529ca7be39fc8caa24179e329ac4c7b4fe1e19
│ │ ├── 7
│ │ └── 2
│ │ │ └── 72c51fb67f3ba037d4479250d6dd628285a787f0
│ │ ├── 8
│ │ ├── 2
│ │ │ └── 825d413a42a1f622cf0e4609b6fd821c79380c69
│ │ ├── 5
│ │ │ ├── 853fd1ab93e95b4e50c5afc6c6ebd37a88b06d05
│ │ │ └── 85cd6537bdb88268af2e614f48408421aa6b335a
│ │ └── c
│ │ │ └── 8c55c3ccc257e5907959013f99656e4c8ec3903e
│ │ ├── 9
│ │ └── 9
│ │ │ └── 9918a437a5b07f8c88076691082986dbb88c79e6
│ │ ├── a
│ │ ├── 0
│ │ │ └── a05f861f6e0f6c67ecd77f8a0506a962b92685c1
│ │ ├── 4
│ │ │ └── a4e71cd8bf0c8f03f9d7165ba6cff432752851fc
│ │ ├── 8
│ │ │ └── a844d715abf30b4b4211722cb76395e226494799
│ │ └── f
│ │ │ └── af0f8f93e036ecde9c6f9c98e03cb54cccbd2872
│ │ ├── b
│ │ └── 7
│ │ │ └── b7958cd2f38d62d942383c11f4dce821b63bee09
│ │ ├── c
│ │ ├── 5
│ │ │ └── c5d82e44e2f9a7a842f813e8e6dff73efd023bd9
│ │ └── 8
│ │ │ └── c88611dee963e3f7d7dca2ed131a0cf7dd7449f5
│ │ ├── d
│ │ └── 4
│ │ │ └── d4a7ece69a89f886abfffc86f9f7795034fbc454
│ │ ├── e
│ │ └── b
│ │ │ └── ebf01a6289185e2423d865b70aef99936307a89d
│ │ ├── f
│ │ ├── a
│ │ │ └── fa883d7d4d9f89a180ef9ef945d2ed8c80104b1d
│ │ └── f
│ │ │ └── ff215de2a3e6ed6e1aa0a57678b450da9c5c3f7a
│ │ └── index.pb
├── build.gradle
├── data
│ ├── .gitignore
│ ├── build.gradle
│ └── src
│ │ └── main
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── com
│ │ └── mtjin
│ │ └── data
│ │ ├── api
│ │ ├── ApiClient.kt
│ │ └── ApiInterface.kt
│ │ ├── db
│ │ ├── MovieDao.kt
│ │ └── MovieDatabase.kt
│ │ ├── di
│ │ ├── ApiModule.kt
│ │ ├── LocalDataModule.kt
│ │ ├── RemoteDataModule.kt
│ │ └── RepositoryModule.kt
│ │ ├── mapper
│ │ └── Mapper.kt
│ │ ├── model
│ │ └── search
│ │ │ ├── MovieEntity.kt
│ │ │ └── MovieResponse.kt
│ │ ├── repository
│ │ ├── login
│ │ │ ├── LoginRepositoryImpl.kt
│ │ │ └── local
│ │ │ │ ├── LoginLocalDataSource.kt
│ │ │ │ └── LoginLocalDataSourceImpl.kt
│ │ └── search
│ │ │ ├── MovieRepositoryImpl.kt
│ │ │ ├── local
│ │ │ ├── MovieLocalDataSource.kt
│ │ │ └── MovieLocalDataSourceImpl.kt
│ │ │ └── remote
│ │ │ ├── MovieRemoteDataSource.kt
│ │ │ └── MovieRemoteDataSourceImpl.kt
│ │ └── utils
│ │ ├── Constatnts.kt
│ │ └── PreferenceManager.kt
├── domain
│ ├── .gitignore
│ ├── build.gradle
│ └── src
│ │ └── main
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── com
│ │ └── mtjin
│ │ └── domain
│ │ ├── di
│ │ └── UseCaseModule.kt
│ │ ├── model
│ │ └── search
│ │ │ └── Movie.kt
│ │ ├── repository
│ │ ├── LoginRepository.kt
│ │ └── MovieRepository.kt
│ │ └── usecase
│ │ ├── GetLocalMoviesUseCase.kt
│ │ ├── GetLoginUseCase.kt
│ │ ├── GetMoviesUseCase.kt
│ │ ├── GetPagingMoviesUseCase.kt
│ │ └── InsertLoginUseCase.kt
├── gradle.properties
├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── presentation
│ ├── .gitignore
│ ├── build.gradle
│ ├── proguard-rules.pro
│ └── src
│ │ ├── androidTest
│ │ └── java
│ │ │ └── com
│ │ │ └── mtjin
│ │ │ └── androidarchitecturestudy
│ │ │ └── ExampleInstrumentedTest.kt
│ │ ├── main
│ │ ├── AndroidManifest.xml
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── mtjin
│ │ │ │ └── presentation
│ │ │ │ ├── ViewDataBindingAdapters.kt
│ │ │ │ ├── base
│ │ │ │ ├── BaseActivity.kt
│ │ │ │ └── BaseViewModel.kt
│ │ │ │ ├── di
│ │ │ │ └── MyApplication.kt
│ │ │ │ ├── module
│ │ │ │ └── NetworkModule.kt
│ │ │ │ ├── utils
│ │ │ │ ├── EndlessRecyclerViewScrollListener.kt
│ │ │ │ └── NetworkManager.kt
│ │ │ │ └── views
│ │ │ │ ├── login
│ │ │ │ ├── LoginActivity.kt
│ │ │ │ └── LoginViewModel.kt
│ │ │ │ ├── search
│ │ │ │ ├── MovieAdapter.kt
│ │ │ │ ├── MovieSearchActivity.kt
│ │ │ │ └── MovieSearchViewModel.kt
│ │ │ │ └── splash
│ │ │ │ ├── SplashActivity.kt
│ │ │ │ └── SplashViewModel.kt
│ │ └── res
│ │ │ ├── drawable-v24
│ │ │ ├── bg_green_gradation.xml
│ │ │ ├── bg_splash.xml
│ │ │ └── ic_launcher_foreground.xml
│ │ │ ├── drawable
│ │ │ ├── ic_app_logo.png
│ │ │ ├── ic_default.png
│ │ │ └── ic_launcher_background.xml
│ │ │ ├── layout
│ │ │ ├── activity_login.xml
│ │ │ ├── activity_movie_search.xml
│ │ │ └── item_movie.xml
│ │ │ ├── mipmap-anydpi-v26
│ │ │ ├── ic_launcher.xml
│ │ │ └── ic_launcher_round.xml
│ │ │ ├── mipmap-hdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-mdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ │ └── values
│ │ │ ├── colors.xml
│ │ │ ├── strings.xml
│ │ │ └── styles.xml
│ │ └── test
│ │ └── java
│ │ └── com
│ │ └── mtjin
│ │ └── androidarchitecturestudy
│ │ └── ExampleUnitTest.kt
└── settings.gradle
├── LICENSE
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | # Built application files
2 | *.apk
3 | *.aar
4 | *.ap_
5 | *.aab
6 |
7 | # Files for the ART/Dalvik VM
8 | *.dex
9 |
10 | # Java class files
11 | *.class
12 |
13 | # Generated files
14 | bin/
15 | gen/
16 | out/
17 | # Uncomment the following line in case you need and you don't have the release build type files in your app
18 | # release/
19 |
20 | # Gradle files
21 | .gradle/
22 | build/
23 |
24 | # Local configuration file (sdk path, etc)
25 | local.properties
26 |
27 | # Proguard folder generated by Eclipse
28 | proguard/
29 |
30 | # Log Files
31 | *.log
32 |
33 | # Android Studio Navigation editor temp files
34 | .navigation/
35 |
36 | # Android Studio captures folder
37 | captures/
38 |
39 | # IntelliJ
40 | *.iml
41 | .idea/workspace.xml
42 | .idea/tasks.xml
43 | .idea/gradle.xml
44 | .idea/assetWizardSettings.xml
45 | .idea/dictionaries
46 | .idea/libraries
47 | # Android Studio 3 in .gitignore file.
48 | .idea/caches
49 | .idea/modules.xml
50 | # Comment next line if keeping position of elements in Navigation Editor is relevant for you
51 | .idea/navEditor.xml
52 |
53 | # Keystore files
54 | # Uncomment the following lines if you do not want to check your keystore files in.
55 | #*.jks
56 | #*.keystore
57 |
58 | # External native build folder generated in Android Studio 2.2 and later
59 | .externalNativeBuild
60 | .cxx/
61 |
62 | # Google Services (e.g. APIs or Firebase)
63 | # google-services.json
64 |
65 | # Freeline
66 | freeline.py
67 | freeline/
68 | freeline_project_description.json
69 |
70 | # fastlane
71 | fastlane/report.xml
72 | fastlane/Preview.html
73 | fastlane/screenshots
74 | fastlane/test_output
75 | fastlane/readme.md
76 |
77 | # Version control
78 | vcs.xml
79 |
80 | # lint
81 | lint/intermediates/
82 | lint/generated/
83 | lint/outputs/
84 | lint/tmp/
85 | # lint/reports/
86 |
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Created by https://www.gitignore.io/api/kotlin,android,androidstudio
3 | # Edit at https://www.gitignore.io/?templates=kotlin,android,androidstudio
4 |
5 | ### Android ###
6 | # Built application files
7 | *.apk
8 | *.ap_
9 | *.aab
10 |
11 | # Files for the ART/Dalvik VM
12 | *.dex
13 |
14 | # Java class files
15 | *.class
16 |
17 | # Generated files
18 | bin/
19 | gen/
20 | out/
21 | release/
22 |
23 | # Gradle files
24 | .gradle/
25 | build/
26 |
27 | # Local configuration file (sdk path, etc)
28 | local.properties
29 |
30 | # Proguard folder generated by Eclipse
31 | proguard/
32 |
33 | # Log Files
34 | *.log
35 |
36 | # Android Studio Navigation editor temp files
37 | .navigation/
38 |
39 | # Android Studio captures folder
40 | captures/
41 |
42 | # IntelliJ
43 | *.iml
44 | .idea/workspace.xml
45 | .idea/tasks.xml
46 | .idea/gradle.xml
47 | .idea/assetWizardSettings.xml
48 | .idea/dictionaries
49 | .idea/libraries
50 | # Android Studio 3 in .gitignore file.
51 | .idea/caches
52 | .idea/modules.xml
53 | # Comment next line if keeping position of elements in Navigation Editor is relevant for you
54 | .idea/navEditor.xml
55 |
56 | # Keystore files
57 | # Uncomment the following lines if you do not want to check your keystore files in.
58 | #*.jks
59 | #*.keystore
60 |
61 | # External native build folder generated in Android Studio 2.2 and later
62 | .externalNativeBuild
63 |
64 | # Google Services (e.g. APIs or Firebase)
65 | # google-services.json
66 |
67 | # Freeline
68 | freeline.py
69 | freeline/
70 | freeline_project_description.json
71 |
72 | # fastlane
73 | fastlane/report.xml
74 | fastlane/Preview.html
75 | fastlane/screenshots
76 | fastlane/test_output
77 | fastlane/readme.md
78 |
79 | # Version control
80 | vcs.xml
81 |
82 | # lint
83 | lint/intermediates/
84 | lint/generated/
85 | lint/outputs/
86 | lint/tmp/
87 | # lint/reports/
88 |
89 | ### Android Patch ###
90 | gen-external-apklibs
91 | output.json
92 |
93 | # Replacement of .externalNativeBuild directories introduced
94 | # with Android Studio 3.5.
95 | .cxx/
96 |
97 | ### Kotlin ###
98 | # Compiled class file
99 |
100 | # Log file
101 |
102 | # BlueJ files
103 | *.ctxt
104 |
105 | # Mobile Tools for Java (J2ME)
106 | .mtj.tmp/
107 |
108 | # Package Files #
109 | *.jar
110 | *.war
111 | *.nar
112 | *.ear
113 | *.zip
114 | *.tar.gz
115 | *.rar
116 |
117 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
118 | hs_err_pid*
119 |
120 | ### AndroidStudio ###
121 | # Covers files to be ignored for android development using Android Studio.
122 |
123 | # Built application files
124 |
125 | # Files for the ART/Dalvik VM
126 |
127 | # Java class files
128 |
129 | # Generated files
130 |
131 | # Gradle files
132 | .gradle
133 |
134 | # Signing files
135 | .signing/
136 |
137 | # Local configuration file (sdk path, etc)
138 |
139 | # Proguard folder generated by Eclipse
140 |
141 | # Log Files
142 |
143 | # Android Studio
144 | /*/build/
145 | /*/local.properties
146 | /*/out
147 | /*/*/build
148 | /*/*/production
149 | *.ipr
150 | *~
151 | *.swp
152 |
153 | # Android Patch
154 |
155 | # External native build folder generated in Android Studio 2.2 and later
156 |
157 | # NDK
158 | obj/
159 |
160 | # IntelliJ IDEA
161 | *.iws
162 | /out/
163 |
164 | # User-specific configurations
165 | .idea/caches/
166 | .idea/libraries/
167 | .idea/shelf/
168 | .idea/.name
169 | .idea/compiler.xml
170 | .idea/copyright/profiles_settings.xml
171 | .idea/encodings.xml
172 | .idea/misc.xml
173 | .idea/scopes/scope_settings.xml
174 | .idea/vcs.xml
175 | .idea/jsLibraryMappings.xml
176 | .idea/datasources.xml
177 | .idea/dataSources.ids
178 | .idea/sqlDataSources.xml
179 | .idea/dynamic.xml
180 | .idea/uiDesigner.xml
181 |
182 | # OS-specific files
183 | .DS_Store
184 | .DS_Store?
185 | ._*
186 | .Spotlight-V100
187 | .Trashes
188 | ehthumbs.db
189 | Thumbs.db
190 |
191 | # Legacy Eclipse project files
192 | .classpath
193 | .project
194 | .cproject
195 | .settings/
196 |
197 | # Mobile Tools for Java (J2ME)
198 |
199 | # Package Files #
200 |
201 | # virtual machine crash logs (Reference: http://www.java.com/en/download/help/error_hotspot.xml)
202 |
203 | ## Plugin-specific files:
204 |
205 | # mpeltonen/sbt-idea plugin
206 | .idea_modules/
207 |
208 | # JIRA plugin
209 | atlassian-ide-plugin.xml
210 |
211 | # Mongo Explorer plugin
212 | .idea/mongoSettings.xml
213 |
214 | # Crashlytics plugin (for Android Studio and IntelliJ)
215 | com_crashlytics_export_strings.xml
216 | crashlytics.properties
217 | crashlytics-build.properties
218 | fabric.properties
219 |
220 | ### AndroidStudio Patch ###
221 |
222 | !/gradle/wrapper/gradle-wrapper.jar
223 |
224 | # End of https://www.gitignore.io/api/kotlin,android,androidstudio
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | xmlns:android
33 |
34 | ^$
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 | xmlns:.*
44 |
45 | ^$
46 |
47 |
48 | BY_NAME
49 |
50 |
51 |
52 |
53 |
54 |
55 | .*:id
56 |
57 | http://schemas.android.com/apk/res/android
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 | .*:name
67 |
68 | http://schemas.android.com/apk/res/android
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 | name
78 |
79 | ^$
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 | style
89 |
90 | ^$
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 | .*
100 |
101 | ^$
102 |
103 |
104 | BY_NAME
105 |
106 |
107 |
108 |
109 |
110 |
111 | .*
112 |
113 | http://schemas.android.com/apk/res/android
114 |
115 |
116 | ANDROID_ATTRIBUTE_ORDER
117 |
118 |
119 |
120 |
121 |
122 |
123 | .*
124 |
125 | .*
126 |
127 |
128 | BY_NAME
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/.idea/jarRepositories.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/.idea/sonarIssues.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
472 |
473 |
474 |
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/.idea/sonarlint/issuestore/2/5/252852c50f0155c32283ca0c6d8c22374a1fdb5f:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mtjin/mtjin-android-clean-architecture-movieapp/edc3e6aad017063bf00ef17126eb9aae7100bdb8/AndroidArchitectureStudy/.idea/sonarlint/issuestore/2/5/252852c50f0155c32283ca0c6d8c22374a1fdb5f
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/.idea/sonarlint/issuestore/4/6/4649a81fe7d1f6b0a7facdac49d4f4baca4204c3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mtjin/mtjin-android-clean-architecture-movieapp/edc3e6aad017063bf00ef17126eb9aae7100bdb8/AndroidArchitectureStudy/.idea/sonarlint/issuestore/4/6/4649a81fe7d1f6b0a7facdac49d4f4baca4204c3
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/.idea/sonarlint/issuestore/6/1/61cf1b62acdc3845092bb22a12b6311aff6ad50f:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mtjin/mtjin-android-clean-architecture-movieapp/edc3e6aad017063bf00ef17126eb9aae7100bdb8/AndroidArchitectureStudy/.idea/sonarlint/issuestore/6/1/61cf1b62acdc3845092bb22a12b6311aff6ad50f
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/.idea/sonarlint/issuestore/6/8/686f2cda820f4c5a20385115f1a8df6eaafde5ce:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mtjin/mtjin-android-clean-architecture-movieapp/edc3e6aad017063bf00ef17126eb9aae7100bdb8/AndroidArchitectureStudy/.idea/sonarlint/issuestore/6/8/686f2cda820f4c5a20385115f1a8df6eaafde5ce
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/.idea/sonarlint/issuestore/6/c/6c3f1c2b32030ec0610286578b94e4e62276493a:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mtjin/mtjin-android-clean-architecture-movieapp/edc3e6aad017063bf00ef17126eb9aae7100bdb8/AndroidArchitectureStudy/.idea/sonarlint/issuestore/6/c/6c3f1c2b32030ec0610286578b94e4e62276493a
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/.idea/sonarlint/issuestore/6/f/6f529ca7be39fc8caa24179e329ac4c7b4fe1e19:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mtjin/mtjin-android-clean-architecture-movieapp/edc3e6aad017063bf00ef17126eb9aae7100bdb8/AndroidArchitectureStudy/.idea/sonarlint/issuestore/6/f/6f529ca7be39fc8caa24179e329ac4c7b4fe1e19
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/.idea/sonarlint/issuestore/7/2/72c51fb67f3ba037d4479250d6dd628285a787f0:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mtjin/mtjin-android-clean-architecture-movieapp/edc3e6aad017063bf00ef17126eb9aae7100bdb8/AndroidArchitectureStudy/.idea/sonarlint/issuestore/7/2/72c51fb67f3ba037d4479250d6dd628285a787f0
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/.idea/sonarlint/issuestore/8/2/825d413a42a1f622cf0e4609b6fd821c79380c69:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mtjin/mtjin-android-clean-architecture-movieapp/edc3e6aad017063bf00ef17126eb9aae7100bdb8/AndroidArchitectureStudy/.idea/sonarlint/issuestore/8/2/825d413a42a1f622cf0e4609b6fd821c79380c69
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/.idea/sonarlint/issuestore/8/5/853fd1ab93e95b4e50c5afc6c6ebd37a88b06d05:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mtjin/mtjin-android-clean-architecture-movieapp/edc3e6aad017063bf00ef17126eb9aae7100bdb8/AndroidArchitectureStudy/.idea/sonarlint/issuestore/8/5/853fd1ab93e95b4e50c5afc6c6ebd37a88b06d05
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/.idea/sonarlint/issuestore/8/5/85cd6537bdb88268af2e614f48408421aa6b335a:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mtjin/mtjin-android-clean-architecture-movieapp/edc3e6aad017063bf00ef17126eb9aae7100bdb8/AndroidArchitectureStudy/.idea/sonarlint/issuestore/8/5/85cd6537bdb88268af2e614f48408421aa6b335a
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/.idea/sonarlint/issuestore/8/c/8c55c3ccc257e5907959013f99656e4c8ec3903e:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mtjin/mtjin-android-clean-architecture-movieapp/edc3e6aad017063bf00ef17126eb9aae7100bdb8/AndroidArchitectureStudy/.idea/sonarlint/issuestore/8/c/8c55c3ccc257e5907959013f99656e4c8ec3903e
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/.idea/sonarlint/issuestore/9/9/9918a437a5b07f8c88076691082986dbb88c79e6:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mtjin/mtjin-android-clean-architecture-movieapp/edc3e6aad017063bf00ef17126eb9aae7100bdb8/AndroidArchitectureStudy/.idea/sonarlint/issuestore/9/9/9918a437a5b07f8c88076691082986dbb88c79e6
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/.idea/sonarlint/issuestore/a/0/a05f861f6e0f6c67ecd77f8a0506a962b92685c1:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mtjin/mtjin-android-clean-architecture-movieapp/edc3e6aad017063bf00ef17126eb9aae7100bdb8/AndroidArchitectureStudy/.idea/sonarlint/issuestore/a/0/a05f861f6e0f6c67ecd77f8a0506a962b92685c1
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/.idea/sonarlint/issuestore/a/4/a4e71cd8bf0c8f03f9d7165ba6cff432752851fc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mtjin/mtjin-android-clean-architecture-movieapp/edc3e6aad017063bf00ef17126eb9aae7100bdb8/AndroidArchitectureStudy/.idea/sonarlint/issuestore/a/4/a4e71cd8bf0c8f03f9d7165ba6cff432752851fc
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/.idea/sonarlint/issuestore/a/8/a844d715abf30b4b4211722cb76395e226494799:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mtjin/mtjin-android-clean-architecture-movieapp/edc3e6aad017063bf00ef17126eb9aae7100bdb8/AndroidArchitectureStudy/.idea/sonarlint/issuestore/a/8/a844d715abf30b4b4211722cb76395e226494799
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/.idea/sonarlint/issuestore/a/f/af0f8f93e036ecde9c6f9c98e03cb54cccbd2872:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mtjin/mtjin-android-clean-architecture-movieapp/edc3e6aad017063bf00ef17126eb9aae7100bdb8/AndroidArchitectureStudy/.idea/sonarlint/issuestore/a/f/af0f8f93e036ecde9c6f9c98e03cb54cccbd2872
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/.idea/sonarlint/issuestore/b/7/b7958cd2f38d62d942383c11f4dce821b63bee09:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mtjin/mtjin-android-clean-architecture-movieapp/edc3e6aad017063bf00ef17126eb9aae7100bdb8/AndroidArchitectureStudy/.idea/sonarlint/issuestore/b/7/b7958cd2f38d62d942383c11f4dce821b63bee09
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/.idea/sonarlint/issuestore/c/5/c5d82e44e2f9a7a842f813e8e6dff73efd023bd9:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mtjin/mtjin-android-clean-architecture-movieapp/edc3e6aad017063bf00ef17126eb9aae7100bdb8/AndroidArchitectureStudy/.idea/sonarlint/issuestore/c/5/c5d82e44e2f9a7a842f813e8e6dff73efd023bd9
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/.idea/sonarlint/issuestore/c/8/c88611dee963e3f7d7dca2ed131a0cf7dd7449f5:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mtjin/mtjin-android-clean-architecture-movieapp/edc3e6aad017063bf00ef17126eb9aae7100bdb8/AndroidArchitectureStudy/.idea/sonarlint/issuestore/c/8/c88611dee963e3f7d7dca2ed131a0cf7dd7449f5
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/.idea/sonarlint/issuestore/d/4/d4a7ece69a89f886abfffc86f9f7795034fbc454:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mtjin/mtjin-android-clean-architecture-movieapp/edc3e6aad017063bf00ef17126eb9aae7100bdb8/AndroidArchitectureStudy/.idea/sonarlint/issuestore/d/4/d4a7ece69a89f886abfffc86f9f7795034fbc454
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/.idea/sonarlint/issuestore/e/b/ebf01a6289185e2423d865b70aef99936307a89d:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mtjin/mtjin-android-clean-architecture-movieapp/edc3e6aad017063bf00ef17126eb9aae7100bdb8/AndroidArchitectureStudy/.idea/sonarlint/issuestore/e/b/ebf01a6289185e2423d865b70aef99936307a89d
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/.idea/sonarlint/issuestore/f/a/fa883d7d4d9f89a180ef9ef945d2ed8c80104b1d:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mtjin/mtjin-android-clean-architecture-movieapp/edc3e6aad017063bf00ef17126eb9aae7100bdb8/AndroidArchitectureStudy/.idea/sonarlint/issuestore/f/a/fa883d7d4d9f89a180ef9ef945d2ed8c80104b1d
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/.idea/sonarlint/issuestore/f/f/ff215de2a3e6ed6e1aa0a57678b450da9c5c3f7a:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mtjin/mtjin-android-clean-architecture-movieapp/edc3e6aad017063bf00ef17126eb9aae7100bdb8/AndroidArchitectureStudy/.idea/sonarlint/issuestore/f/f/ff215de2a3e6ed6e1aa0a57678b450da9c5c3f7a
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/.idea/sonarlint/issuestore/index.pb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mtjin/mtjin-android-clean-architecture-movieapp/edc3e6aad017063bf00ef17126eb9aae7100bdb8/AndroidArchitectureStudy/.idea/sonarlint/issuestore/index.pb
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | ext.kotlin_version = '1.3.61'
5 | repositories {
6 | google()
7 | jcenter()
8 |
9 | }
10 | dependencies {
11 | classpath 'com.android.tools.build:gradle:3.5.4'
12 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
13 | //Hilt
14 | classpath "com.google.dagger:hilt-android-gradle-plugin:2.39.1"
15 | // NOTE: Do not place your application dependencies here; they belong
16 | // in the individual module build.gradle files
17 | }
18 | }
19 |
20 | allprojects {
21 | repositories {
22 | google()
23 | jcenter()
24 |
25 | }
26 | }
27 |
28 | task clean(type: Delete) {
29 | delete rootProject.buildDir
30 | }
31 |
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/data/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/data/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | apply plugin: 'kotlin-android'
4 |
5 | apply plugin: 'kotlin-kapt'
6 |
7 | apply plugin: 'kotlin-android-extensions'
8 |
9 | apply plugin: 'dagger.hilt.android.plugin'
10 |
11 | android {
12 | compileSdkVersion 29
13 | buildToolsVersion "29.0.2"
14 | defaultConfig {
15 | minSdkVersion 23
16 | targetSdkVersion 29
17 | versionCode 1
18 | versionName "1.0"
19 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
20 | }
21 | compileOptions {
22 | targetCompatibility = "8"
23 | sourceCompatibility = "8"
24 | }
25 | dataBinding {
26 | enabled = true
27 | }
28 | kotlinOptions {
29 | jvmTarget = "1.8"
30 | }
31 | }
32 |
33 | dependencies {
34 | implementation fileTree(dir: 'libs', include: ['*.jar'])
35 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
36 | //retrofit2
37 | implementation 'com.squareup.retrofit2:retrofit:2.9.0'
38 | implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
39 | implementation "com.squareup.retrofit2:adapter-rxjava2:2.8.1"
40 | //rxjava
41 | implementation "io.reactivex.rxjava2:rxjava:2.2.17"
42 | implementation "io.reactivex.rxjava2:rxandroid:2.1.1"
43 | implementation 'com.squareup.retrofit2:adapter-rxjava2:2.8.1'
44 | implementation "io.reactivex.rxjava2:rxkotlin:2.3.0"
45 | implementation 'com.squareup.okhttp3:okhttp:4.9.0'
46 | implementation "com.squareup.okhttp3:logging-interceptor:4.8.1"
47 | // Room
48 | implementation "androidx.room:room-runtime:2.2.6"
49 | implementation "androidx.room:room-rxjava2:2.2.6"
50 | testImplementation "androidx.room:room-testing:2.2.6"
51 | kapt "androidx.room:room-compiler:2.2.6"
52 | implementation 'androidx.room:room-ktx:2.2.6'
53 | // Hilt
54 | implementation "com.google.dagger:hilt-android:2.35"
55 | kapt "com.google.dagger:hilt-compiler:2.35"
56 | //multi module
57 | implementation project(':domain')
58 | }
59 |
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/data/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/data/src/main/java/com/mtjin/data/api/ApiClient.kt:
--------------------------------------------------------------------------------
1 | package com.mtjin.data.api
2 |
3 | object ApiClient {
4 | const val BASE_URL = "https://openapi.naver.com/"
5 | }
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/data/src/main/java/com/mtjin/data/api/ApiInterface.kt:
--------------------------------------------------------------------------------
1 | package com.mtjin.data.api
2 |
3 | import com.mtjin.data.api.ApiClient.BASE_URL
4 | import com.mtjin.data.model.search.MovieResponse
5 | import io.reactivex.Single
6 | import okhttp3.Interceptor
7 | import okhttp3.OkHttpClient
8 | import okhttp3.logging.HttpLoggingInterceptor
9 | import retrofit2.Retrofit
10 | import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
11 | import retrofit2.converter.gson.GsonConverterFactory
12 | import retrofit2.http.GET
13 | import retrofit2.http.Query
14 |
15 | interface ApiInterface {
16 | // query : 검색어, start : 시작, display : 갖고올개수
17 | @GET("v1/search/movie.json")
18 | fun getSearchMovie(
19 | @Query("query") query: String,
20 | @Query("start") start: Int = 1,
21 | @Query("display") display: Int = 15
22 | ): Single
23 |
24 | companion object {
25 |
26 | fun create(): ApiInterface {
27 | val logger = HttpLoggingInterceptor().apply {
28 | level =
29 | HttpLoggingInterceptor.Level.BASIC
30 | }
31 | val interceptor = Interceptor { chain ->
32 | with(chain) {
33 | val newRequest = request().newBuilder()
34 | .addHeader("X-Naver-Client-Id", "33chRuAiqlSn5hn8tIme")
35 | .addHeader("X-Naver-Client-Secret", "fyfwt9PCUN")
36 | .build()
37 | proceed(newRequest)
38 | }
39 | }
40 | val client = OkHttpClient.Builder()
41 | .addInterceptor(logger)
42 | .addInterceptor(interceptor)
43 | .build()
44 |
45 | return Retrofit.Builder()
46 | .baseUrl(BASE_URL)
47 | .client(client)
48 | .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
49 | .addConverterFactory(GsonConverterFactory.create())
50 | .build()
51 | .create(ApiInterface::class.java)
52 | }
53 | }
54 | }
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/data/src/main/java/com/mtjin/data/db/MovieDao.kt:
--------------------------------------------------------------------------------
1 | package com.mtjin.data.db
2 |
3 | import androidx.room.Dao
4 | import androidx.room.Insert
5 | import androidx.room.OnConflictStrategy.REPLACE
6 | import androidx.room.Query
7 | import com.mtjin.data.model.search.MovieEntity
8 | import io.reactivex.Completable
9 | import io.reactivex.Single
10 |
11 | @Dao
12 | interface MovieDao {
13 |
14 | @Insert(onConflict = REPLACE)
15 | fun insertMovies(movies: List): Completable
16 |
17 | @Query("SELECT * FROM movie")
18 | fun getAllMovies(): Single>
19 |
20 | @Query("SELECT * FROM movie WHERE title LIKE '%' || :title || '%'")
21 | fun getMoviesByTitle(title: String): Single>
22 |
23 | @Query("DELETE FROM movie")
24 | fun deleteAllMovies(): Completable
25 | }
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/data/src/main/java/com/mtjin/data/db/MovieDatabase.kt:
--------------------------------------------------------------------------------
1 | package com.mtjin.data.db
2 |
3 | import androidx.room.Database
4 | import androidx.room.RoomDatabase
5 | import com.mtjin.data.model.search.MovieEntity
6 |
7 | @Database(entities = [MovieEntity::class], version = 1, exportSchema = false)
8 | abstract class MovieDatabase : RoomDatabase() {
9 | abstract fun movieDao(): MovieDao
10 | }
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/data/src/main/java/com/mtjin/data/di/ApiModule.kt:
--------------------------------------------------------------------------------
1 | package com.mtjin.data.di
2 |
3 | import com.mtjin.data.api.ApiInterface
4 | import dagger.Module
5 | import dagger.Provides
6 | import dagger.hilt.InstallIn
7 | import dagger.hilt.components.SingletonComponent
8 | import javax.inject.Singleton
9 |
10 |
11 | @InstallIn(SingletonComponent::class)
12 | @Module
13 | class ApiModule {
14 |
15 | @Provides
16 | @Singleton
17 | fun provideApiInterface(): ApiInterface {
18 | return ApiInterface.create()
19 | }
20 | }
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/data/src/main/java/com/mtjin/data/di/LocalDataModule.kt:
--------------------------------------------------------------------------------
1 | package com.mtjin.data.di
2 |
3 | import android.content.Context
4 | import androidx.room.Room
5 | import com.mtjin.data.db.MovieDao
6 | import com.mtjin.data.db.MovieDatabase
7 | import com.mtjin.data.repository.login.local.LoginLocalDataSource
8 | import com.mtjin.data.repository.login.local.LoginLocalDataSourceImpl
9 | import com.mtjin.data.repository.search.local.MovieLocalDataSource
10 | import com.mtjin.data.repository.search.local.MovieLocalDataSourceImpl
11 | import com.mtjin.data.utils.PreferenceManager
12 | import dagger.Module
13 | import dagger.Provides
14 | import dagger.hilt.InstallIn
15 | import dagger.hilt.android.qualifiers.ApplicationContext
16 | import dagger.hilt.components.SingletonComponent
17 | import javax.inject.Singleton
18 |
19 | @InstallIn(SingletonComponent::class)
20 | @Module
21 | class LocalDataModule {
22 |
23 | //localRepo
24 | @Provides
25 | @Singleton
26 | fun provideLoginLocalDataSource(preferenceManager: PreferenceManager): LoginLocalDataSource {
27 | return LoginLocalDataSourceImpl(preferenceManager)
28 | }
29 |
30 | @Provides
31 | @Singleton
32 | fun provideMovieLocalDataSource(movieDao: MovieDao): MovieLocalDataSource {
33 | return MovieLocalDataSourceImpl(movieDao)
34 | }
35 |
36 | //room
37 | @Provides
38 | @Singleton
39 | fun provideDatabase(@ApplicationContext context: Context): MovieDatabase {
40 | return Room.databaseBuilder(
41 | context,
42 | MovieDatabase::class.java, "Movie.db"
43 | )
44 | .allowMainThreadQueries()
45 | .build()
46 | }
47 |
48 | @Provides
49 | @Singleton
50 | fun provideMovieDao(movieDatabase: MovieDatabase): MovieDao {
51 | return movieDatabase.movieDao()
52 | }
53 |
54 | // sharedPref
55 | @Provides
56 | @Singleton
57 | fun providePreferenceManager(@ApplicationContext context: Context): PreferenceManager {
58 | return PreferenceManager(context)
59 | }
60 |
61 | }
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/data/src/main/java/com/mtjin/data/di/RemoteDataModule.kt:
--------------------------------------------------------------------------------
1 | package com.mtjin.data.di
2 |
3 | import com.mtjin.data.api.ApiInterface
4 | import com.mtjin.data.repository.search.remote.MovieRemoteDataSource
5 | import com.mtjin.data.repository.search.remote.MovieRemoteDataSourceImpl
6 | import dagger.Module
7 | import dagger.Provides
8 | import dagger.hilt.InstallIn
9 | import dagger.hilt.components.SingletonComponent
10 | import javax.inject.Singleton
11 |
12 | @InstallIn(SingletonComponent::class)
13 | @Module
14 | class RemoteDataModule {
15 | @Provides
16 | @Singleton
17 | fun provideMovieRemoteDataSource(apiInterface: ApiInterface): MovieRemoteDataSource {
18 | return MovieRemoteDataSourceImpl(apiInterface)
19 | }
20 | }
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/data/src/main/java/com/mtjin/data/di/RepositoryModule.kt:
--------------------------------------------------------------------------------
1 | package com.mtjin.data.di
2 |
3 | import com.mtjin.data.repository.login.LoginRepositoryImpl
4 | import com.mtjin.data.repository.login.local.LoginLocalDataSource
5 | import com.mtjin.data.repository.search.MovieRepositoryImpl
6 | import com.mtjin.data.repository.search.local.MovieLocalDataSource
7 | import com.mtjin.data.repository.search.remote.MovieRemoteDataSource
8 | import com.mtjin.domain.repository.LoginRepository
9 | import com.mtjin.domain.repository.MovieRepository
10 | import dagger.Module
11 | import dagger.Provides
12 | import dagger.hilt.InstallIn
13 | import dagger.hilt.components.SingletonComponent
14 | import javax.inject.Singleton
15 |
16 | @InstallIn(SingletonComponent::class)
17 | @Module
18 | class RepositoryModule {
19 |
20 | @Provides
21 | @Singleton
22 | fun provideMovieRepository(
23 | movieRemoteDataSource: MovieRemoteDataSource,
24 | movieLocalDataSource: MovieLocalDataSource
25 | ): MovieRepository {
26 | return MovieRepositoryImpl(movieRemoteDataSource, movieLocalDataSource)
27 | }
28 |
29 | @Provides
30 | @Singleton
31 | fun provideLoginRepository(loginLocalDataSource: LoginLocalDataSource): LoginRepository {
32 | return LoginRepositoryImpl(loginLocalDataSource)
33 | }
34 |
35 | }
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/data/src/main/java/com/mtjin/data/mapper/Mapper.kt:
--------------------------------------------------------------------------------
1 | package com.mtjin.data.mapper
2 |
3 | import com.mtjin.data.model.search.MovieEntity
4 | import com.mtjin.domain.model.search.Movie
5 |
6 | fun mapperToMovie(movies: List): List {
7 | return movies.toList().map {
8 | Movie(
9 | it.actor,
10 | it.director,
11 | it.image,
12 | it.link,
13 | it.pubDate,
14 | it.subtitle,
15 | it.title,
16 | it.userRating
17 | )
18 | }
19 | }
20 |
21 | // 이 프로젝트에서는 Domain -> Data 레이어로 모델클래스를 매개변수로 전송하는 일이 없어서 사용은안한다
22 | fun mapperToMovieEntity(movies: List): List {
23 | return movies.toList().map {
24 | MovieEntity(
25 | it.actor,
26 | it.director,
27 | it.image,
28 | it.link,
29 | it.pubDate,
30 | it.subtitle,
31 | it.title,
32 | it.userRating
33 | )
34 | }
35 | }
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/data/src/main/java/com/mtjin/data/model/search/MovieEntity.kt:
--------------------------------------------------------------------------------
1 | package com.mtjin.data.model.search
2 |
3 |
4 | import androidx.room.Entity
5 | import androidx.room.PrimaryKey
6 | import com.google.gson.annotations.SerializedName
7 |
8 | @Entity(tableName = "movie")
9 | data class MovieEntity(
10 | @SerializedName("actor")
11 | val actor: String,
12 | @SerializedName("director")
13 | val director: String,
14 | @SerializedName("image")
15 | val image: String,
16 | @SerializedName("link")
17 | val link: String,
18 | @SerializedName("pubDate")
19 | val pubDate: String,
20 | @SerializedName("subtitle")
21 | val subtitle: String,
22 | @PrimaryKey(autoGenerate = false)
23 | @SerializedName("title")
24 | val title: String,
25 | @SerializedName("userRating")
26 | val userRating: String
27 | )
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/data/src/main/java/com/mtjin/data/model/search/MovieResponse.kt:
--------------------------------------------------------------------------------
1 | package com.mtjin.data.model.search
2 |
3 |
4 | import com.google.gson.annotations.SerializedName
5 |
6 | data class MovieResponse(
7 | @SerializedName("display")
8 | val display: Int,
9 | @SerializedName("items")
10 | val movies: List,
11 | @SerializedName("lastBuildDate")
12 | val lastBuildDate: String,
13 | @SerializedName("start")
14 | val start: Int,
15 | @SerializedName("total")
16 | val total: Int
17 | )
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/data/src/main/java/com/mtjin/data/repository/login/LoginRepositoryImpl.kt:
--------------------------------------------------------------------------------
1 | package com.mtjin.data.repository.login
2 |
3 | import com.mtjin.data.repository.login.local.LoginLocalDataSource
4 | import com.mtjin.domain.repository.LoginRepository
5 | import javax.inject.Inject
6 |
7 | class LoginRepositoryImpl @Inject constructor(private val loginLocalDataSource: LoginLocalDataSource) :
8 | LoginRepository {
9 |
10 | override var autoLogin: Boolean
11 | get() = loginLocalDataSource.autoLogin
12 | set(value) {
13 | loginLocalDataSource.autoLogin = value
14 | }
15 | }
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/data/src/main/java/com/mtjin/data/repository/login/local/LoginLocalDataSource.kt:
--------------------------------------------------------------------------------
1 | package com.mtjin.data.repository.login.local
2 |
3 | interface LoginLocalDataSource {
4 | var autoLogin: Boolean
5 | }
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/data/src/main/java/com/mtjin/data/repository/login/local/LoginLocalDataSourceImpl.kt:
--------------------------------------------------------------------------------
1 | package com.mtjin.data.repository.login.local
2 |
3 | import com.mtjin.data.utils.PreferenceManager
4 | import javax.inject.Inject
5 |
6 |
7 | class LoginLocalDataSourceImpl @Inject constructor(private val preferenceManager: PreferenceManager) :
8 | LoginLocalDataSource {
9 | override var autoLogin: Boolean
10 | get() = preferenceManager.autoLogin
11 | set(value) {
12 | preferenceManager.autoLogin = value
13 | }
14 | }
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/data/src/main/java/com/mtjin/data/repository/search/MovieRepositoryImpl.kt:
--------------------------------------------------------------------------------
1 | package com.mtjin.data.repository.search
2 |
3 | import com.mtjin.data.mapper.mapperToMovie
4 | import com.mtjin.data.repository.search.local.MovieLocalDataSource
5 | import com.mtjin.data.repository.search.remote.MovieRemoteDataSource
6 | import com.mtjin.data.utils.LAST_PAGE
7 | import com.mtjin.data.utils.NO_DATA_FROM_LOCAL_DB
8 | import com.mtjin.domain.model.search.Movie
9 | import com.mtjin.domain.repository.MovieRepository
10 | import io.reactivex.Flowable
11 | import io.reactivex.Single
12 | import javax.inject.Inject
13 |
14 | class MovieRepositoryImpl @Inject constructor(
15 | private val movieRemoteDataSource: MovieRemoteDataSource,
16 | private val movieLocalDataSource: MovieLocalDataSource
17 | ) : MovieRepository {
18 |
19 | //첫 영화검색
20 | override fun getSearchMovies(query: String): Flowable> {
21 | return movieLocalDataSource.getSearchMovies(query)
22 | .onErrorReturn { listOf() }
23 | .flatMapPublisher { localMovies ->
24 | if (localMovies.isEmpty()) {
25 | getRemoteSearchMovies(query)
26 | .toFlowable()
27 | .onErrorReturn { listOf() }
28 | } else {
29 | val local = Single.just(mapperToMovie(localMovies)) // 로컬 DB
30 | val remote = getRemoteSearchMovies(query) // 서버 API
31 | .onErrorResumeNext { local }
32 | Single.concat(local, remote) // 순서대로 불러옴
33 | }
34 | }
35 | }
36 |
37 | //인터넷이 끊킨 경우 로컬디비에서 검색
38 | override fun getLocalSearchMovies(query: String): Flowable> {
39 | return movieLocalDataSource.getSearchMovies(query)
40 | .onErrorReturn { listOf() }
41 | .flatMapPublisher { cachedMovies ->
42 | if (cachedMovies.isEmpty()) {
43 | Flowable.error(IllegalStateException(NO_DATA_FROM_LOCAL_DB))
44 | } else {
45 | Flowable.just(mapperToMovie(cachedMovies))
46 | }
47 | }
48 | }
49 |
50 | // 서버 DB 영화검색 요청
51 | override fun getRemoteSearchMovies(
52 | query: String
53 | ): Single> {
54 | return movieRemoteDataSource.getSearchMovies(query)
55 | .flatMap {
56 | movieLocalDataSource.insertMovies(it.movies)
57 | .andThen(Single.just(mapperToMovie(it.movies)))
58 | }
59 | }
60 |
61 | //영화 검색 후 스크롤 내리면 영화 더 불러오기
62 | override fun getPagingMovies(
63 | query: String,
64 | offset: Int
65 | ): Single> {
66 | return movieRemoteDataSource.getSearchMovies(query, offset).flatMap {
67 | if (it.movies.isEmpty()) {
68 | Single.error(IllegalStateException(LAST_PAGE))
69 | } else {
70 | if (offset != it.start) {
71 | Single.error(IllegalStateException(LAST_PAGE))
72 | } else {
73 | movieLocalDataSource.insertMovies(it.movies)
74 | .andThen(Single.just(mapperToMovie(it.movies)))
75 | }
76 | }
77 | }
78 | }
79 |
80 | }
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/data/src/main/java/com/mtjin/data/repository/search/local/MovieLocalDataSource.kt:
--------------------------------------------------------------------------------
1 | package com.mtjin.data.repository.search.local
2 |
3 | import com.mtjin.data.model.search.MovieEntity
4 | import io.reactivex.Completable
5 | import io.reactivex.Single
6 |
7 | interface MovieLocalDataSource {
8 | fun insertMovies(movies: List): Completable
9 | fun getAllMovies(): Single>
10 | fun getSearchMovies(title: String): Single>
11 | fun deleteAllMovies(): Completable
12 | }
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/data/src/main/java/com/mtjin/data/repository/search/local/MovieLocalDataSourceImpl.kt:
--------------------------------------------------------------------------------
1 | package com.mtjin.data.repository.search.local
2 |
3 | import com.mtjin.data.db.MovieDao
4 | import com.mtjin.data.model.search.MovieEntity
5 | import io.reactivex.Completable
6 | import io.reactivex.Single
7 | import javax.inject.Inject
8 |
9 | class MovieLocalDataSourceImpl @Inject constructor(private val movieDao: MovieDao) :
10 | MovieLocalDataSource {
11 | override fun insertMovies(movies: List): Completable =
12 | movieDao.insertMovies(movies)
13 |
14 | override fun getAllMovies(): Single> = movieDao.getAllMovies()
15 |
16 | override fun getSearchMovies(title: String): Single> =
17 | movieDao.getMoviesByTitle(title)
18 |
19 | override fun deleteAllMovies(): Completable = movieDao.deleteAllMovies()
20 |
21 | }
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/data/src/main/java/com/mtjin/data/repository/search/remote/MovieRemoteDataSource.kt:
--------------------------------------------------------------------------------
1 | package com.mtjin.data.repository.search.remote
2 |
3 | import com.mtjin.data.model.search.MovieResponse
4 | import io.reactivex.Single
5 |
6 | interface MovieRemoteDataSource {
7 | fun getSearchMovies(
8 | query: String,
9 | start: Int = 1
10 | ): Single
11 | }
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/data/src/main/java/com/mtjin/data/repository/search/remote/MovieRemoteDataSourceImpl.kt:
--------------------------------------------------------------------------------
1 | package com.mtjin.data.repository.search.remote
2 |
3 | import com.mtjin.data.api.ApiInterface
4 | import com.mtjin.data.model.search.MovieResponse
5 | import io.reactivex.Single
6 | import javax.inject.Inject
7 |
8 |
9 | class MovieRemoteDataSourceImpl @Inject constructor(private val apiInterface: ApiInterface) :
10 | MovieRemoteDataSource {
11 | override fun getSearchMovies(query: String, start: Int): Single {
12 | return apiInterface.getSearchMovie(query, start)
13 | }
14 | }
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/data/src/main/java/com/mtjin/data/utils/Constatnts.kt:
--------------------------------------------------------------------------------
1 | package com.mtjin.data.utils
2 |
3 | const val LAST_PAGE= "LAST_PAGE"
4 | const val NO_DATA_FROM_LOCAL_DB= "NO_DATA_FROM_LOCAL_DB"
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/data/src/main/java/com/mtjin/data/utils/PreferenceManager.kt:
--------------------------------------------------------------------------------
1 | package com.mtjin.data.utils
2 |
3 | import android.content.Context
4 | import android.content.SharedPreferences
5 |
6 | class PreferenceManager(context: Context) {
7 | private val autoLoginPref: SharedPreferences =
8 | context.getSharedPreferences(MOVIE_SEARCH_APP, Context.MODE_PRIVATE)
9 |
10 | var autoLogin: Boolean
11 | get() = autoLoginPref.getBoolean(AUTO_LOGIN_KEY, false)
12 | set(value) {
13 | val editor = autoLoginPref.edit()
14 | editor.putBoolean(AUTO_LOGIN_KEY, value)
15 | editor.apply()
16 | }
17 |
18 | companion object {
19 | private const val MOVIE_SEARCH_APP = "MOVIE_SEARCH_APP"
20 | const val AUTO_LOGIN_KEY = "AUTO_LOGIN_KEY"
21 | }
22 | }
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/domain/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/domain/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | apply plugin: 'kotlin-android'
4 |
5 | apply plugin: 'kotlin-kapt'
6 |
7 | apply plugin: 'kotlin-android-extensions'
8 |
9 | apply plugin: 'dagger.hilt.android.plugin'
10 |
11 | android {
12 | compileSdkVersion 29
13 | buildToolsVersion "29.0.2"
14 | defaultConfig {
15 | minSdkVersion 23
16 | targetSdkVersion 29
17 | versionCode 1
18 | versionName "1.0"
19 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
20 | }
21 | compileOptions {
22 | targetCompatibility = "8"
23 | sourceCompatibility = "8"
24 | }
25 | dataBinding {
26 | enabled = true
27 | }
28 | kotlinOptions {
29 | jvmTarget = "1.8"
30 | }
31 | }
32 |
33 | dependencies {
34 | implementation fileTree(dir: 'libs', include: ['*.jar'])
35 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
36 | //rxjava
37 | implementation "io.reactivex.rxjava2:rxjava:2.2.17"
38 | implementation "io.reactivex.rxjava2:rxandroid:2.1.1"
39 | implementation 'com.squareup.retrofit2:adapter-rxjava2:2.8.1'
40 | implementation "io.reactivex.rxjava2:rxkotlin:2.3.0"
41 | // Hilt
42 | implementation "com.google.dagger:hilt-android:2.35"
43 | kapt "com.google.dagger:hilt-compiler:2.35"
44 | }
45 |
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/domain/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/domain/src/main/java/com/mtjin/domain/di/UseCaseModule.kt:
--------------------------------------------------------------------------------
1 | package com.mtjin.domain.di
2 |
3 | import com.mtjin.domain.repository.LoginRepository
4 | import com.mtjin.domain.repository.MovieRepository
5 | import com.mtjin.domain.usecase.*
6 | import dagger.Module
7 | import dagger.Provides
8 | import dagger.hilt.InstallIn
9 | import dagger.hilt.components.SingletonComponent
10 | import javax.inject.Singleton
11 |
12 | @InstallIn(SingletonComponent::class)
13 | @Module
14 | class UseCaseModule {
15 | @Provides
16 | @Singleton
17 | fun provideGetLocalMoviesUseCase(movieRepository: MovieRepository): GetLocalMoviesUseCase {
18 | return GetLocalMoviesUseCase(movieRepository)
19 | }
20 |
21 | @Provides
22 | @Singleton
23 | fun provideGetLoginUseCase(loginRepository: LoginRepository): GetLoginUseCase {
24 | return GetLoginUseCase(loginRepository)
25 | }
26 |
27 | @Provides
28 | @Singleton
29 | fun provideGetMoviesUseCase(movieRepository: MovieRepository): GetMoviesUseCase {
30 | return GetMoviesUseCase(movieRepository)
31 | }
32 |
33 | @Provides
34 | @Singleton
35 | fun provideGetPagingMoviesUseCase(movieRepository: MovieRepository): GetPagingMoviesUseCase {
36 | return GetPagingMoviesUseCase(movieRepository)
37 | }
38 |
39 | @Provides
40 | @Singleton
41 | fun provideInsertLoginUseCase(loginRepository: LoginRepository): InsertLoginUseCase {
42 | return InsertLoginUseCase(loginRepository)
43 | }
44 |
45 | }
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/domain/src/main/java/com/mtjin/domain/model/search/Movie.kt:
--------------------------------------------------------------------------------
1 | package com.mtjin.domain.model.search
2 |
3 |
4 | data class Movie(
5 | val actor: String,
6 | val director: String,
7 | val image: String,
8 | val link: String,
9 | val pubDate: String,
10 | val subtitle: String,
11 | val title: String,
12 | val userRating: String
13 | )
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/domain/src/main/java/com/mtjin/domain/repository/LoginRepository.kt:
--------------------------------------------------------------------------------
1 | package com.mtjin.domain.repository
2 |
3 | interface LoginRepository {
4 |
5 | var autoLogin: Boolean
6 | }
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/domain/src/main/java/com/mtjin/domain/repository/MovieRepository.kt:
--------------------------------------------------------------------------------
1 | package com.mtjin.domain.repository
2 |
3 | import com.mtjin.domain.model.search.Movie
4 | import io.reactivex.Flowable
5 | import io.reactivex.Single
6 |
7 | interface MovieRepository {
8 | fun getSearchMovies(
9 | query: String
10 | ): Flowable>
11 |
12 | fun getLocalSearchMovies(
13 | query: String
14 | ): Flowable>
15 |
16 | fun getRemoteSearchMovies(
17 | query: String
18 | ): Single>
19 |
20 | fun getPagingMovies(
21 | query: String,
22 | offset: Int
23 | ): Single>
24 | }
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/domain/src/main/java/com/mtjin/domain/usecase/GetLocalMoviesUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.mtjin.domain.usecase
2 |
3 | import com.mtjin.domain.model.search.Movie
4 | import com.mtjin.domain.repository.MovieRepository
5 | import io.reactivex.Flowable
6 | import javax.inject.Inject
7 |
8 | class GetLocalMoviesUseCase @Inject constructor(private val repository: MovieRepository) {
9 | fun execute(
10 | query: String
11 | ): Flowable> = repository.getSearchMovies(query)
12 |
13 | }
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/domain/src/main/java/com/mtjin/domain/usecase/GetLoginUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.mtjin.domain.usecase
2 |
3 | import com.mtjin.domain.repository.LoginRepository
4 | import javax.inject.Inject
5 |
6 | class GetLoginUseCase @Inject constructor(private val repository: LoginRepository) {
7 | fun execute(): Boolean = repository.autoLogin //자동로그인 : 이전 로그인 기록있으면 자동로그인이 된다
8 | }
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/domain/src/main/java/com/mtjin/domain/usecase/GetMoviesUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.mtjin.domain.usecase
2 |
3 | import com.mtjin.domain.model.search.Movie
4 | import com.mtjin.domain.repository.MovieRepository
5 | import io.reactivex.Flowable
6 | import javax.inject.Inject
7 |
8 | class GetMoviesUseCase @Inject constructor(private val repository: MovieRepository) {
9 | fun execute(
10 | query: String
11 | ): Flowable> = repository.getSearchMovies(query)
12 |
13 | }
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/domain/src/main/java/com/mtjin/domain/usecase/GetPagingMoviesUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.mtjin.domain.usecase
2 |
3 | import com.mtjin.domain.model.search.Movie
4 | import com.mtjin.domain.repository.MovieRepository
5 | import io.reactivex.Single
6 | import javax.inject.Inject
7 |
8 | class GetPagingMoviesUseCase @Inject constructor(private val repository: MovieRepository) {
9 | fun execute(
10 | query: String,
11 | offset: Int
12 | ): Single> = repository.getPagingMovies(query, offset)
13 | }
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/domain/src/main/java/com/mtjin/domain/usecase/InsertLoginUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.mtjin.domain.usecase
2 |
3 | import com.mtjin.domain.repository.LoginRepository
4 | import javax.inject.Inject
5 |
6 | class InsertLoginUseCase @Inject constructor(private val repository: LoginRepository) {
7 | fun execute(success: Boolean) { // 다음 앱 실행부터는 자동로그인되게끔 플래그 저장
8 | repository.autoLogin = success
9 | }
10 | }
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx1536m
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # 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
22 | kapt.incremental.apt=true
23 |
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mtjin/mtjin-android-clean-architecture-movieapp/edc3e6aad017063bf00ef17126eb9aae7100bdb8/AndroidArchitectureStudy/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sun Feb 23 16:11:47 KST 2020
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
7 |
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/presentation/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/presentation/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | apply plugin: 'kotlin-android'
4 |
5 | apply plugin: 'kotlin-kapt'
6 |
7 | apply plugin: 'kotlin-android-extensions'
8 |
9 | apply plugin: 'dagger.hilt.android.plugin'
10 |
11 | android {
12 | compileSdkVersion 29
13 | buildToolsVersion "29.0.2"
14 | defaultConfig {
15 | applicationId "com.mtjin.androidarchitecturestudy"
16 | minSdkVersion 23
17 | targetSdkVersion 29
18 | versionCode 1
19 | versionName "1.0"
20 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
21 | }
22 | buildTypes {
23 | release {
24 | minifyEnabled false
25 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
26 | }
27 | }
28 | compileOptions {
29 | targetCompatibility = "8"
30 | sourceCompatibility = "8"
31 | }
32 | dataBinding {
33 | enabled = true
34 | }
35 | kotlinOptions {
36 | jvmTarget = "1.8"
37 | }
38 | }
39 |
40 | dependencies {
41 | implementation fileTree(dir: 'libs', include: ['*.jar'])
42 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
43 | implementation 'androidx.appcompat:appcompat:1.2.0'
44 | implementation 'androidx.core:core-ktx:1.3.2'
45 | implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
46 | testImplementation 'junit:junit:4.13.1'
47 | androidTestImplementation 'androidx.test.ext:junit:1.1.2'
48 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
49 | //recyclerview
50 | implementation 'androidx.recyclerview:recyclerview:1.1.0'
51 | //glide
52 | implementation 'com.github.bumptech.glide:glide:4.11.0'
53 | kapt 'com.github.bumptech.glide:compiler:4.11.0'
54 | //rxjava
55 | implementation "io.reactivex.rxjava2:rxjava:2.2.17"
56 | implementation "io.reactivex.rxjava2:rxandroid:2.1.1"
57 | implementation 'com.squareup.retrofit2:adapter-rxjava2:2.8.1'
58 | implementation "io.reactivex.rxjava2:rxkotlin:2.3.0"
59 | // Room
60 | implementation "androidx.room:room-runtime:2.2.6"
61 | //Gson
62 | implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
63 | //coroutine lifecycle
64 | implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.3.0-rc01"
65 | //ktx extensions
66 | implementation 'androidx.core:core-ktx:1.3.2'
67 | implementation 'androidx.fragment:fragment-ktx:1.2.5'
68 | implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
69 | // Hilt
70 | implementation "com.google.dagger:hilt-android:2.35"
71 | kapt "com.google.dagger:hilt-compiler:2.35"
72 | //multi module
73 | implementation project(':data')
74 | implementation project(':domain')
75 | }
76 |
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/presentation/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/presentation/src/androidTest/java/com/mtjin/androidarchitecturestudy/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.mtjin.androidarchitecturestudy
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 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.mtjin.androidarchitecturestudy", appContext.packageName)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/presentation/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
18 |
21 |
24 |
25 |
26 |
27 |
28 |
29 |
32 |
33 |
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/presentation/src/main/java/com/mtjin/presentation/ViewDataBindingAdapters.kt:
--------------------------------------------------------------------------------
1 | package com.mtjin.presentation
2 |
3 | import android.widget.ImageView
4 | import android.widget.RatingBar
5 | import android.widget.TextView
6 | import androidx.core.text.HtmlCompat
7 | import androidx.databinding.BindingAdapter
8 | import androidx.recyclerview.widget.LinearLayoutManager
9 | import androidx.recyclerview.widget.RecyclerView
10 | import com.bumptech.glide.Glide
11 | import com.mtjin.domain.model.search.Movie
12 | import com.mtjin.presentation.utils.EndlessRecyclerViewScrollListener
13 | import com.mtjin.presentation.views.search.MovieAdapter
14 | import com.mtjin.presentation.views.search.MovieSearchViewModel
15 |
16 |
17 | @BindingAdapter("htmlText")
18 | fun TextView.setHtmlText(html: String) {
19 | text = HtmlCompat.fromHtml(html, HtmlCompat.FROM_HTML_MODE_COMPACT)
20 | }
21 |
22 | @BindingAdapter("urlImage")
23 | fun ImageView.setUrlImage(url: String) {
24 | Glide.with(this).load(url)
25 | .placeholder(R.drawable.ic_default)
26 | .into(this)
27 | }
28 |
29 | @BindingAdapter("movieRating")
30 | fun RatingBar.setMovieRating(score: String) {
31 | rating = (score.toFloatOrNull() ?: 0f) / 2
32 | }
33 |
34 | @BindingAdapter("setItems")
35 | fun RecyclerView.setAdapterItems(items: MutableList?) {
36 | items?.let {
37 | (adapter as MovieAdapter).submitList(it.toMutableList())
38 | }
39 | }
40 |
41 | @BindingAdapter("endlessScroll")
42 | fun RecyclerView.setEndlessScroll(
43 | viewModel: MovieSearchViewModel
44 | ) {
45 | val scrollListener =
46 | object : EndlessRecyclerViewScrollListener(layoutManager as LinearLayoutManager) {
47 | override fun onLoadMore(page: Int, totalItemsCount: Int, view: RecyclerView?) {
48 | viewModel.requestPagingMovie(totalItemsCount + 1)
49 | }
50 | }
51 | addOnScrollListener(scrollListener)
52 | }
53 |
54 |
55 |
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/presentation/src/main/java/com/mtjin/presentation/base/BaseActivity.kt:
--------------------------------------------------------------------------------
1 | package com.mtjin.presentation.base
2 |
3 | import android.os.Bundle
4 | import android.widget.Toast
5 | import androidx.annotation.LayoutRes
6 | import androidx.appcompat.app.AppCompatActivity
7 | import androidx.databinding.DataBindingUtil
8 | import androidx.databinding.ViewDataBinding
9 | import io.reactivex.disposables.CompositeDisposable
10 |
11 | abstract class BaseActivity(
12 | @LayoutRes val layoutId: Int
13 | ) : AppCompatActivity() {
14 | lateinit var binding: B
15 | private val compositeDisposable = CompositeDisposable()
16 |
17 | override fun onCreate(savedInstanceState: Bundle?) {
18 | super.onCreate(savedInstanceState)
19 | binding = DataBindingUtil.setContentView(this, layoutId)
20 | binding.lifecycleOwner = this
21 | }
22 |
23 | protected fun showToast(msg: String) {
24 | Toast.makeText(this, msg, Toast.LENGTH_SHORT).show()
25 | }
26 |
27 | override fun onDestroy() {
28 | super.onDestroy()
29 | compositeDisposable.clear()
30 | }
31 | }
32 |
33 |
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/presentation/src/main/java/com/mtjin/presentation/base/BaseViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.mtjin.presentation.base
2 |
3 | import androidx.lifecycle.LiveData
4 | import androidx.lifecycle.MutableLiveData
5 | import androidx.lifecycle.ViewModel
6 | import io.reactivex.disposables.CompositeDisposable
7 |
8 | abstract class BaseViewModel : ViewModel() {
9 | protected val compositeDisposable = CompositeDisposable()
10 |
11 | private val _isLoading = MutableLiveData(false)
12 | val isLoading: LiveData get() = _isLoading
13 |
14 | fun showProgress() {
15 | _isLoading.value = true
16 | }
17 |
18 | fun hideProgress() {
19 | _isLoading.value = false
20 | }
21 |
22 | override fun onCleared() {
23 | compositeDisposable.dispose()
24 | super.onCleared()
25 | }
26 | }
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/presentation/src/main/java/com/mtjin/presentation/di/MyApplication.kt:
--------------------------------------------------------------------------------
1 | package com.mtjin.presentation.di
2 |
3 | import android.app.Application
4 | import dagger.hilt.android.HiltAndroidApp
5 |
6 | @HiltAndroidApp
7 | class MyApplication : Application()
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/presentation/src/main/java/com/mtjin/presentation/module/NetworkModule.kt:
--------------------------------------------------------------------------------
1 | package com.mtjin.presentation.module
2 |
3 | import android.content.Context
4 | import com.mtjin.presentation.utils.NetworkManager
5 | import dagger.Module
6 | import dagger.Provides
7 | import dagger.hilt.InstallIn
8 | import dagger.hilt.android.qualifiers.ApplicationContext
9 | import dagger.hilt.components.SingletonComponent
10 | import javax.inject.Singleton
11 |
12 | @InstallIn(SingletonComponent::class)
13 | @Module
14 | class NetworkModule {
15 | @Provides
16 | @Singleton
17 | fun provideNetworkManager(@ApplicationContext context: Context): NetworkManager {
18 | return NetworkManager(context)
19 | }
20 |
21 | }
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/presentation/src/main/java/com/mtjin/presentation/utils/EndlessRecyclerViewScrollListener.kt:
--------------------------------------------------------------------------------
1 | package com.mtjin.presentation.utils
2 |
3 | import androidx.recyclerview.widget.GridLayoutManager
4 | import androidx.recyclerview.widget.LinearLayoutManager
5 | import androidx.recyclerview.widget.RecyclerView
6 | import androidx.recyclerview.widget.StaggeredGridLayoutManager
7 |
8 |
9 | abstract class EndlessRecyclerViewScrollListener : RecyclerView.OnScrollListener {
10 | // The minimum amount of items to have below your current scroll position
11 | // before loading more.
12 | private var visibleThreshold = 5
13 |
14 | // The current offset index of data you have loaded
15 | private var currentPage = 0
16 |
17 | // The total number of items in the dataset after the last load
18 | private var previousTotalItemCount = 0
19 |
20 | // True if we are still waiting for the last set of data to load.
21 | private var loading = true
22 |
23 | // Sets the starting page index
24 | private val startingPageIndex = 0
25 | var mLayoutManager: RecyclerView.LayoutManager
26 |
27 | constructor(layoutManager: LinearLayoutManager) {
28 | mLayoutManager = layoutManager
29 | }
30 |
31 | constructor(layoutManager: GridLayoutManager) {
32 | mLayoutManager = layoutManager
33 | visibleThreshold *= layoutManager.spanCount
34 | }
35 |
36 | constructor(layoutManager: StaggeredGridLayoutManager) {
37 | mLayoutManager = layoutManager
38 | visibleThreshold *= layoutManager.spanCount
39 | }
40 |
41 | private fun getLastVisibleItem(lastVisibleItemPositions: IntArray): Int {
42 | var maxSize = 0
43 | for (i in lastVisibleItemPositions.indices) {
44 | if (i == 0) {
45 | maxSize = lastVisibleItemPositions[i]
46 | } else if (lastVisibleItemPositions[i] > maxSize) {
47 | maxSize = lastVisibleItemPositions[i]
48 | }
49 | }
50 | return maxSize
51 | }
52 |
53 | // This happens many times a second during a scroll, so be wary of the code you place here.
54 | // We are given a few useful parameters to help us work out if we need to load some more data,
55 | // but first we check if we are waiting for the previous load to finish.
56 | override fun onScrolled(view: RecyclerView, dx: Int, dy: Int) {
57 | var lastVisibleItemPosition = 0
58 | val totalItemCount = mLayoutManager.itemCount
59 | if (mLayoutManager is StaggeredGridLayoutManager) {
60 | val lastVisibleItemPositions =
61 | (mLayoutManager as StaggeredGridLayoutManager).findLastVisibleItemPositions(null)
62 | // get maximum element within the list
63 | lastVisibleItemPosition = getLastVisibleItem(lastVisibleItemPositions)
64 | } else if (mLayoutManager is GridLayoutManager) {
65 | lastVisibleItemPosition =
66 | (mLayoutManager as GridLayoutManager).findLastVisibleItemPosition()
67 | } else if (mLayoutManager is LinearLayoutManager) {
68 | lastVisibleItemPosition =
69 | (mLayoutManager as LinearLayoutManager).findLastVisibleItemPosition()
70 | }
71 |
72 | // If the total item count is zero and the previous isn't, assume the
73 | // list is invalidated and should be reset back to initial state
74 | if (totalItemCount < previousTotalItemCount) {
75 | currentPage = startingPageIndex
76 | previousTotalItemCount = totalItemCount
77 | if (totalItemCount == 0) {
78 | loading = true
79 | }
80 | }
81 | // If it’s still loading, we check to see if the dataset count has
82 | // changed, if so we conclude it has finished loading and update the current page
83 | // number and total item count.
84 | if (loading && totalItemCount > previousTotalItemCount) {
85 | loading = false
86 | previousTotalItemCount = totalItemCount
87 | }
88 |
89 | // If it isn’t currently loading, we check to see if we have breached
90 | // the visibleThreshold and need to reload more data.
91 | // If we do need to reload some more data, we execute onLoadMore to fetch the data.
92 | // threshold should reflect how many total columns there are too
93 | if (!loading && lastVisibleItemPosition + visibleThreshold > totalItemCount) {
94 | currentPage++
95 | onLoadMore(currentPage, totalItemCount, view)
96 | loading = true
97 | }
98 | }
99 |
100 | // Call this method whenever performing new searches
101 | fun resetState() {
102 | currentPage = startingPageIndex
103 | previousTotalItemCount = 0
104 | loading = true
105 | }
106 |
107 | // Defines the process for actually loading more data based on page
108 | abstract fun onLoadMore(page: Int, totalItemsCount: Int, view: RecyclerView?)
109 | }
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/presentation/src/main/java/com/mtjin/presentation/utils/NetworkManager.kt:
--------------------------------------------------------------------------------
1 | package com.mtjin.presentation.utils
2 |
3 | import android.content.Context
4 | import android.net.ConnectivityManager
5 | import android.net.NetworkCapabilities
6 | import android.os.Build
7 | import javax.inject.Inject
8 |
9 | class NetworkManager @Inject constructor(private val context: Context) {
10 | fun checkNetworkState(): Boolean {
11 | val connectivityManager =
12 | context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
13 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
14 | val nw = connectivityManager.activeNetwork ?: return false
15 | val actNw = connectivityManager.getNetworkCapabilities(nw) ?: return false
16 | return when {
17 | actNw.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true
18 | actNw.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true
19 | else -> false
20 | }
21 | } else {
22 | val nwInfo = connectivityManager.activeNetworkInfo ?: return false
23 | return nwInfo.isConnected
24 | }
25 | }
26 | }
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/presentation/src/main/java/com/mtjin/presentation/views/login/LoginActivity.kt:
--------------------------------------------------------------------------------
1 | package com.mtjin.presentation.views.login
2 |
3 | import android.content.Intent
4 | import android.os.Bundle
5 | import androidx.activity.viewModels
6 | import androidx.lifecycle.Observer
7 | import com.mtjin.presentation.R
8 | import com.mtjin.presentation.base.BaseActivity
9 | import com.mtjin.presentation.databinding.ActivityLoginBinding
10 | import com.mtjin.presentation.views.search.MovieSearchActivity
11 | import dagger.hilt.android.AndroidEntryPoint
12 |
13 | @AndroidEntryPoint
14 | class LoginActivity : BaseActivity(R.layout.activity_login) {
15 | private val viewModel: LoginViewModel by viewModels()
16 |
17 | override fun onCreate(savedInstanceState: Bundle?) {
18 | super.onCreate(savedInstanceState)
19 | binding.vm = viewModel
20 | initViewModelCallback()
21 | }
22 |
23 | private fun initViewModelCallback() {
24 | with(viewModel) {
25 | isIdEmpty.observe(this@LoginActivity, Observer {
26 | showIdEmptyError()
27 | })
28 | isPwEmpty.observe(this@LoginActivity, Observer {
29 | showPwEmptyError()
30 | })
31 | loginErrorMsg.observe(this@LoginActivity, Observer {
32 | showToast(getString(R.string.id_pw_not_correct_error_msg))
33 | })
34 | successLogin.observe(this@LoginActivity, Observer {
35 | goMovieSearch()
36 | })
37 | }
38 | }
39 |
40 | private fun showIdEmptyError() {
41 | binding.etId.error = getString(R.string.id_empty_error_msg)
42 | }
43 |
44 | private fun showPwEmptyError() {
45 | binding.etPw.error = getString(R.string.pw_empty_error_msg)
46 | }
47 |
48 | private fun goMovieSearch() {
49 | showToast(getString(R.string.login_success_msg))
50 | startActivity(Intent(this, MovieSearchActivity::class.java))
51 | finish()
52 | }
53 |
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/presentation/src/main/java/com/mtjin/presentation/views/login/LoginViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.mtjin.presentation.views.login
2 |
3 | import androidx.lifecycle.LiveData
4 | import androidx.lifecycle.MutableLiveData
5 | import androidx.lifecycle.ViewModel
6 | import com.mtjin.domain.usecase.InsertLoginUseCase
7 | import dagger.hilt.android.lifecycle.HiltViewModel
8 | import javax.inject.Inject
9 |
10 | @HiltViewModel
11 | class LoginViewModel @Inject constructor(private val insertLoginUseCase: InsertLoginUseCase) :
12 | ViewModel() {
13 | val id: MutableLiveData = MutableLiveData("")
14 | val pw: MutableLiveData = MutableLiveData("")
15 | private val _isIdEmpty: MutableLiveData = MutableLiveData()
16 | private val _isPwEmpty: MutableLiveData = MutableLiveData()
17 | private val _loginErrorMsg: MutableLiveData = MutableLiveData()
18 | private val _successLogin: MutableLiveData = MutableLiveData()
19 |
20 | val isIdEmpty: LiveData get() = _isIdEmpty
21 | val isPwEmpty: LiveData get() = _isPwEmpty
22 | val loginErrorMsg: LiveData get() = _loginErrorMsg
23 | val successLogin: LiveData get() = _successLogin
24 |
25 | fun onLoginClick() {
26 | val id = id.value.toString().trim()
27 | val pw = pw.value.toString().trim()
28 | if (id.isEmpty()) {
29 | _isIdEmpty.value = Unit
30 | } else if (pw.isEmpty()) {
31 | _isPwEmpty.value = Unit
32 | } else if (id != USER_ID || pw != USER_PW) {
33 | _loginErrorMsg.value = Unit
34 | } else {
35 | insertLoginUseCase.execute(true)
36 | _successLogin.value = Unit
37 | }
38 | }
39 |
40 | companion object { //이 아이디와 비번으로만 로그인이 가능 (서버X)
41 | private const val USER_ID = "id"
42 | private const val USER_PW = "pass"
43 | }
44 | }
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/presentation/src/main/java/com/mtjin/presentation/views/search/MovieAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.mtjin.presentation.views.search
2 |
3 | import android.view.LayoutInflater
4 | import android.view.ViewGroup
5 | import androidx.databinding.DataBindingUtil
6 | import androidx.recyclerview.widget.DiffUtil
7 | import androidx.recyclerview.widget.ListAdapter
8 | import androidx.recyclerview.widget.RecyclerView
9 | import com.mtjin.domain.model.search.Movie
10 | import com.mtjin.presentation.R
11 | import com.mtjin.presentation.databinding.ItemMovieBinding
12 |
13 | class MovieAdapter(private val itemClick: (Movie) -> Unit) :
14 | ListAdapter(
15 | diffUtil
16 | ) {
17 |
18 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
19 | val binding: ItemMovieBinding = DataBindingUtil.inflate(
20 | LayoutInflater.from(parent.context),
21 | R.layout.item_movie,
22 | parent,
23 | false
24 | )
25 | return ViewHolder(binding).apply {
26 | binding.root.setOnClickListener { view ->
27 | val position = adapterPosition.takeIf { it != RecyclerView.NO_POSITION }
28 | ?: return@setOnClickListener
29 | itemClick(getItem(position))
30 | }
31 | }
32 | }
33 |
34 | override fun onBindViewHolder(holder: ViewHolder, position: Int) {
35 | holder.bind(getItem(position))
36 | }
37 |
38 | class ViewHolder(private val binding: ItemMovieBinding) :
39 | RecyclerView.ViewHolder(binding.root) {
40 |
41 | fun bind(movie: Movie) {
42 | binding.movie = movie
43 | binding.executePendingBindings()
44 | }
45 | }
46 |
47 | companion object {
48 | val diffUtil = object : DiffUtil.ItemCallback() {
49 | override fun areContentsTheSame(oldItem: Movie, newItem: Movie) =
50 | oldItem == newItem
51 |
52 | override fun areItemsTheSame(oldItem: Movie, newItem: Movie) =
53 | oldItem.title == newItem.title
54 | }
55 | }
56 | }
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/presentation/src/main/java/com/mtjin/presentation/views/search/MovieSearchActivity.kt:
--------------------------------------------------------------------------------
1 | package com.mtjin.presentation.views.search
2 |
3 | import android.content.Intent
4 | import android.net.Uri
5 | import android.os.Bundle
6 | import androidx.activity.viewModels
7 | import androidx.lifecycle.Observer
8 | import com.mtjin.presentation.R
9 | import com.mtjin.presentation.base.BaseActivity
10 | import com.mtjin.presentation.databinding.ActivityMovieSearchBinding
11 | import dagger.hilt.android.AndroidEntryPoint
12 |
13 | @AndroidEntryPoint
14 | class MovieSearchActivity :
15 | BaseActivity(R.layout.activity_movie_search) {
16 | private lateinit var movieAdapter: MovieAdapter
17 | private val viewModel: MovieSearchViewModel by viewModels()
18 |
19 | override fun onCreate(savedInstanceState: Bundle?) {
20 | super.onCreate(savedInstanceState)
21 | binding.vm = viewModel
22 | initViewModelCallback()
23 | initAdapter()
24 | }
25 |
26 | private fun initAdapter() {
27 | movieAdapter = MovieAdapter { movie ->
28 | Intent(Intent.ACTION_VIEW, Uri.parse(movie.link)).takeIf {
29 | it.resolveActivity(packageManager) != null
30 | }?.run(this::startActivity)
31 | }
32 | binding.rvMovies.adapter = movieAdapter
33 | }
34 |
35 | private fun initViewModelCallback() {
36 | with(viewModel) {
37 | toastMsg.observe(this@MovieSearchActivity, Observer {
38 | when (toastMsg.value) {
39 | MovieSearchViewModel.MessageSet.LAST_PAGE -> showToast(getString(R.string.last_page_msg))
40 | MovieSearchViewModel.MessageSet.EMPTY_QUERY -> showToast(getString(R.string.search_input_query_msg))
41 | MovieSearchViewModel.MessageSet.NETWORK_NOT_CONNECTED -> showToast(getString(R.string.network_error_msg))
42 | MovieSearchViewModel.MessageSet.SUCCESS -> showToast(getString(R.string.load_movie_success_msg))
43 | MovieSearchViewModel.MessageSet.NO_RESULT -> showToast(getString(R.string.no_movie_error_msg))
44 | MovieSearchViewModel.MessageSet.ERROR -> showToast(getString(R.string.error_msg))
45 | MovieSearchViewModel.MessageSet.LOCAL_SUCCESS -> showToast(getString(R.string.local_db_msg))
46 | }
47 | })
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/presentation/src/main/java/com/mtjin/presentation/views/search/MovieSearchViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.mtjin.presentation.views.search
2 |
3 | import android.util.Log
4 | import androidx.lifecycle.LiveData
5 | import androidx.lifecycle.MutableLiveData
6 | import com.mtjin.data.utils.LAST_PAGE
7 | import com.mtjin.domain.model.search.Movie
8 | import com.mtjin.domain.usecase.GetLocalMoviesUseCase
9 | import com.mtjin.domain.usecase.GetMoviesUseCase
10 | import com.mtjin.domain.usecase.GetPagingMoviesUseCase
11 | import com.mtjin.presentation.base.BaseViewModel
12 | import com.mtjin.presentation.utils.NetworkManager
13 | import dagger.hilt.android.lifecycle.HiltViewModel
14 | import io.reactivex.android.schedulers.AndroidSchedulers
15 | import io.reactivex.schedulers.Schedulers
16 | import javax.inject.Inject
17 |
18 | @HiltViewModel
19 | class MovieSearchViewModel @Inject constructor(
20 | private val getMoviesUseCase: GetMoviesUseCase,
21 | private val getPagingMoviesUseCase: GetPagingMoviesUseCase,
22 | private val getLocalMoviesUseCase: GetLocalMoviesUseCase,
23 | private val networkManager: NetworkManager
24 | ) : BaseViewModel() {
25 |
26 | private var currentQuery: String = "" // 현재 검색어
27 | val query = MutableLiveData() // 검색어(EditText two-way binding)
28 | private val _movieList = MutableLiveData>() // 영화리스트
29 | private val _toastMsg = MutableLiveData() //검색결과 토스트 메시지
30 |
31 | val movieList: LiveData> get() = _movieList
32 | val toastMsg: LiveData get() = _toastMsg
33 |
34 |
35 | // 영화검색 (15개)
36 | fun requestMovie() {
37 | currentQuery = query.value.toString().trim()
38 | if (currentQuery.isEmpty()) {
39 | _toastMsg.value = MessageSet.EMPTY_QUERY
40 | return
41 | }
42 | if (!checkNetworkState()) return //네트워크연결 유무
43 | compositeDisposable.add(
44 | getMoviesUseCase.execute(currentQuery)
45 | .subscribeOn(Schedulers.io())
46 | .observeOn(AndroidSchedulers.mainThread())
47 | .doOnSubscribe { showProgress() }
48 | .doAfterTerminate { hideProgress() }
49 | .subscribe({ movies ->
50 | if (movies.isEmpty()) {
51 | _toastMsg.value = MessageSet.NO_RESULT
52 | } else {
53 | _movieList.value = movies as ArrayList
54 | _toastMsg.value = MessageSet.SUCCESS
55 | }
56 | }, {
57 | Log.d("AAAAAAAAA11", it.message.toString())
58 | Log.d("AAAAAAAAA11", it.localizedMessage.toString())
59 | _toastMsg.value = MessageSet.ERROR
60 | })
61 | )
62 | }
63 |
64 | // 검색한 영화 더 불러오기(페이징, 무한스크롤)
65 | fun requestPagingMovie(offset: Int) {
66 | if (!checkNetworkState()) return //네트워크연결 유무
67 | compositeDisposable.add(
68 | getPagingMoviesUseCase.execute(currentQuery, offset)
69 | .subscribeOn(Schedulers.io())
70 | .observeOn(AndroidSchedulers.mainThread())
71 | .doOnSubscribe { showProgress() }
72 | .doAfterTerminate { hideProgress() }
73 | .subscribe({ movies ->
74 | val pagingMovieList = _movieList.value
75 | pagingMovieList?.addAll(movies)
76 | _movieList.value = pagingMovieList
77 | _toastMsg.value = MessageSet.SUCCESS
78 | }, {
79 | when (it.message) {
80 | LAST_PAGE -> _toastMsg.value = MessageSet.LAST_PAGE
81 | else -> {
82 | _toastMsg.value = MessageSet.ERROR
83 | Log.d("AAAAAAAAA22", it.message.toString())
84 | Log.d("AAAAAAAAA22", it.localizedMessage.toString())
85 | }
86 |
87 | }
88 | })
89 | )
90 | }
91 |
92 | private fun checkNetworkState(): Boolean {
93 | return if (networkManager.checkNetworkState()) {
94 | true
95 | } else {
96 | requestLocalMovies()
97 | false
98 | }
99 | }
100 |
101 | private fun requestLocalMovies() {
102 | compositeDisposable.add(
103 | getLocalMoviesUseCase.execute(currentQuery)
104 | .subscribeOn(Schedulers.io())
105 | .observeOn(AndroidSchedulers.mainThread())
106 | .doOnSubscribe { showProgress() }
107 | .doAfterTerminate { hideProgress() }
108 | .subscribe({ movies ->
109 | if (movies.isEmpty()) {
110 | _toastMsg.value = MessageSet.NETWORK_NOT_CONNECTED
111 | } else {
112 | _movieList.value = movies as ArrayList
113 | _toastMsg.value = MessageSet.LOCAL_SUCCESS
114 | }
115 | }, {
116 | _toastMsg.value = MessageSet.NETWORK_NOT_CONNECTED
117 | })
118 | )
119 | }
120 |
121 | enum class MessageSet {
122 | LAST_PAGE,
123 | EMPTY_QUERY,
124 | NETWORK_NOT_CONNECTED,
125 | ERROR,
126 | SUCCESS,
127 | NO_RESULT,
128 | LOCAL_SUCCESS
129 | }
130 |
131 | }
132 |
133 |
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/presentation/src/main/java/com/mtjin/presentation/views/splash/SplashActivity.kt:
--------------------------------------------------------------------------------
1 | package com.mtjin.presentation.views.splash
2 |
3 | import android.content.Intent
4 | import android.os.Bundle
5 | import android.widget.Toast
6 | import androidx.activity.viewModels
7 | import androidx.appcompat.app.AppCompatActivity
8 | import androidx.lifecycle.Observer
9 | import com.mtjin.presentation.R
10 | import com.mtjin.presentation.views.login.LoginActivity
11 | import com.mtjin.presentation.views.search.MovieSearchActivity
12 | import dagger.hilt.android.AndroidEntryPoint
13 |
14 | @AndroidEntryPoint
15 | open class SplashActivity : AppCompatActivity() {
16 | private val viewModel: SplashViewModel by viewModels()
17 |
18 | override fun onCreate(savedInstanceState: Bundle?) {
19 | super.onCreate(savedInstanceState)
20 | initViewModelCallback()
21 | viewModel.doSplash()
22 | }
23 |
24 | private fun initViewModelCallback() {
25 | with(viewModel) {
26 | goMovieSearch.observe(this@SplashActivity, Observer {
27 | goMovieSearch()
28 | })
29 | goLogin.observe(this@SplashActivity, Observer {
30 | goLogin()
31 | })
32 | }
33 | }
34 |
35 |
36 | private fun goLogin() {
37 | startActivity(Intent(this, LoginActivity::class.java))
38 | finish()
39 | }
40 |
41 | private fun goMovieSearch() {
42 | showToast(getString(R.string.auto_login_msg))
43 | startActivity(Intent(this, MovieSearchActivity::class.java))
44 | finish()
45 | }
46 |
47 | private fun showToast(msg: String) {
48 | Toast.makeText(this, msg, Toast.LENGTH_SHORT).show()
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/presentation/src/main/java/com/mtjin/presentation/views/splash/SplashViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.mtjin.presentation.views.splash
2 |
3 | import androidx.lifecycle.LiveData
4 | import androidx.lifecycle.MutableLiveData
5 | import androidx.lifecycle.ViewModel
6 | import com.mtjin.domain.usecase.GetLoginUseCase
7 | import dagger.hilt.android.lifecycle.HiltViewModel
8 | import javax.inject.Inject
9 |
10 | @HiltViewModel
11 | class SplashViewModel @Inject constructor(private val getLoginUserCase: GetLoginUseCase) :
12 | ViewModel() {
13 | private val _goMovieSearch: MutableLiveData = MutableLiveData()
14 | private val _goLogin: MutableLiveData = MutableLiveData()
15 |
16 | val goMovieSearch: LiveData get() = _goMovieSearch
17 | val goLogin: LiveData get() = _goLogin
18 |
19 | fun doSplash() {
20 | if (getLoginUserCase.execute()) { //자동로그인 가능 여부
21 | _goMovieSearch.value = Unit
22 | } else {
23 | _goLogin.value = Unit
24 | }
25 | }
26 | }
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/presentation/src/main/res/drawable-v24/bg_green_gradation.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/presentation/src/main/res/drawable-v24/bg_splash.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | -
8 |
10 |
11 |
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/presentation/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
13 |
19 |
22 |
25 |
26 |
27 |
28 |
34 |
35 |
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/presentation/src/main/res/drawable/ic_app_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mtjin/mtjin-android-clean-architecture-movieapp/edc3e6aad017063bf00ef17126eb9aae7100bdb8/AndroidArchitectureStudy/presentation/src/main/res/drawable/ic_app_logo.png
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/presentation/src/main/res/drawable/ic_default.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mtjin/mtjin-android-clean-architecture-movieapp/edc3e6aad017063bf00ef17126eb9aae7100bdb8/AndroidArchitectureStudy/presentation/src/main/res/drawable/ic_default.png
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/presentation/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 |
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/presentation/src/main/res/layout/activity_login.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
11 |
12 |
13 |
19 |
20 |
29 |
30 |
42 |
43 |
53 |
54 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/presentation/src/main/res/layout/activity_movie_search.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
10 |
11 |
14 |
15 |
16 |
21 |
22 |
28 |
29 |
41 |
42 |
53 |
54 |
68 |
69 |
79 |
80 |
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/presentation/src/main/res/layout/item_movie.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
10 |
11 |
12 |
18 |
19 |
26 |
27 |
33 |
34 |
41 |
42 |
53 |
54 |
60 |
61 |
67 |
68 |
74 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/presentation/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/presentation/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/presentation/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mtjin/mtjin-android-clean-architecture-movieapp/edc3e6aad017063bf00ef17126eb9aae7100bdb8/AndroidArchitectureStudy/presentation/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/presentation/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mtjin/mtjin-android-clean-architecture-movieapp/edc3e6aad017063bf00ef17126eb9aae7100bdb8/AndroidArchitectureStudy/presentation/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/presentation/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mtjin/mtjin-android-clean-architecture-movieapp/edc3e6aad017063bf00ef17126eb9aae7100bdb8/AndroidArchitectureStudy/presentation/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/presentation/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mtjin/mtjin-android-clean-architecture-movieapp/edc3e6aad017063bf00ef17126eb9aae7100bdb8/AndroidArchitectureStudy/presentation/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/presentation/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mtjin/mtjin-android-clean-architecture-movieapp/edc3e6aad017063bf00ef17126eb9aae7100bdb8/AndroidArchitectureStudy/presentation/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/presentation/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mtjin/mtjin-android-clean-architecture-movieapp/edc3e6aad017063bf00ef17126eb9aae7100bdb8/AndroidArchitectureStudy/presentation/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/presentation/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mtjin/mtjin-android-clean-architecture-movieapp/edc3e6aad017063bf00ef17126eb9aae7100bdb8/AndroidArchitectureStudy/presentation/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/presentation/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mtjin/mtjin-android-clean-architecture-movieapp/edc3e6aad017063bf00ef17126eb9aae7100bdb8/AndroidArchitectureStudy/presentation/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/presentation/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mtjin/mtjin-android-clean-architecture-movieapp/edc3e6aad017063bf00ef17126eb9aae7100bdb8/AndroidArchitectureStudy/presentation/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/presentation/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mtjin/mtjin-android-clean-architecture-movieapp/edc3e6aad017063bf00ef17126eb9aae7100bdb8/AndroidArchitectureStudy/presentation/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/presentation/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #008577
4 | #00574B
5 | #D81B60
6 | #000000
7 |
8 |
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/presentation/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | AndroidArchitectureStudy
3 | 33chRuAiqlSn5hn8tIme
4 | fyfwt9PCUN
5 | 검색어를 입력해주세요
6 | 네트워크를 연결해주세요.
7 | 영화를 불러왔습니다.
8 | 해당 영화는 존재하지 않습니다.
9 | 마지막 페이지 입니다
10 | 잠시만 기다려주세요.
11 | 자동 로그인
12 | 아이디 또는 패스워드가 틀렸습니다
13 | 아이디를 입력해주세요
14 | 비밀번호를 입력해주세요
15 | 로그인 성공
16 | 영화를 입력해주세요
17 | 검색
18 | 아이디(id 치세요)
19 | 패스워드(pass 치세요)
20 | 로그인
21 | 에러
22 | 네트워크가 불안정하여 로컬DB 에서 불러옵니다
23 |
24 |
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/presentation/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
12 |
13 |
14 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/presentation/src/test/java/com/mtjin/androidarchitecturestudy/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.mtjin.androidarchitecturestudy
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 | }
18 |
--------------------------------------------------------------------------------
/AndroidArchitectureStudy/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':domain'
2 | include ':data'
3 | include ':presentation'
4 | rootProject.name='AndroidArchitectureStudy'
5 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 JackJackE
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # # 안드로이드 클린아키텍처 공부 및 네이버 영화검색 앱 구현 (Main 브랜치 : Clean MVVM + RxJava2 + Hilt + Multi Module)
2 |
3 | ---
4 |
5 | ### 설명
6 |
7 | ---
8 |
9 | [이전 프로젝트](https://github.com/mtjin/android-architecture-study-movieapp)
10 |
11 | 이전에 안드로이드 아키텍처 스터디로 사용했던 프로젝트를 리펙토링하여 사용합니다.
12 |
13 | 클린아키텍처 관련 영상과 자료를 참고하여 공부하고 프로젝트에 적용해보며 학습합니다. 처음이라 미숙한 점이 많습니다. 😂
14 |
15 |
16 | ### 블로그 정리
17 |
18 | ---
19 | [블로그 정리](https://youngest-programming.tistory.com/484)
20 |
21 |
22 | ### 아키텍처
23 |
24 | ---
25 |
26 |
27 |
28 |
29 | [출처](https://proandroiddev.com/clean-architecture-data-flow-dependency-rule-615ffdd79e29)
30 |
31 | 위 사진의 아키텍처를 따르며 DataSource 로는 **로컬 DB, 캐시, 서버 DB** 로 구성 및 구현했다가 이 프로젝트에서는 캐시 데이터소스로 얻는 단점과 필요성이 적어 제거하게 되었습니다.
32 |
33 | 화면별 패키지는 splash, login, search가 있으며 **영화검색(search패키지) 위주로 구현**합니다.
34 |
35 |
36 |
37 | ### 해야할 리스트
38 |
39 | ---
40 |
41 | 1차 : Clean MVVM + RxJava2 + Koin [clean-mvvm-rxjava-koin 브랜치](https://github.com/mtjin/mtjin-android-clean-architecture-movieapp/tree/clean-mvvm-rxjava-koin)
42 |
43 | 2차 : 1차에서 모듈나누기 [Main] [clean-mvvm-multi-module 브랜치](https://github.com/mtjin/mtjin-android-clean-architecture-movieapp/tree/clean-mvvm-multi-module)
44 |
45 | 3차 : Koin → Dagger2 로 변경 [clean-mvvm-rxjava-dagger 브랜치](https://github.com/mtjin/mtjin-android-clean-architecture-movieapp/tree/clean-mvvm-rxjava-dagger)
46 |
47 | 4차 : Clean MVVM + RxJava2 + Hilt [clean-mvvm-multi-module-rxjava-hilt 브랜치](https://github.com/mtjin/mtjin-android-clean-architecture-movieapp/tree/clean-mvvm-multi-module-rxjava-hilt)
48 |
49 |
50 |
51 | ### 사용 및 공부한 것
52 |
53 | ---
54 |
55 | Android, Kotlin, MVVM, 클린아키텍처, RxJava2, RxAndroid, AAC ViewModel, LiveData, Koin, ListAdapter, Databinding, Retrofit2, Room, Dagger2, Hilt
56 |
--------------------------------------------------------------------------------