├── app ├── .gitignore ├── src │ └── main │ │ ├── res │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── values │ │ │ ├── dimens.xml │ │ │ ├── strings.xml │ │ │ ├── colors.xml │ │ │ └── styles.xml │ │ ├── layout │ │ │ ├── layout_frame.xml │ │ │ ├── layout_question_list_item.xml │ │ │ ├── layout_view_model.xml │ │ │ ├── layout_questions_list.xml │ │ │ ├── layout_my_toolbar.xml │ │ │ └── layout_question_details.xml │ │ ├── mipmap-anydpi-v26 │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ ├── drawable-v24 │ │ │ ├── ic_navigate_up.xml │ │ │ └── ic_launcher_foreground.xml │ │ └── drawable │ │ │ └── ic_launcher_background.xml │ │ ├── java │ │ └── com │ │ │ └── techyourchance │ │ │ └── dagger2course │ │ │ ├── common │ │ │ ├── dependnecyinjection │ │ │ │ ├── app │ │ │ │ │ ├── AppScope.kt │ │ │ │ │ ├── AppComponent.kt │ │ │ │ │ └── AppModule.kt │ │ │ │ ├── Retrofit1.kt │ │ │ │ ├── Retrofit2.kt │ │ │ │ ├── activity │ │ │ │ │ ├── ActivityScope.kt │ │ │ │ │ ├── ActivityComponent.kt │ │ │ │ │ └── ActivityModule.kt │ │ │ │ ├── presentation │ │ │ │ │ ├── PresentationScope.kt │ │ │ │ │ ├── ViewModelKey.kt │ │ │ │ │ ├── PresentationModule.kt │ │ │ │ │ ├── ViewModelModule.kt │ │ │ │ │ └── PresentationComponent.kt │ │ │ │ └── service │ │ │ │ │ ├── ServiceComponent.kt │ │ │ │ │ └── ServiceModule.kt │ │ │ └── service │ │ │ │ └── BaseService.kt │ │ │ ├── Constants.kt │ │ │ ├── screens │ │ │ ├── common │ │ │ │ ├── ScreensNavigator.kt │ │ │ │ ├── viewmodels │ │ │ │ │ ├── SavedStateViewModel.kt │ │ │ │ │ └── ViewModelFactory.kt │ │ │ │ ├── dialogs │ │ │ │ │ ├── DialogsNavigator.kt │ │ │ │ │ ├── BaseDialog.kt │ │ │ │ │ └── ServerErrorDialogFragment.kt │ │ │ │ ├── fragments │ │ │ │ │ └── BaseFragment.kt │ │ │ │ ├── imageloader │ │ │ │ │ └── ImageLoader.kt │ │ │ │ ├── ScreensNavigatorImpl.kt │ │ │ │ ├── activities │ │ │ │ │ └── BaseActivity.kt │ │ │ │ ├── viewsmvc │ │ │ │ │ ├── ViewMvcFactory.kt │ │ │ │ │ └── BaseViewMvc.kt │ │ │ │ └── toolbar │ │ │ │ │ └── MyToolbar.kt │ │ │ ├── questionslist │ │ │ │ ├── QuestionsListActivity.kt │ │ │ │ ├── QuestionsListFragment.kt │ │ │ │ └── QuestionsListViewMvc.kt │ │ │ ├── viewmodel │ │ │ │ ├── MyViewModel2.kt │ │ │ │ ├── MyViewModel.kt │ │ │ │ └── ViewModelActivity.kt │ │ │ └── questiondetails │ │ │ │ ├── QuestionDetailsViewMvc.kt │ │ │ │ └── QuestionDetailsActivity.kt │ │ │ ├── users │ │ │ └── User.kt │ │ │ ├── networking │ │ │ ├── QuestionsListResponseSchema.kt │ │ │ ├── UrlProvider.kt │ │ │ ├── SingleQuestionResponseSchema.kt │ │ │ └── StackoverflowApi.kt │ │ │ ├── questions │ │ │ ├── Question.kt │ │ │ ├── QuestionWithBody.kt │ │ │ ├── FetchQuestionsUseCase.kt │ │ │ └── FetchQuestionDetailsUseCase.kt │ │ │ └── MyApplication.kt │ │ └── AndroidManifest.xml ├── proguard-rules.pro └── build.gradle ├── settings.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitignore ├── gradle.properties ├── gradlew.bat └── gradlew /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techyourchance/course-android-dependency-injection-with-dagger-2/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techyourchance/course-android-dependency-injection-with-dagger-2/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techyourchance/course-android-dependency-injection-with-dagger-2/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techyourchance/course-android-dependency-injection-with-dagger-2/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techyourchance/course-android-dependency-injection-with-dagger-2/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techyourchance/course-android-dependency-injection-with-dagger-2/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techyourchance/course-android-dependency-injection-with-dagger-2/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techyourchance/course-android-dependency-injection-with-dagger-2/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techyourchance/course-android-dependency-injection-with-dagger-2/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techyourchance/course-android-dependency-injection-with-dagger-2/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techyourchance/course-android-dependency-injection-with-dagger-2/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 54dp 3 | 54dp 4 | 24dp 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/dagger2course/common/dependnecyinjection/app/AppScope.kt: -------------------------------------------------------------------------------- 1 | package com.techyourchance.dagger2course.common.dependnecyinjection.app 2 | 3 | import javax.inject.Scope 4 | 5 | @Scope 6 | annotation class AppScope { 7 | } -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/dagger2course/common/dependnecyinjection/Retrofit1.kt: -------------------------------------------------------------------------------- 1 | package com.techyourchance.dagger2course.common.dependnecyinjection 2 | 3 | import javax.inject.Qualifier 4 | 5 | @Qualifier 6 | annotation class Retrofit1 { 7 | } -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/dagger2course/common/dependnecyinjection/Retrofit2.kt: -------------------------------------------------------------------------------- 1 | package com.techyourchance.dagger2course.common.dependnecyinjection 2 | 3 | import javax.inject.Qualifier 4 | 5 | @Qualifier 6 | annotation class Retrofit2 { 7 | } -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/dagger2course/Constants.kt: -------------------------------------------------------------------------------- 1 | package com.techyourchance.dagger2course 2 | 3 | object Constants { 4 | const val BASE_URL = "https://api.stackexchange.com/2.2/" 5 | const val STACKOVERFLOW_API_KEY = "ZiXCZbWaOwnDgpVT9Hx8IA((" 6 | } -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/dagger2course/common/dependnecyinjection/activity/ActivityScope.kt: -------------------------------------------------------------------------------- 1 | package com.techyourchance.dagger2course.common.dependnecyinjection.activity 2 | 3 | import javax.inject.Scope 4 | 5 | @Scope 6 | annotation class ActivityScope { 7 | } -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/dagger2course/common/dependnecyinjection/presentation/PresentationScope.kt: -------------------------------------------------------------------------------- 1 | package com.techyourchance.dagger2course.common.dependnecyinjection.presentation 2 | 3 | import javax.inject.Scope 4 | 5 | @Scope 6 | annotation class PresentationScope { 7 | } -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/dagger2course/screens/common/ScreensNavigator.kt: -------------------------------------------------------------------------------- 1 | package com.techyourchance.dagger2course.screens.common 2 | 3 | interface ScreensNavigator { 4 | 5 | fun navigateBack() 6 | 7 | fun toQuestionDetails(questionId: String) 8 | fun toViewModel() 9 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Apr 15 12:21:12 IDT 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-8.1.1-all.zip 7 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_frame.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/dagger2course/common/dependnecyinjection/service/ServiceComponent.kt: -------------------------------------------------------------------------------- 1 | package com.techyourchance.dagger2course.common.dependnecyinjection.service 2 | 3 | import dagger.Subcomponent 4 | 5 | @Subcomponent(modules = [ServiceModule::class]) 6 | interface ServiceComponent { 7 | 8 | } -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/dagger2course/users/User.kt: -------------------------------------------------------------------------------- 1 | package com.techyourchance.dagger2course.users 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | data class User( 6 | @SerializedName("display_name") val name: String, 7 | @SerializedName("profile_image") val imageUrl: String 8 | ) -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Dagger 2 Course 3 | Server Error 4 | Communication with the server failed 5 | OK 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/dagger2course/networking/QuestionsListResponseSchema.kt: -------------------------------------------------------------------------------- 1 | package com.techyourchance.dagger2course.networking 2 | 3 | import com.google.gson.annotations.SerializedName 4 | import com.techyourchance.dagger2course.questions.Question 5 | 6 | data class QuestionsListResponseSchema(@SerializedName("items") val questions: List) -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/dagger2course/common/dependnecyinjection/presentation/ViewModelKey.kt: -------------------------------------------------------------------------------- 1 | package com.techyourchance.dagger2course.common.dependnecyinjection.presentation 2 | 3 | import androidx.lifecycle.ViewModel 4 | import dagger.MapKey 5 | import kotlin.reflect.KClass 6 | 7 | @MapKey 8 | annotation class ViewModelKey(val value: KClass) -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/dagger2course/questions/Question.kt: -------------------------------------------------------------------------------- 1 | package com.techyourchance.dagger2course.questions 2 | 3 | import com.google.gson.annotations.SerializedName 4 | import java.io.Serializable 5 | 6 | data class Question( 7 | @SerializedName("title") val title: String, 8 | @SerializedName("question_id") val id: String 9 | ): Serializable -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/dagger2course/screens/common/viewmodels/SavedStateViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.techyourchance.dagger2course.screens.common.viewmodels 2 | 3 | import androidx.lifecycle.SavedStateHandle 4 | import androidx.lifecycle.ViewModel 5 | 6 | abstract class SavedStateViewModel: ViewModel() { 7 | abstract fun init(savedStateHandle: SavedStateHandle) 8 | } -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/dagger2course/networking/UrlProvider.kt: -------------------------------------------------------------------------------- 1 | package com.techyourchance.dagger2course.networking 2 | 3 | import com.techyourchance.dagger2course.Constants 4 | 5 | class UrlProvider { 6 | 7 | fun getBaseUrl1(): String { 8 | return Constants.BASE_URL 9 | } 10 | 11 | fun getBaseUrl2(): String { 12 | return "base_url" 13 | } 14 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_navigate_up.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | #000000 8 | #FFFFFF 9 | #00000000 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/dagger2course/networking/SingleQuestionResponseSchema.kt: -------------------------------------------------------------------------------- 1 | package com.techyourchance.dagger2course.networking 2 | 3 | import com.google.gson.annotations.SerializedName 4 | import com.techyourchance.dagger2course.questions.QuestionWithBody 5 | 6 | data class SingleQuestionResponseSchema(@SerializedName("items") val questions: List) { 7 | val question: QuestionWithBody get() = questions[0] 8 | } -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/dagger2course/common/dependnecyinjection/service/ServiceModule.kt: -------------------------------------------------------------------------------- 1 | package com.techyourchance.dagger2course.common.dependnecyinjection.service 2 | 3 | import android.app.Service 4 | import android.content.Context 5 | import dagger.Module 6 | import dagger.Provides 7 | 8 | @Module 9 | class ServiceModule( 10 | val service: Service 11 | ) { 12 | 13 | @Provides 14 | fun context(): Context = service 15 | 16 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | #built application files 3 | *.apk 4 | *.ap_ 5 | 6 | # files for the dex VM 7 | *.dex 8 | 9 | # Java class files 10 | *.class 11 | 12 | # generated files 13 | bin/ 14 | gen/ 15 | 16 | # Local configuration file (sdk path, etc) 17 | local.properties 18 | 19 | # Windows thumbnail db 20 | Thumbs.db 21 | 22 | # OSX files 23 | .DS_Store 24 | 25 | # Android Studio 26 | .idea/ 27 | .gradle 28 | build/ 29 | *.iml 30 | captures/ 31 | .externalNativeBuild -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/dagger2course/common/dependnecyinjection/presentation/PresentationModule.kt: -------------------------------------------------------------------------------- 1 | package com.techyourchance.dagger2course.common.dependnecyinjection.presentation 2 | 3 | import androidx.savedstate.SavedStateRegistryOwner 4 | import dagger.Module 5 | import dagger.Provides 6 | 7 | @Module 8 | class PresentationModule(private val savedStateRegistryOwner: SavedStateRegistryOwner) { 9 | 10 | @Provides 11 | fun savedStateRegistryOwner() = savedStateRegistryOwner 12 | } -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/dagger2course/questions/QuestionWithBody.kt: -------------------------------------------------------------------------------- 1 | package com.techyourchance.dagger2course.questions 2 | 3 | import com.google.gson.annotations.SerializedName 4 | import com.techyourchance.dagger2course.users.User 5 | 6 | data class QuestionWithBody( 7 | @SerializedName("title") val title: String, 8 | @SerializedName("question_id") val id: String, 9 | @SerializedName("body") val body: String, 10 | @SerializedName("owner") val owner: User 11 | ) -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/dagger2course/screens/common/dialogs/DialogsNavigator.kt: -------------------------------------------------------------------------------- 1 | package com.techyourchance.dagger2course.screens.common.dialogs 2 | 3 | import androidx.fragment.app.FragmentManager 4 | import javax.inject.Inject 5 | 6 | class DialogsNavigator @Inject constructor(private val fragmentManager: FragmentManager) { 7 | 8 | fun showServerErrorDialog() { 9 | fragmentManager.beginTransaction() 10 | .add(ServerErrorDialogFragment.newInstance(), null) 11 | .commitAllowingStateLoss() 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/dagger2course/common/service/BaseService.kt: -------------------------------------------------------------------------------- 1 | package com.techyourchance.dagger2course.common.service 2 | 3 | import android.app.Service 4 | import com.techyourchance.dagger2course.MyApplication 5 | import com.techyourchance.dagger2course.common.dependnecyinjection.service.ServiceModule 6 | 7 | abstract class BaseService: Service() { 8 | 9 | private val appComponent get() = (application as MyApplication).appComponent 10 | 11 | val serviceComponent by lazy { 12 | appComponent.newServiceComponent(ServiceModule(this)) 13 | } 14 | 15 | } -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/dagger2course/screens/common/fragments/BaseFragment.kt: -------------------------------------------------------------------------------- 1 | package com.techyourchance.dagger2course.screens.common.fragments 2 | 3 | import androidx.fragment.app.Fragment 4 | import com.techyourchance.dagger2course.common.dependnecyinjection.presentation.PresentationModule 5 | import com.techyourchance.dagger2course.screens.common.activities.BaseActivity 6 | 7 | open class BaseFragment: Fragment() { 8 | 9 | private val presentationComponent by lazy { 10 | (requireActivity() as BaseActivity).activityComponent.newPresentationComponent(PresentationModule(this)) 11 | } 12 | 13 | protected val injector get() = presentationComponent 14 | } -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/dagger2course/screens/common/dialogs/BaseDialog.kt: -------------------------------------------------------------------------------- 1 | package com.techyourchance.dagger2course.screens.common.dialogs 2 | 3 | import androidx.fragment.app.DialogFragment 4 | import com.techyourchance.dagger2course.common.dependnecyinjection.presentation.PresentationModule 5 | import com.techyourchance.dagger2course.screens.common.activities.BaseActivity 6 | 7 | open class BaseDialog: DialogFragment() { 8 | 9 | private val presentationComponent by lazy { 10 | (requireActivity() as BaseActivity).activityComponent.newPresentationComponent(PresentationModule(this)) 11 | } 12 | 13 | protected val injector get() = presentationComponent 14 | } -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/dagger2course/MyApplication.kt: -------------------------------------------------------------------------------- 1 | package com.techyourchance.dagger2course 2 | 3 | import android.app.Application 4 | import com.techyourchance.dagger2course.common.dependnecyinjection.app.AppComponent 5 | import com.techyourchance.dagger2course.common.dependnecyinjection.app.AppModule 6 | import com.techyourchance.dagger2course.common.dependnecyinjection.app.DaggerAppComponent 7 | 8 | class MyApplication: Application() { 9 | 10 | public val appComponent: AppComponent by lazy { 11 | DaggerAppComponent.builder() 12 | .appModule(AppModule(this)) 13 | .build() 14 | } 15 | 16 | override fun onCreate() { 17 | super.onCreate() 18 | } 19 | 20 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_question_list_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/dagger2course/common/dependnecyinjection/app/AppComponent.kt: -------------------------------------------------------------------------------- 1 | package com.techyourchance.dagger2course.common.dependnecyinjection.app 2 | 3 | import com.techyourchance.dagger2course.common.dependnecyinjection.activity.ActivityComponent 4 | import com.techyourchance.dagger2course.common.dependnecyinjection.service.ServiceComponent 5 | import com.techyourchance.dagger2course.common.dependnecyinjection.service.ServiceModule 6 | import dagger.Component 7 | 8 | @AppScope 9 | @Component(modules = [AppModule::class]) 10 | interface AppComponent { 11 | 12 | fun newActivityComponentBuilder(): ActivityComponent.Builder 13 | 14 | fun newServiceComponent(serviceModule: ServiceModule): ServiceComponent 15 | 16 | } -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/dagger2course/screens/questionslist/QuestionsListActivity.kt: -------------------------------------------------------------------------------- 1 | package com.techyourchance.dagger2course.screens.questionslist 2 | 3 | import android.os.Bundle 4 | import com.techyourchance.dagger2course.R 5 | import com.techyourchance.dagger2course.screens.common.activities.BaseActivity 6 | 7 | class QuestionsListActivity : BaseActivity() { 8 | 9 | override fun onCreate(savedInstanceState: Bundle?) { 10 | super.onCreate(savedInstanceState) 11 | setContentView(R.layout.layout_frame) 12 | 13 | if (savedInstanceState == null) { 14 | supportFragmentManager.beginTransaction() 15 | .add(R.id.frame_content, QuestionsListFragment()) 16 | .commit() 17 | } 18 | 19 | } 20 | 21 | 22 | } -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/dagger2course/screens/common/imageloader/ImageLoader.kt: -------------------------------------------------------------------------------- 1 | package com.techyourchance.dagger2course.screens.common.imageloader 2 | 3 | import android.widget.ImageView 4 | import androidx.appcompat.app.AppCompatActivity 5 | import com.bumptech.glide.Glide 6 | import com.bumptech.glide.request.RequestOptions 7 | import com.techyourchance.dagger2course.common.dependnecyinjection.activity.ActivityScope 8 | import javax.inject.Inject 9 | 10 | @ActivityScope 11 | class ImageLoader @Inject constructor(private val activity: AppCompatActivity) { 12 | 13 | private val requestOptions = RequestOptions().centerCrop() 14 | 15 | fun loadImage(imageUrl: String, target: ImageView) { 16 | Glide.with(activity).load(imageUrl).apply(requestOptions).into(target) 17 | } 18 | } -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/dagger2course/networking/StackoverflowApi.kt: -------------------------------------------------------------------------------- 1 | package com.techyourchance.dagger2course.networking 2 | 3 | import com.techyourchance.dagger2course.Constants 4 | import retrofit2.Response 5 | import retrofit2.http.GET 6 | import retrofit2.http.Path 7 | import retrofit2.http.Query 8 | 9 | interface StackoverflowApi { 10 | @GET("/questions?key=" + Constants.STACKOVERFLOW_API_KEY + "&order=desc&sort=activity&site=stackoverflow") 11 | suspend fun lastActiveQuestions(@Query("pagesize") pageSize: Int?): Response 12 | 13 | @GET("/questions/{questionId}?key=" + Constants.STACKOVERFLOW_API_KEY + "&site=stackoverflow&filter=withbody") 14 | suspend fun questionDetails(@Path("questionId") questionId: String?): Response 15 | } -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/dagger2course/screens/common/ScreensNavigatorImpl.kt: -------------------------------------------------------------------------------- 1 | package com.techyourchance.dagger2course.screens.common 2 | 3 | import androidx.appcompat.app.AppCompatActivity 4 | import com.techyourchance.dagger2course.screens.questiondetails.QuestionDetailsActivity 5 | import com.techyourchance.dagger2course.screens.viewmodel.ViewModelActivity 6 | import javax.inject.Inject 7 | 8 | class ScreensNavigatorImpl @Inject constructor(private val activity: AppCompatActivity): ScreensNavigator { 9 | 10 | override fun navigateBack() { 11 | activity.onBackPressed() 12 | } 13 | 14 | override fun toQuestionDetails(questionId: String) { 15 | QuestionDetailsActivity.start(activity, questionId) 16 | } 17 | 18 | override fun toViewModel() { 19 | ViewModelActivity.start(activity) 20 | } 21 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/dagger2course/common/dependnecyinjection/presentation/ViewModelModule.kt: -------------------------------------------------------------------------------- 1 | package com.techyourchance.dagger2course.common.dependnecyinjection.presentation 2 | 3 | import androidx.lifecycle.ViewModel 4 | import com.techyourchance.dagger2course.screens.viewmodel.MyViewModel 5 | import com.techyourchance.dagger2course.screens.viewmodel.MyViewModel2 6 | import dagger.Binds 7 | import dagger.Module 8 | import dagger.Provides 9 | import dagger.multibindings.IntoMap 10 | 11 | @Module 12 | abstract class ViewModelModule { 13 | 14 | @Binds 15 | @IntoMap 16 | @ViewModelKey(MyViewModel::class) 17 | abstract fun myViewModel(myViewModel: MyViewModel): ViewModel 18 | 19 | @Binds 20 | @IntoMap 21 | @ViewModelKey(MyViewModel2::class) 22 | abstract fun myViewModel2(myViewModel2: MyViewModel2): ViewModel 23 | 24 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | android.enableJetifier=true 13 | android.useAndroidX=true 14 | org.gradle.jvmargs=-Xmx1536m 15 | 16 | # When configured, Gradle will run in incubating parallel mode. 17 | # This option should only be used with decoupled projects. More details, visit 18 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 19 | # org.gradle.parallel=true 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/dagger2course/screens/viewmodel/MyViewModel2.kt: -------------------------------------------------------------------------------- 1 | package com.techyourchance.dagger2course.screens.viewmodel 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.lifecycle.MutableLiveData 5 | import androidx.lifecycle.ViewModel 6 | import com.techyourchance.dagger2course.questions.FetchQuestionDetailsUseCase 7 | import com.techyourchance.dagger2course.questions.FetchQuestionsUseCase 8 | import com.techyourchance.dagger2course.questions.Question 9 | import javax.inject.Inject 10 | 11 | class MyViewModel2 @Inject constructor( 12 | private val fetchQuestionsUseCase: FetchQuestionsUseCase, 13 | private val fetchQuestionDetailsUseCase: FetchQuestionDetailsUseCase 14 | ): ViewModel() { 15 | 16 | private val _questions = MutableLiveData>() 17 | val questions: LiveData> = _questions 18 | 19 | } -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/dagger2course/screens/common/activities/BaseActivity.kt: -------------------------------------------------------------------------------- 1 | package com.techyourchance.dagger2course.screens.common.activities 2 | 3 | import androidx.appcompat.app.AppCompatActivity 4 | import com.techyourchance.dagger2course.MyApplication 5 | import com.techyourchance.dagger2course.common.dependnecyinjection.presentation.PresentationModule 6 | 7 | open class BaseActivity: AppCompatActivity() { 8 | 9 | private val appComponent get() = (application as MyApplication).appComponent 10 | 11 | val activityComponent by lazy { 12 | appComponent.newActivityComponentBuilder() 13 | .activity(this) 14 | .build() 15 | } 16 | 17 | private val presentationComponent by lazy { 18 | activityComponent.newPresentationComponent(PresentationModule(this)) 19 | } 20 | 21 | protected val injector get() = presentationComponent 22 | } -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/dagger2course/common/dependnecyinjection/activity/ActivityComponent.kt: -------------------------------------------------------------------------------- 1 | package com.techyourchance.dagger2course.common.dependnecyinjection.activity 2 | 3 | import androidx.appcompat.app.AppCompatActivity 4 | import com.techyourchance.dagger2course.common.dependnecyinjection.presentation.PresentationComponent 5 | import com.techyourchance.dagger2course.common.dependnecyinjection.presentation.PresentationModule 6 | import dagger.BindsInstance 7 | import dagger.Subcomponent 8 | 9 | @ActivityScope 10 | @Subcomponent(modules = [ActivityModule::class]) 11 | interface ActivityComponent { 12 | 13 | fun newPresentationComponent(presentationModule: PresentationModule): PresentationComponent 14 | 15 | @Subcomponent.Builder 16 | interface Builder { 17 | @BindsInstance fun activity(activity: AppCompatActivity): Builder 18 | fun build(): ActivityComponent 19 | } 20 | 21 | } -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/dagger2course/common/dependnecyinjection/presentation/PresentationComponent.kt: -------------------------------------------------------------------------------- 1 | package com.techyourchance.dagger2course.common.dependnecyinjection.presentation 2 | 3 | import com.techyourchance.dagger2course.screens.questiondetails.QuestionDetailsActivity 4 | import com.techyourchance.dagger2course.screens.questionslist.QuestionsListActivity 5 | import com.techyourchance.dagger2course.screens.questionslist.QuestionsListFragment 6 | import com.techyourchance.dagger2course.screens.viewmodel.ViewModelActivity 7 | import dagger.Subcomponent 8 | 9 | @PresentationScope 10 | @Subcomponent(modules = [PresentationModule::class, ViewModelModule::class]) 11 | interface PresentationComponent { 12 | fun inject(fragment: QuestionsListFragment) 13 | fun inject(activity: QuestionDetailsActivity) 14 | fun inject(questionsListActivity: QuestionsListActivity) 15 | fun inject(viewModelActivity: ViewModelActivity) 16 | } -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/dagger2course/screens/common/dialogs/ServerErrorDialogFragment.kt: -------------------------------------------------------------------------------- 1 | package com.techyourchance.dagger2course.screens.common.dialogs 2 | 3 | import android.app.AlertDialog 4 | import android.app.Dialog 5 | import android.os.Bundle 6 | import com.techyourchance.dagger2course.R 7 | 8 | class ServerErrorDialogFragment : BaseDialog() { 9 | 10 | override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { 11 | return AlertDialog.Builder(activity).let { 12 | it.setTitle(R.string.server_error_dialog_title) 13 | it.setMessage(R.string.server_error_dialog_message) 14 | it.setPositiveButton(R.string.server_error_dialog_button_caption) { _, _ -> dismiss() } 15 | it.create() 16 | } 17 | } 18 | 19 | companion object { 20 | fun newInstance(): ServerErrorDialogFragment { 21 | return ServerErrorDialogFragment() 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/dagger2course/common/dependnecyinjection/activity/ActivityModule.kt: -------------------------------------------------------------------------------- 1 | package com.techyourchance.dagger2course.common.dependnecyinjection.activity 2 | 3 | import android.view.LayoutInflater 4 | import androidx.appcompat.app.AppCompatActivity 5 | import com.techyourchance.dagger2course.screens.common.ScreensNavigator 6 | import com.techyourchance.dagger2course.screens.common.ScreensNavigatorImpl 7 | import dagger.Binds 8 | import dagger.Module 9 | import dagger.Provides 10 | 11 | @Module 12 | abstract class ActivityModule { 13 | 14 | @ActivityScope 15 | @Binds 16 | abstract fun screensNavigator(screensNavigatorImpl: ScreensNavigatorImpl): ScreensNavigator 17 | 18 | companion object { 19 | @Provides 20 | fun layoutInflater(activity: AppCompatActivity) = LayoutInflater.from(activity) 21 | 22 | @Provides 23 | fun fragmentManager(activity: AppCompatActivity) = activity.supportFragmentManager 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_view_model.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 14 | 15 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_questions_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | 17 | 18 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/dagger2course/screens/common/viewsmvc/ViewMvcFactory.kt: -------------------------------------------------------------------------------- 1 | package com.techyourchance.dagger2course.screens.common.viewsmvc 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import com.techyourchance.dagger2course.screens.common.imageloader.ImageLoader 6 | import com.techyourchance.dagger2course.screens.questiondetails.QuestionDetailsViewMvc 7 | import com.techyourchance.dagger2course.screens.questionslist.QuestionsListViewMvc 8 | import javax.inject.Inject 9 | import javax.inject.Provider 10 | 11 | class ViewMvcFactory @Inject constructor( 12 | private val layoutInflaterProvider: Provider, 13 | private val imageLoaderProvider: Provider 14 | ) { 15 | 16 | fun newQuestionsListViewMvc(parent: ViewGroup?): QuestionsListViewMvc { 17 | return QuestionsListViewMvc(layoutInflaterProvider.get(), parent) 18 | } 19 | 20 | fun newQuestionDetailsViewMvc(parent: ViewGroup?): QuestionDetailsViewMvc { 21 | return QuestionDetailsViewMvc(layoutInflaterProvider.get(), imageLoaderProvider.get(), parent) 22 | } 23 | } -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/dagger2course/screens/common/viewsmvc/BaseViewMvc.kt: -------------------------------------------------------------------------------- 1 | package com.techyourchance.dagger2course.screens.common.viewsmvc 2 | 3 | import android.content.Context 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import androidx.annotation.IdRes 8 | import androidx.annotation.LayoutRes 9 | 10 | open class BaseViewMvc( 11 | private val layoutInflater: LayoutInflater, 12 | private val parent: ViewGroup?, 13 | @LayoutRes private val layoutId: Int 14 | ) { 15 | 16 | val rootView: View = layoutInflater.inflate(layoutId, parent, false) 17 | 18 | protected val context: Context get() = rootView.context 19 | 20 | protected val listeners = HashSet() 21 | 22 | 23 | fun registerListener(listener: LISTENER_TYPE) { 24 | listeners.add(listener) 25 | } 26 | 27 | fun unregisterListener(listener: LISTENER_TYPE) { 28 | listeners.remove(listener) 29 | } 30 | 31 | protected fun findViewById(@IdRes id: Int): T { 32 | return rootView.findViewById(id) 33 | } 34 | 35 | } -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/dagger2course/screens/common/viewmodels/ViewModelFactory.kt: -------------------------------------------------------------------------------- 1 | package com.techyourchance.dagger2course.screens.common.viewmodels 2 | 3 | import androidx.lifecycle.AbstractSavedStateViewModelFactory 4 | import androidx.lifecycle.SavedStateHandle 5 | import androidx.lifecycle.ViewModel 6 | import androidx.savedstate.SavedStateRegistryOwner 7 | import javax.inject.Inject 8 | import javax.inject.Provider 9 | 10 | class ViewModelFactory @Inject constructor( 11 | private val providersMap: Map, @JvmSuppressWildcards Provider>, 12 | savedStateRegistryOwner: SavedStateRegistryOwner 13 | ): AbstractSavedStateViewModelFactory(savedStateRegistryOwner, null) { 14 | 15 | override fun create(key: String, modelClass: Class, handle: SavedStateHandle): T { 16 | val provider = providersMap[modelClass] 17 | val viewModel = provider?.get() ?: throw RuntimeException("unsupported viewmodel type: $modelClass") 18 | if (viewModel is SavedStateViewModel) { 19 | viewModel.init(handle) 20 | } 21 | return viewModel as T 22 | } 23 | 24 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 14 | 15 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/dagger2course/questions/FetchQuestionsUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.techyourchance.dagger2course.questions 2 | 3 | import com.techyourchance.dagger2course.networking.StackoverflowApi 4 | import kotlinx.coroutines.CancellationException 5 | import kotlinx.coroutines.Dispatchers 6 | import kotlinx.coroutines.withContext 7 | import javax.inject.Inject 8 | 9 | class FetchQuestionsUseCase @Inject constructor(private val stackoverflowApi: StackoverflowApi) { 10 | 11 | sealed class Result { 12 | data class Success(val questions: List) : Result() 13 | object Failure: Result() 14 | } 15 | 16 | suspend fun fetchLatestQuestions(): Result { 17 | return withContext(Dispatchers.IO) { 18 | try { 19 | val response = stackoverflowApi.lastActiveQuestions(20) 20 | if (response.isSuccessful && response.body() != null) { 21 | return@withContext Result.Success(response.body()!!.questions) 22 | } else { 23 | return@withContext Result.Failure 24 | } 25 | } catch (t: Throwable) { 26 | if (t !is CancellationException) { 27 | return@withContext Result.Failure 28 | } else { 29 | throw t 30 | } 31 | } 32 | } 33 | } 34 | 35 | 36 | } -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/dagger2course/questions/FetchQuestionDetailsUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.techyourchance.dagger2course.questions 2 | 3 | import com.techyourchance.dagger2course.networking.StackoverflowApi 4 | import kotlinx.coroutines.CancellationException 5 | import kotlinx.coroutines.Dispatchers 6 | import kotlinx.coroutines.withContext 7 | import javax.inject.Inject 8 | 9 | class FetchQuestionDetailsUseCase @Inject constructor(private val stackoverflowApi: StackoverflowApi) { 10 | 11 | sealed class Result { 12 | data class Success(val question: QuestionWithBody) : Result() 13 | object Failure: Result() 14 | } 15 | 16 | suspend fun fetchQuestion(questionId: String): Result { 17 | return withContext(Dispatchers.IO) { 18 | try { 19 | val response = stackoverflowApi.questionDetails(questionId) 20 | if (response.isSuccessful && response.body() != null) { 21 | return@withContext Result.Success(response.body()!!.question) 22 | } else { 23 | return@withContext Result.Failure 24 | } 25 | } catch (t: Throwable) { 26 | if (t !is CancellationException) { 27 | return@withContext Result.Failure 28 | } else { 29 | throw t 30 | } 31 | } 32 | } 33 | } 34 | 35 | 36 | } -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/dagger2course/screens/viewmodel/MyViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.techyourchance.dagger2course.screens.viewmodel 2 | 3 | import androidx.lifecycle.* 4 | import com.techyourchance.dagger2course.questions.FetchQuestionDetailsUseCase 5 | import com.techyourchance.dagger2course.questions.FetchQuestionsUseCase 6 | import com.techyourchance.dagger2course.questions.Question 7 | import com.techyourchance.dagger2course.screens.common.viewmodels.SavedStateViewModel 8 | import kotlinx.coroutines.delay 9 | import kotlinx.coroutines.launch 10 | import javax.inject.Inject 11 | 12 | class MyViewModel @Inject constructor( 13 | private val fetchQuestionsUseCase: FetchQuestionsUseCase, 14 | private val fetchQuestionDetailsUseCase: FetchQuestionDetailsUseCase 15 | ): SavedStateViewModel() { 16 | 17 | private lateinit var _questions: MutableLiveData> 18 | val questions: LiveData> get() = _questions 19 | 20 | override fun init(savedStateHandle: SavedStateHandle) { 21 | _questions = savedStateHandle.getLiveData("questions") 22 | 23 | viewModelScope.launch { 24 | delay(5000) 25 | val result = fetchQuestionsUseCase.fetchLatestQuestions() 26 | if (result is FetchQuestionsUseCase.Result.Success) { 27 | _questions.value = result.questions 28 | } else { 29 | throw RuntimeException("fetch failed") 30 | } 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/dagger2course/common/dependnecyinjection/app/AppModule.kt: -------------------------------------------------------------------------------- 1 | package com.techyourchance.dagger2course.common.dependnecyinjection.app 2 | 3 | import android.app.Application 4 | import com.techyourchance.dagger2course.common.dependnecyinjection.Retrofit1 5 | import com.techyourchance.dagger2course.common.dependnecyinjection.Retrofit2 6 | import com.techyourchance.dagger2course.networking.StackoverflowApi 7 | import com.techyourchance.dagger2course.networking.UrlProvider 8 | import dagger.Module 9 | import dagger.Provides 10 | import retrofit2.Retrofit 11 | import retrofit2.converter.gson.GsonConverterFactory 12 | 13 | @Module 14 | class AppModule(val application: Application) { 15 | 16 | @Provides 17 | @AppScope 18 | @Retrofit1 19 | fun retrofit1(urlProvider: UrlProvider): Retrofit { 20 | return Retrofit.Builder() 21 | .baseUrl(urlProvider.getBaseUrl1()) 22 | .addConverterFactory(GsonConverterFactory.create()) 23 | .build() 24 | } 25 | 26 | @Provides 27 | @AppScope 28 | @Retrofit2 29 | fun retrofit2(urlProvider: UrlProvider): Retrofit { 30 | return Retrofit.Builder() 31 | .baseUrl(urlProvider.getBaseUrl2()) 32 | .addConverterFactory(GsonConverterFactory.create()) 33 | .build() 34 | } 35 | 36 | @AppScope 37 | @Provides 38 | fun urlProvider() = UrlProvider() 39 | 40 | @Provides 41 | fun application() = application 42 | 43 | @Provides 44 | @AppScope 45 | fun stackoverflowApi(@Retrofit1 retrofit: Retrofit) = retrofit.create(StackoverflowApi::class.java) 46 | 47 | } -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-kapt' 4 | 5 | android { 6 | namespace "com.techyourchance.dagger2course" 7 | compileSdk 34 8 | defaultConfig { 9 | minSdkVersion 19 10 | targetSdkVersion 34 11 | versionCode 1 12 | versionName "1.0" 13 | vectorDrawables.useSupportLibrary = true 14 | } 15 | buildTypes { 16 | release { 17 | minifyEnabled false 18 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 19 | } 20 | } 21 | compileOptions { 22 | sourceCompatibility = 17 23 | targetCompatibility = 17 24 | } 25 | 26 | kotlinOptions { 27 | jvmTarget = 17 28 | } 29 | } 30 | 31 | dependencies { 32 | implementation fileTree(dir: 'libs', include: ['*.jar']) 33 | 34 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 35 | 36 | // DI 37 | api "com.google.dagger:dagger:$dagger_version" 38 | kapt "com.google.dagger:dagger-compiler:$dagger_version" 39 | 40 | // Image loading 41 | implementation 'com.github.bumptech.glide:glide:4.11.0' 42 | annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0' 43 | 44 | implementation 'androidx.appcompat:appcompat:1.6.1' 45 | implementation 'androidx.recyclerview:recyclerview:1.2.1' 46 | implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' 47 | implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2' 48 | implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:2.6.2" 49 | 50 | implementation 'com.squareup.retrofit2:retrofit:2.8.2' 51 | implementation 'com.squareup.retrofit2:converter-gson:2.8.2' 52 | 53 | implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3' 54 | implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3' 55 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_my_toolbar.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 15 | 16 | 21 | 22 | 23 | 24 | 39 | 40 | 48 | 49 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/dagger2course/screens/viewmodel/ViewModelActivity.kt: -------------------------------------------------------------------------------- 1 | package com.techyourchance.dagger2course.screens.viewmodel 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import android.os.Bundle 6 | import android.widget.Toast 7 | import androidx.lifecycle.Observer 8 | import androidx.lifecycle.ViewModelProvider 9 | import com.techyourchance.dagger2course.R 10 | import com.techyourchance.dagger2course.screens.common.ScreensNavigator 11 | import com.techyourchance.dagger2course.screens.common.activities.BaseActivity 12 | import com.techyourchance.dagger2course.screens.common.toolbar.MyToolbar 13 | import com.techyourchance.dagger2course.screens.common.viewmodels.ViewModelFactory 14 | import javax.inject.Inject 15 | 16 | class ViewModelActivity : BaseActivity() { 17 | 18 | @Inject lateinit var screensNavigator: ScreensNavigator 19 | @Inject lateinit var myViewModelFactory: ViewModelFactory 20 | 21 | private lateinit var myViewModel: MyViewModel 22 | private lateinit var myViewModel2: MyViewModel2 23 | 24 | private lateinit var toolbar: MyToolbar 25 | 26 | override fun onCreate(savedInstanceState: Bundle?) { 27 | injector.inject(this) 28 | super.onCreate(savedInstanceState) 29 | 30 | setContentView(R.layout.layout_view_model) 31 | 32 | toolbar = findViewById(R.id.toolbar) 33 | toolbar.setNavigateUpListener { 34 | screensNavigator.navigateBack() 35 | } 36 | 37 | myViewModel = ViewModelProvider(this, myViewModelFactory).get(MyViewModel::class.java) 38 | myViewModel2 = ViewModelProvider(this, myViewModelFactory).get(MyViewModel2::class.java) 39 | 40 | myViewModel.questions.observe(this, Observer { 41 | questions -> Toast.makeText(this, "fetched ${questions.size} questions", Toast.LENGTH_SHORT).show() 42 | }) 43 | } 44 | 45 | companion object { 46 | fun start(context: Context) { 47 | val intent = Intent(context, ViewModelActivity::class.java) 48 | context.startActivity(intent) 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/dagger2course/screens/common/toolbar/MyToolbar.kt: -------------------------------------------------------------------------------- 1 | package com.techyourchance.dagger2course.screens.common.toolbar 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.widget.FrameLayout 8 | import android.widget.TextView 9 | import androidx.appcompat.widget.Toolbar 10 | import com.techyourchance.dagger2course.R 11 | 12 | class MyToolbar : Toolbar { 13 | 14 | interface NavigateUpListener { 15 | fun onNavigationUpClicked() 16 | } 17 | 18 | interface ViewModelListener { 19 | fun onViewModelClicked() 20 | } 21 | 22 | private var navigateUpListener: () -> Unit = {} 23 | private var viewModelListener: () -> Unit = {} 24 | 25 | private lateinit var navigateUp: FrameLayout 26 | private lateinit var viewmodel: TextView 27 | 28 | constructor(context: Context) : super(context) { 29 | init(context) 30 | } 31 | 32 | constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) { 33 | init(context) 34 | } 35 | 36 | constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { 37 | init(context) 38 | } 39 | 40 | private fun init(context: Context) { 41 | val view = LayoutInflater.from(context).inflate(R.layout.layout_my_toolbar, this, true) 42 | setContentInsetsRelative(0, 0) 43 | navigateUp = view.findViewById(R.id.navigate_up) 44 | navigateUp.setOnClickListener { navigateUpListener.invoke() } 45 | viewmodel = view.findViewById(R.id.viewmodel) 46 | viewmodel.setOnClickListener { viewModelListener.invoke() } 47 | } 48 | 49 | fun setNavigateUpListener(navigateUpListener: () -> Unit) { 50 | this.navigateUpListener = navigateUpListener 51 | navigateUp.visibility = View.VISIBLE 52 | } 53 | 54 | fun setViewModelListener(viewModelListener: () -> Unit) { 55 | this.viewModelListener = viewModelListener 56 | viewmodel.visibility = View.VISIBLE 57 | } 58 | 59 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_question_details.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 14 | 15 | 19 | 20 | 23 | 24 | 28 | 29 | 35 | 36 | 47 | 48 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /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 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 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 Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/dagger2course/screens/questiondetails/QuestionDetailsViewMvc.kt: -------------------------------------------------------------------------------- 1 | package com.techyourchance.dagger2course.screens.questiondetails 2 | 3 | import android.os.Build 4 | import android.text.Html 5 | import android.view.LayoutInflater 6 | import android.view.ViewGroup 7 | import android.widget.ImageView 8 | import android.widget.TextView 9 | import androidx.swiperefreshlayout.widget.SwipeRefreshLayout 10 | import com.techyourchance.dagger2course.R 11 | import com.techyourchance.dagger2course.questions.QuestionWithBody 12 | import com.techyourchance.dagger2course.screens.common.imageloader.ImageLoader 13 | import com.techyourchance.dagger2course.screens.common.toolbar.MyToolbar 14 | import com.techyourchance.dagger2course.screens.common.viewsmvc.BaseViewMvc 15 | 16 | class QuestionDetailsViewMvc( 17 | layoutInflater: LayoutInflater, 18 | private val imageLoader: ImageLoader, 19 | parent: ViewGroup? 20 | ): BaseViewMvc( 21 | layoutInflater, 22 | parent, 23 | R.layout.layout_question_details 24 | ) { 25 | 26 | interface Listener { 27 | fun onBackClicked() 28 | } 29 | 30 | private val toolbar: MyToolbar 31 | private val swipeRefresh: SwipeRefreshLayout 32 | private val txtQuestionBody: TextView 33 | private val imgUser: ImageView 34 | private val txtUserName: TextView 35 | 36 | init { 37 | txtQuestionBody = findViewById(R.id.txt_question_body) 38 | 39 | // init toolbar 40 | toolbar = findViewById(R.id.toolbar) 41 | toolbar.setNavigateUpListener { 42 | for (listener in listeners) { 43 | listener.onBackClicked() 44 | } 45 | } 46 | 47 | // init pull-down-to-refresh (used as a progress indicator) 48 | swipeRefresh = findViewById(R.id.swipeRefresh) 49 | swipeRefresh.isEnabled = false 50 | 51 | imgUser = findViewById(R.id.img_user) 52 | txtUserName = findViewById(R.id.txt_user_name) 53 | } 54 | 55 | fun bindQuestionWithBody(question: QuestionWithBody) { 56 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { 57 | txtQuestionBody.text = Html.fromHtml(question.body, Html.FROM_HTML_MODE_LEGACY) 58 | } else { 59 | @Suppress("DEPRECATION") 60 | txtQuestionBody.text = Html.fromHtml(question.body) 61 | } 62 | imageLoader.loadImage(question.owner.imageUrl, imgUser) 63 | txtUserName.text = question.owner.name 64 | } 65 | 66 | fun showProgressIndication() { 67 | swipeRefresh.isRefreshing = true 68 | } 69 | 70 | fun hideProgressIndication() { 71 | if (swipeRefresh.isRefreshing) { 72 | swipeRefresh.isRefreshing = false 73 | } 74 | } 75 | } -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/dagger2course/screens/questiondetails/QuestionDetailsActivity.kt: -------------------------------------------------------------------------------- 1 | package com.techyourchance.dagger2course.screens.questiondetails 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import android.os.Bundle 6 | import com.techyourchance.dagger2course.questions.FetchQuestionDetailsUseCase 7 | import com.techyourchance.dagger2course.screens.common.ScreensNavigator 8 | import com.techyourchance.dagger2course.screens.common.activities.BaseActivity 9 | import com.techyourchance.dagger2course.screens.common.dialogs.DialogsNavigator 10 | import com.techyourchance.dagger2course.screens.common.viewsmvc.ViewMvcFactory 11 | import kotlinx.coroutines.* 12 | import javax.inject.Inject 13 | 14 | class QuestionDetailsActivity : BaseActivity(), QuestionDetailsViewMvc.Listener { 15 | 16 | private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate) 17 | 18 | @Inject lateinit var fetchQuestionDetailsUseCase: FetchQuestionDetailsUseCase 19 | @Inject lateinit var dialogsNavigator: DialogsNavigator 20 | @Inject lateinit var screensNavigator: ScreensNavigator 21 | @Inject lateinit var viewMvcFactory: ViewMvcFactory 22 | 23 | private lateinit var viewMvc: QuestionDetailsViewMvc 24 | 25 | private lateinit var questionId: String 26 | 27 | override fun onCreate(savedInstanceState: Bundle?) { 28 | injector.inject(this) 29 | super.onCreate(savedInstanceState) 30 | viewMvc = viewMvcFactory.newQuestionDetailsViewMvc(null) 31 | setContentView(viewMvc.rootView) 32 | 33 | questionId = intent.extras!!.getString(EXTRA_QUESTION_ID)!! 34 | } 35 | 36 | override fun onStart() { 37 | super.onStart() 38 | viewMvc.registerListener(this) 39 | fetchQuestionDetails() 40 | } 41 | 42 | override fun onStop() { 43 | super.onStop() 44 | coroutineScope.coroutineContext.cancelChildren() 45 | viewMvc.unregisterListener(this) 46 | } 47 | 48 | private fun fetchQuestionDetails() { 49 | coroutineScope.launch { 50 | viewMvc.showProgressIndication() 51 | try { 52 | val result = fetchQuestionDetailsUseCase.fetchQuestion(questionId) 53 | when(result) { 54 | is FetchQuestionDetailsUseCase.Result.Success -> { 55 | viewMvc.bindQuestionWithBody(result.question) 56 | } 57 | is FetchQuestionDetailsUseCase.Result.Failure -> onFetchFailed() 58 | } 59 | } finally { 60 | viewMvc.hideProgressIndication() 61 | } 62 | 63 | } 64 | } 65 | 66 | private fun onFetchFailed() { 67 | dialogsNavigator.showServerErrorDialog() 68 | } 69 | 70 | override fun onBackClicked() { 71 | screensNavigator.navigateBack() 72 | } 73 | 74 | companion object { 75 | const val EXTRA_QUESTION_ID = "EXTRA_QUESTION_ID" 76 | fun start(context: Context, questionId: String) { 77 | val intent = Intent(context, QuestionDetailsActivity::class.java) 78 | intent.putExtra(EXTRA_QUESTION_ID, questionId) 79 | context.startActivity(intent) 80 | } 81 | } 82 | } -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/dagger2course/screens/questionslist/QuestionsListFragment.kt: -------------------------------------------------------------------------------- 1 | package com.techyourchance.dagger2course.screens.questionslist 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import com.techyourchance.dagger2course.questions.FetchQuestionsUseCase 8 | import com.techyourchance.dagger2course.questions.Question 9 | import com.techyourchance.dagger2course.screens.common.ScreensNavigator 10 | import com.techyourchance.dagger2course.screens.common.dialogs.DialogsNavigator 11 | import com.techyourchance.dagger2course.screens.common.fragments.BaseFragment 12 | import com.techyourchance.dagger2course.screens.common.viewsmvc.ViewMvcFactory 13 | import kotlinx.coroutines.* 14 | import javax.inject.Inject 15 | 16 | class QuestionsListFragment : BaseFragment(), QuestionsListViewMvc.Listener { 17 | 18 | private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate) 19 | 20 | @Inject lateinit var fetchQuestionsUseCase: FetchQuestionsUseCase 21 | @Inject lateinit var dialogsNavigator: DialogsNavigator 22 | @Inject lateinit var screensNavigator: ScreensNavigator 23 | @Inject lateinit var viewMvcFactory: ViewMvcFactory 24 | 25 | private lateinit var viewMvc: QuestionsListViewMvc 26 | 27 | private var isDataLoaded = false 28 | 29 | override fun onCreate(savedInstanceState: Bundle?) { 30 | injector.inject(this) 31 | super.onCreate(savedInstanceState) 32 | } 33 | 34 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { 35 | viewMvc = viewMvcFactory.newQuestionsListViewMvc(container) 36 | return viewMvc.rootView 37 | } 38 | 39 | override fun onStart() { 40 | super.onStart() 41 | viewMvc.registerListener(this) 42 | if (!isDataLoaded) { 43 | fetchQuestions() 44 | } 45 | } 46 | 47 | override fun onStop() { 48 | super.onStop() 49 | coroutineScope.coroutineContext.cancelChildren() 50 | viewMvc.unregisterListener(this) 51 | } 52 | 53 | override fun onRefreshClicked() { 54 | fetchQuestions() 55 | } 56 | 57 | private fun fetchQuestions() { 58 | coroutineScope.launch { 59 | viewMvc.showProgressIndication() 60 | try { 61 | val result = fetchQuestionsUseCase.fetchLatestQuestions() 62 | when (result) { 63 | is FetchQuestionsUseCase.Result.Success -> { 64 | viewMvc.bindQuestions(result.questions) 65 | isDataLoaded = true 66 | } 67 | is FetchQuestionsUseCase.Result.Failure -> onFetchFailed() 68 | } 69 | } finally { 70 | viewMvc.hideProgressIndication() 71 | } 72 | } 73 | } 74 | 75 | private fun onFetchFailed() { 76 | dialogsNavigator.showServerErrorDialog() 77 | } 78 | 79 | override fun onQuestionClicked(clickedQuestion: Question) { 80 | screensNavigator.toQuestionDetails(clickedQuestion.id) 81 | } 82 | 83 | override fun onViewModelClicked() { 84 | screensNavigator.toViewModel() 85 | } 86 | } -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/dagger2course/screens/questionslist/QuestionsListViewMvc.kt: -------------------------------------------------------------------------------- 1 | package com.techyourchance.dagger2course.screens.questionslist 2 | 3 | import android.view.LayoutInflater 4 | import android.view.View 5 | import android.view.ViewGroup 6 | import android.widget.TextView 7 | import androidx.recyclerview.widget.LinearLayoutManager 8 | import androidx.recyclerview.widget.RecyclerView 9 | import androidx.swiperefreshlayout.widget.SwipeRefreshLayout 10 | import com.techyourchance.dagger2course.R 11 | import com.techyourchance.dagger2course.questions.Question 12 | import com.techyourchance.dagger2course.screens.common.toolbar.MyToolbar 13 | import com.techyourchance.dagger2course.screens.common.viewsmvc.BaseViewMvc 14 | 15 | class QuestionsListViewMvc( 16 | private val layoutInflater: LayoutInflater, 17 | private val parent: ViewGroup? 18 | ): BaseViewMvc( 19 | layoutInflater, 20 | parent, 21 | R.layout.layout_questions_list 22 | ) { 23 | 24 | interface Listener { 25 | fun onRefreshClicked() 26 | fun onQuestionClicked(clickedQuestion: Question) 27 | fun onViewModelClicked() 28 | } 29 | 30 | private val toolbar: MyToolbar 31 | private val swipeRefresh: SwipeRefreshLayout 32 | private val recyclerView: RecyclerView 33 | private val questionsAdapter: QuestionsAdapter 34 | 35 | init { 36 | 37 | toolbar = findViewById(R.id.toolbar) 38 | toolbar.setViewModelListener { 39 | for (listener in listeners) { 40 | listener.onViewModelClicked() 41 | } 42 | } 43 | // init pull-down-to-refresh 44 | swipeRefresh = findViewById(R.id.swipeRefresh) 45 | swipeRefresh.setOnRefreshListener { 46 | for (listener in listeners) { 47 | listener.onRefreshClicked() 48 | } 49 | } 50 | 51 | // init recycler view 52 | recyclerView = findViewById(R.id.recycler) 53 | recyclerView.layoutManager = LinearLayoutManager(context) 54 | questionsAdapter = QuestionsAdapter{ clickedQuestion -> 55 | for (listener in listeners) { 56 | listener.onQuestionClicked(clickedQuestion) 57 | } 58 | } 59 | recyclerView.adapter = questionsAdapter 60 | } 61 | 62 | fun bindQuestions(questions: List) { 63 | questionsAdapter.bindData(questions) 64 | } 65 | 66 | fun showProgressIndication() { 67 | swipeRefresh.isRefreshing = true 68 | } 69 | 70 | fun hideProgressIndication() { 71 | if (swipeRefresh.isRefreshing) { 72 | swipeRefresh.isRefreshing = false 73 | } 74 | } 75 | 76 | class QuestionsAdapter( 77 | private val onQuestionClickListener: (Question) -> Unit 78 | ) : RecyclerView.Adapter() { 79 | 80 | private var questionsList: List = ArrayList(0) 81 | 82 | inner class QuestionViewHolder(view: View) : RecyclerView.ViewHolder(view) { 83 | val title: TextView = view.findViewById(R.id.txt_title) 84 | } 85 | 86 | fun bindData(questions: List) { 87 | questionsList = ArrayList(questions) 88 | notifyDataSetChanged() 89 | } 90 | 91 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QuestionViewHolder { 92 | val itemView = LayoutInflater.from(parent.context) 93 | .inflate(R.layout.layout_question_list_item, parent, false) 94 | return QuestionViewHolder(itemView) 95 | } 96 | 97 | override fun onBindViewHolder(holder: QuestionViewHolder, position: Int) { 98 | holder.title.text = questionsList[position].title 99 | holder.itemView.setOnClickListener { 100 | onQuestionClickListener.invoke(questionsList[position]) 101 | } 102 | } 103 | 104 | override fun getItemCount(): Int { 105 | return questionsList.size 106 | } 107 | 108 | } 109 | } -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | --------------------------------------------------------------------------------