diff --git a/README.md b/README.md index c452e05..a6851d6 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@

- +

# StackOverflow - Community Version @@ -25,7 +25,7 @@ Users can also search for a particular problem they are having by typing in a se * [Preferences DataStore](https://developer.android.com/topic/libraries/architecture/datastore) for storing and retrieving key-value pairs of primitive data types # Installation -This App requires a minimum API level of 21. Clone the repository. You will need an API key from [Stack Exchange API](https://api.stackexchange.com/) to receive a higher request quota. Locate the StringConstants.java file and edit the following line to add your API key: +This App requires a minimum API level of 26. Clone the repository. You will need an API key from [Stack Exchange API](https://api.stackexchange.com/) to receive a higher request quota. Locate the StringConstants.java file and edit the following line to add your API key: ```` API_KEY = "YOUR_API_KEY" diff --git a/android_glide_lint.xml b/android_glide_lint.xml new file mode 100644 index 0000000..90c96a4 --- /dev/null +++ b/android_glide_lint.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 3609b47..2ac4741 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,18 +1,19 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' -apply plugin: 'kotlin-android-extensions' apply plugin: 'com.google.gms.google-services' +apply plugin: 'kotlin-kapt' +apply plugin: 'com.google.dagger.hilt.android' android { - compileSdkVersion 31 - buildToolsVersion "29.0.3" + namespace 'com.josycom.mayorjay.flowoverstack' + compileSdk 34 defaultConfig { applicationId "com.josycom.mayorjay.flowoverstack" - minSdkVersion 21 - targetSdkVersion 31 - versionCode 8 - versionName "1.8" + minSdk 26 + targetSdk 34 + versionCode 200 + versionName "2.00" multiDexEnabled true testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -50,20 +51,30 @@ android { buildFeatures { viewBinding true + buildConfig true } compileOptions { - sourceCompatibility = 1.8 - targetCompatibility = 1.8 + sourceCompatibility = 17 + targetCompatibility = 17 } kotlinOptions { - jvmTarget = JavaVersion.VERSION_1_8 + jvmTarget = JavaVersion.VERSION_17 freeCompilerArgs += [ "-Xjvm-default=all", ] } + kapt { + correctErrorTypes true + } + + lint { + // https://github.com/bumptech/glide/issues/4940 + lintConfig = file("$rootDir/android_glide_lint.xml") + } + } dependencies { @@ -106,10 +117,6 @@ dependencies { // LiveData implementation "androidx.lifecycle:lifecycle-livedata:2.4.0" - // RxAndroid and RxJava - implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' - implementation 'io.reactivex.rxjava2:rxjava:2.2.7' - // SwipeRefreshLayout implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' @@ -124,20 +131,19 @@ dependencies { implementation 'com.github.mukeshsolanki:MarkdownView-Android:1.0.8' // Image cropper - implementation 'com.theartofdev.edmodo:android-image-cropper:2.8.0' + implementation 'com.vanniktech:android-image-cropper:4.3.3' // ML Kit implementation 'com.google.android.gms:play-services-mlkit-text-recognition:17.0.0' - // Dagger - implementation 'com.google.dagger:dagger:2.35.1' - implementation 'com.google.dagger:dagger-android:2.35.1' - implementation 'com.google.dagger:dagger-android-support:2.27' - annotationProcessor 'com.google.dagger:dagger-compiler:2.35.1' - annotationProcessor 'com.google.dagger:dagger-android-processor:2.35.1' + // Hilt + implementation "com.google.dagger:hilt-android:$hilt_version" + kapt "com.google.dagger:hilt-compiler:$hilt_version" // Play core - implementation 'com.google.android.play:core:1.10.2' + implementation 'com.google.android.play:asset-delivery:2.2.2' + implementation 'com.google.android.play:feature-delivery:2.1.0' + implementation 'com.google.android.play:app-update:2.1.0' // Kotlin implementation 'androidx.core:core-ktx:1.6.0' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b7827c7..dd2379d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,10 +1,15 @@ + + - + - + - - - - \ No newline at end of file diff --git a/app/src/main/java/com/josycom/mayorjay/flowoverstack/MyApplication.java b/app/src/main/java/com/josycom/mayorjay/flowoverstack/MyApplication.java deleted file mode 100644 index 11b3ede..0000000 --- a/app/src/main/java/com/josycom/mayorjay/flowoverstack/MyApplication.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.josycom.mayorjay.flowoverstack; - -import android.app.Application; - -import com.josycom.mayorjay.flowoverstack.di.component.DaggerAppComponent; -import com.josycom.mayorjay.flowoverstack.util.AppLogger; - -import javax.inject.Inject; - -import dagger.android.AndroidInjector; -import dagger.android.DispatchingAndroidInjector; -import dagger.android.HasAndroidInjector; - -public class MyApplication extends Application implements HasAndroidInjector { - - @Inject - DispatchingAndroidInjector dispatchingAndroidInjector; - - @Override - public AndroidInjector androidInjector() { - return dispatchingAndroidInjector; - } - - @Override - public void onCreate() { - super.onCreate(); - - AppLogger.INSTANCE.init(); - DaggerAppComponent.builder().application(this).build().inject(this); - } -} diff --git a/app/src/main/java/com/josycom/mayorjay/flowoverstack/MyApplication.kt b/app/src/main/java/com/josycom/mayorjay/flowoverstack/MyApplication.kt new file mode 100644 index 0000000..a0b20d5 --- /dev/null +++ b/app/src/main/java/com/josycom/mayorjay/flowoverstack/MyApplication.kt @@ -0,0 +1,14 @@ +package com.josycom.mayorjay.flowoverstack + +import android.app.Application +import com.josycom.mayorjay.flowoverstack.util.AppLogger +import dagger.hilt.android.HiltAndroidApp + +@HiltAndroidApp +class MyApplication : Application() { + + override fun onCreate() { + super.onCreate() + AppLogger.init() + } +} diff --git a/app/src/main/java/com/josycom/mayorjay/flowoverstack/data/remote/model/SearchResponse.kt b/app/src/main/java/com/josycom/mayorjay/flowoverstack/data/remote/model/SearchResponse.kt index f061438..dcde255 100644 --- a/app/src/main/java/com/josycom/mayorjay/flowoverstack/data/remote/model/SearchResponse.kt +++ b/app/src/main/java/com/josycom/mayorjay/flowoverstack/data/remote/model/SearchResponse.kt @@ -3,6 +3,6 @@ package com.josycom.mayorjay.flowoverstack.data.remote.model import com.josycom.mayorjay.flowoverstack.data.model.Question data class SearchResponse( - val networkState: String, - val questions: List? + val networkState: String = "", + val questions: List? = null ) \ No newline at end of file diff --git a/app/src/main/java/com/josycom/mayorjay/flowoverstack/data/remote/service/ApiService.kt b/app/src/main/java/com/josycom/mayorjay/flowoverstack/data/remote/service/ApiService.kt index c2d5a93..bd85dc5 100644 --- a/app/src/main/java/com/josycom/mayorjay/flowoverstack/data/remote/service/ApiService.kt +++ b/app/src/main/java/com/josycom/mayorjay/flowoverstack/data/remote/service/ApiService.kt @@ -20,7 +20,7 @@ interface ApiService { @Query("site") site: String?, @Query("tagged") tagged: String?, @Query(value = "filter", encoded = true) filter: String?, - @Query("key") siteKey: String?): QuestionsResponse + @Query("key", encoded = true) siteKey: String?): QuestionsResponse @GET(AppConstants.ANSWERS_END_POINT) suspend fun getAnswersToQuestion( diff --git a/app/src/main/java/com/josycom/mayorjay/flowoverstack/data/repository/PreferenceRepositoryImpl.kt b/app/src/main/java/com/josycom/mayorjay/flowoverstack/data/repository/PreferenceRepositoryImpl.kt index d1dc399..ae62587 100644 --- a/app/src/main/java/com/josycom/mayorjay/flowoverstack/data/repository/PreferenceRepositoryImpl.kt +++ b/app/src/main/java/com/josycom/mayorjay/flowoverstack/data/repository/PreferenceRepositoryImpl.kt @@ -1,23 +1,17 @@ package com.josycom.mayorjay.flowoverstack.data.repository -import android.content.Context import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.emptyPreferences import androidx.datastore.preferences.core.intPreferencesKey -import androidx.datastore.preferences.preferencesDataStore -import com.josycom.mayorjay.flowoverstack.util.AppConstants import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.map import timber.log.Timber +import javax.inject.Inject -val Context.dataStore: DataStore by preferencesDataStore( - name = AppConstants.PREFERENCES_FILE_NAME -) - -class PreferenceRepositoryImpl(private val dataStore: DataStore) : +class PreferenceRepositoryImpl @Inject constructor(private val dataStore: DataStore) : PreferenceRepository { override fun getIntPreferenceFlow(key: String): Flow { diff --git a/app/src/main/java/com/josycom/mayorjay/flowoverstack/data/repository/SearchRepository.kt b/app/src/main/java/com/josycom/mayorjay/flowoverstack/data/repository/SearchRepository.kt index 6a73d82..944eff6 100644 --- a/app/src/main/java/com/josycom/mayorjay/flowoverstack/data/repository/SearchRepository.kt +++ b/app/src/main/java/com/josycom/mayorjay/flowoverstack/data/repository/SearchRepository.kt @@ -17,36 +17,35 @@ import javax.inject.Singleton @Singleton class SearchRepository @Inject constructor(private val apiService: ApiService) { - private val mResponse = MutableLiveData() + val searchResponse = MutableLiveData() private fun getQuestionsWithTextInTitle(inTitle: String?, page: Int, pageSize: Int) { - mResponse.postValue(SearchResponse(AppConstants.LOADING, null)) + searchResponse.postValue(SearchResponse(AppConstants.LOADING, null)) val call = apiService.getQuestionsWithTextInTitle(inTitle, page, pageSize) call.enqueue(object : Callback { override fun onResponse(call: Call, response: Response) { val questionsResponse = response.body() if (questionsResponse != null) { if (questionsResponse.items.isNotEmpty()) { - mResponse.setValue( + searchResponse.setValue( SearchResponse( AppConstants.LOADED, questionsResponse.items.map { it.toQuestion() }) ) } else { - mResponse.setValue(SearchResponse(AppConstants.NO_MATCHING_RESULT, null)) + searchResponse.setValue(SearchResponse(AppConstants.NO_MATCHING_RESULT, null)) } } } override fun onFailure(call: Call, t: Throwable) { Timber.e(t) - mResponse.value = SearchResponse(AppConstants.FAILED, null) + searchResponse.value = SearchResponse(AppConstants.FAILED, null) } }) } - fun getResponse(inTitle: String?, page: Int, pageSize: Int): MutableLiveData { + fun performSearch(inTitle: String?, page: Int, pageSize: Int) { ThreadExecutor.mExecutor.execute { getQuestionsWithTextInTitle(inTitle, page, pageSize) } - return mResponse } } \ No newline at end of file diff --git a/app/src/main/java/com/josycom/mayorjay/flowoverstack/di/ApiModule.kt b/app/src/main/java/com/josycom/mayorjay/flowoverstack/di/ApiModule.kt new file mode 100644 index 0000000..0207cb3 --- /dev/null +++ b/app/src/main/java/com/josycom/mayorjay/flowoverstack/di/ApiModule.kt @@ -0,0 +1,51 @@ +package com.josycom.mayorjay.flowoverstack.di + +import com.google.gson.Gson +import com.google.gson.GsonBuilder +import com.josycom.mayorjay.flowoverstack.BuildConfig +import com.josycom.mayorjay.flowoverstack.data.remote.service.ApiService +import com.josycom.mayorjay.flowoverstack.util.AppConstants +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import okhttp3.OkHttpClient +import okhttp3.logging.HttpLoggingInterceptor +import retrofit2.Retrofit +import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory +import retrofit2.converter.gson.GsonConverterFactory +import javax.inject.Singleton + +@InstallIn(SingletonComponent::class) +@Module +object ApiModule { + + @Provides + @Singleton + fun provideGson(): Gson = GsonBuilder().setLenient().create() + + @Provides + @Singleton + fun provideInterceptor(): HttpLoggingInterceptor = + HttpLoggingInterceptor() + .setLevel(if (BuildConfig.DEBUG) HttpLoggingInterceptor.Level.BODY else HttpLoggingInterceptor.Level.NONE) + + @Provides + @Singleton + fun getOkHttpClient(interceptor: HttpLoggingInterceptor): OkHttpClient.Builder = + OkHttpClient.Builder().addInterceptor(interceptor) + + @Provides + @Singleton + fun provideRetrofit(gson: Gson, okHttpClient: OkHttpClient.Builder): Retrofit = + Retrofit.Builder() + .baseUrl(AppConstants.BASE_URL) + .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) + .addConverterFactory(GsonConverterFactory.create(gson)) + .client(okHttpClient.build()) + .build() + + @Provides + @Singleton + fun getApiService(retrofit: Retrofit): ApiService = retrofit.create(ApiService::class.java) +} diff --git a/app/src/main/java/com/josycom/mayorjay/flowoverstack/di/DataStoreModule.kt b/app/src/main/java/com/josycom/mayorjay/flowoverstack/di/DataStoreModule.kt new file mode 100644 index 0000000..6d9c195 --- /dev/null +++ b/app/src/main/java/com/josycom/mayorjay/flowoverstack/di/DataStoreModule.kt @@ -0,0 +1,28 @@ +package com.josycom.mayorjay.flowoverstack.di + +import android.content.Context +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.PreferenceDataStoreFactory +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.preferencesDataStoreFile +import com.josycom.mayorjay.flowoverstack.util.AppConstants +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@InstallIn(SingletonComponent::class) +@Module +object DataStoreModule { + + @Provides + @Singleton + fun provideDataStore(@ApplicationContext context: Context): DataStore = + PreferenceDataStoreFactory.create( + produceFile = { + context.preferencesDataStoreFile(AppConstants.PREFERENCES_FILE_NAME) + } + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/josycom/mayorjay/flowoverstack/di/RepositoryModule.kt b/app/src/main/java/com/josycom/mayorjay/flowoverstack/di/RepositoryModule.kt new file mode 100644 index 0000000..cea16d7 --- /dev/null +++ b/app/src/main/java/com/josycom/mayorjay/flowoverstack/di/RepositoryModule.kt @@ -0,0 +1,16 @@ +package com.josycom.mayorjay.flowoverstack.di + +import com.josycom.mayorjay.flowoverstack.data.repository.PreferenceRepository +import com.josycom.mayorjay.flowoverstack.data.repository.PreferenceRepositoryImpl +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent + +@InstallIn(SingletonComponent::class) +@Module +abstract class RepositoryModule { + + @Binds + abstract fun bindRepository(repositoryImpl: PreferenceRepositoryImpl): PreferenceRepository +} \ No newline at end of file diff --git a/app/src/main/java/com/josycom/mayorjay/flowoverstack/di/ViewModelKey.kt b/app/src/main/java/com/josycom/mayorjay/flowoverstack/di/ViewModelKey.kt deleted file mode 100644 index e9ea335..0000000 --- a/app/src/main/java/com/josycom/mayorjay/flowoverstack/di/ViewModelKey.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.josycom.mayorjay.flowoverstack.di - -import dagger.MapKey -import kotlin.reflect.KClass - -@MustBeDocumented -@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER) -@kotlin.annotation.Retention(AnnotationRetention.RUNTIME) -@MapKey -annotation class ViewModelKey(val value: KClass) \ No newline at end of file diff --git a/app/src/main/java/com/josycom/mayorjay/flowoverstack/di/component/AppComponent.java b/app/src/main/java/com/josycom/mayorjay/flowoverstack/di/component/AppComponent.java deleted file mode 100644 index e6d6557..0000000 --- a/app/src/main/java/com/josycom/mayorjay/flowoverstack/di/component/AppComponent.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.josycom.mayorjay.flowoverstack.di.component; - -import android.app.Application; - -import com.josycom.mayorjay.flowoverstack.MyApplication; -import com.josycom.mayorjay.flowoverstack.di.module.AnswerActivityModule; -import com.josycom.mayorjay.flowoverstack.di.module.ApiModule; -import com.josycom.mayorjay.flowoverstack.di.module.MainActivityModule; -import com.josycom.mayorjay.flowoverstack.di.module.QuestionViewModelFactoryModule; -import com.josycom.mayorjay.flowoverstack.di.module.QuestionViewModelModule; -import com.josycom.mayorjay.flowoverstack.di.module.QuestionsFragmentModule; -import com.josycom.mayorjay.flowoverstack.di.module.SearchActivityModule; -import com.josycom.mayorjay.flowoverstack.di.module.SearchViewModelFactoryModule; -import com.josycom.mayorjay.flowoverstack.di.module.SearchViewModelModule; -import com.josycom.mayorjay.flowoverstack.di.module.TagsDialogFragmentModule; -import com.josycom.mayorjay.flowoverstack.di.module.TagsDialogViewModelModule; -import com.josycom.mayorjay.flowoverstack.di.module.TagsViewModelFactoryModule; - -import javax.inject.Singleton; - -import dagger.BindsInstance; -import dagger.Component; -import dagger.android.AndroidInjectionModule; - -@Component(modules = { - AndroidInjectionModule.class, - MainActivityModule.class, - AnswerActivityModule.class, - SearchActivityModule.class, - SearchViewModelModule.class, - SearchViewModelFactoryModule.class, - QuestionsFragmentModule.class, - QuestionViewModelModule.class, - QuestionViewModelFactoryModule.class, - ApiModule.class, - TagsDialogFragmentModule.class, - TagsDialogViewModelModule.class, - TagsViewModelFactoryModule.class}) -@Singleton -public interface AppComponent { - - @Component.Builder - interface Builder { - - @BindsInstance - Builder application(Application application); - - AppComponent build(); - } - - - /* - * This is our custom Application class - * */ - void inject(MyApplication application); - -} \ No newline at end of file diff --git a/app/src/main/java/com/josycom/mayorjay/flowoverstack/di/module/AnswerActivityModule.java b/app/src/main/java/com/josycom/mayorjay/flowoverstack/di/module/AnswerActivityModule.java deleted file mode 100644 index 8a7c3eb..0000000 --- a/app/src/main/java/com/josycom/mayorjay/flowoverstack/di/module/AnswerActivityModule.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.josycom.mayorjay.flowoverstack.di.module; - -import com.josycom.mayorjay.flowoverstack.view.answer.AnswerActivity; - -import dagger.Module; -import dagger.android.ContributesAndroidInjector; - -@Module -public abstract class AnswerActivityModule { - - - @ContributesAndroidInjector - abstract AnswerActivity contributeActivityAndroidInjector(); - -} \ No newline at end of file diff --git a/app/src/main/java/com/josycom/mayorjay/flowoverstack/di/module/AnswerViewModelFactoryModule.java b/app/src/main/java/com/josycom/mayorjay/flowoverstack/di/module/AnswerViewModelFactoryModule.java deleted file mode 100644 index bf8f369..0000000 --- a/app/src/main/java/com/josycom/mayorjay/flowoverstack/di/module/AnswerViewModelFactoryModule.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.josycom.mayorjay.flowoverstack.di.module; - -import androidx.lifecycle.ViewModelProvider; - -import com.josycom.mayorjay.flowoverstack.viewmodel.CustomAnswerViewModelFactory; - -import dagger.Binds; -import dagger.Module; - -@Module(includes = AnswerViewModelModule.class) -public abstract class AnswerViewModelFactoryModule { - - @Binds - abstract ViewModelProvider.Factory bindViewModelFactory(CustomAnswerViewModelFactory customAnswerViewModelFactory); -} diff --git a/app/src/main/java/com/josycom/mayorjay/flowoverstack/di/module/AnswerViewModelModule.java b/app/src/main/java/com/josycom/mayorjay/flowoverstack/di/module/AnswerViewModelModule.java deleted file mode 100644 index a7b6ea1..0000000 --- a/app/src/main/java/com/josycom/mayorjay/flowoverstack/di/module/AnswerViewModelModule.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.josycom.mayorjay.flowoverstack.di.module; - -import androidx.lifecycle.ViewModel; - -import com.josycom.mayorjay.flowoverstack.data.repository.AnswerRepository; -import com.josycom.mayorjay.flowoverstack.di.ViewModelKey; -import com.josycom.mayorjay.flowoverstack.viewmodel.AnswerViewModel; - -import dagger.Module; -import dagger.Provides; -import dagger.multibindings.IntoMap; - -@Module -public class AnswerViewModelModule { - - @Provides - @IntoMap - @ViewModelKey(AnswerViewModel.class) - ViewModel bindAnswerViewModel(AnswerRepository answerRepository, int questionId, String order, String sortCondition, String site, String filter, String siteKey) { - return new AnswerViewModel(answerRepository, questionId, order, sortCondition, site, filter, siteKey); - } -} diff --git a/app/src/main/java/com/josycom/mayorjay/flowoverstack/di/module/ApiModule.java b/app/src/main/java/com/josycom/mayorjay/flowoverstack/di/module/ApiModule.java deleted file mode 100644 index 3ca9bea..0000000 --- a/app/src/main/java/com/josycom/mayorjay/flowoverstack/di/module/ApiModule.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.josycom.mayorjay.flowoverstack.di.module; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.josycom.mayorjay.flowoverstack.BuildConfig; -import com.josycom.mayorjay.flowoverstack.data.remote.service.ApiService; -import com.josycom.mayorjay.flowoverstack.util.AppConstants; - -import javax.inject.Singleton; - -import dagger.Module; -import dagger.Provides; -import okhttp3.OkHttpClient; -import okhttp3.logging.HttpLoggingInterceptor; -import retrofit2.Retrofit; -import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; -import retrofit2.converter.gson.GsonConverterFactory; - -@Module -public class ApiModule { - - @Provides - @Singleton - Gson provideGson() { - GsonBuilder gsonBuilder = new GsonBuilder(); - return gsonBuilder.setLenient().create(); - } - - @Provides - @Singleton - HttpLoggingInterceptor provideInterceptor() { - HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor(); - if (BuildConfig.DEBUG) { - interceptor.setLevel(HttpLoggingInterceptor.Level.BODY); - } else { - interceptor.setLevel(HttpLoggingInterceptor.Level.NONE); - } - return interceptor; - } - - @Provides - @Singleton - OkHttpClient.Builder getOkHttpClient(HttpLoggingInterceptor interceptor) { - OkHttpClient.Builder okHttpClient = new OkHttpClient.Builder(); - okHttpClient.addInterceptor(interceptor); - return okHttpClient; - } - - @Provides - @Singleton - Retrofit provideRetrofit(Gson gson, OkHttpClient.Builder okHttpClient) { - return new Retrofit.Builder() - .baseUrl(AppConstants.BASE_URL) - .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) - .addConverterFactory(GsonConverterFactory.create(gson)) - .client(okHttpClient.build()) - .build(); - } - - @Provides - @Singleton - ApiService getApiService(Retrofit retrofit) { - return retrofit.create(ApiService.class); - } -} diff --git a/app/src/main/java/com/josycom/mayorjay/flowoverstack/di/module/MainActivityModule.java b/app/src/main/java/com/josycom/mayorjay/flowoverstack/di/module/MainActivityModule.java deleted file mode 100644 index 14342e6..0000000 --- a/app/src/main/java/com/josycom/mayorjay/flowoverstack/di/module/MainActivityModule.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.josycom.mayorjay.flowoverstack.di.module; - -import com.josycom.mayorjay.flowoverstack.view.home.QuestionActivity; - -import dagger.Module; -import dagger.android.ContributesAndroidInjector; - -@Module -public abstract class MainActivityModule { - - - @ContributesAndroidInjector(modules = QuestionsFragmentModule.class) - abstract QuestionActivity contributeMainAndroidInjector(); - -} - diff --git a/app/src/main/java/com/josycom/mayorjay/flowoverstack/di/module/QuestionViewModelFactoryModule.java b/app/src/main/java/com/josycom/mayorjay/flowoverstack/di/module/QuestionViewModelFactoryModule.java deleted file mode 100644 index 5bda00c..0000000 --- a/app/src/main/java/com/josycom/mayorjay/flowoverstack/di/module/QuestionViewModelFactoryModule.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.josycom.mayorjay.flowoverstack.di.module; - -import androidx.lifecycle.ViewModelProvider; - -import com.josycom.mayorjay.flowoverstack.viewmodel.CustomQuestionViewModelFactory; - -import dagger.Binds; -import dagger.Module; - -@Module(includes = QuestionViewModelModule.class) -public abstract class QuestionViewModelFactoryModule { - - @Binds - abstract ViewModelProvider.Factory bindViewModelFactory(CustomQuestionViewModelFactory customQuestionViewModelFactory); -} diff --git a/app/src/main/java/com/josycom/mayorjay/flowoverstack/di/module/QuestionViewModelModule.java b/app/src/main/java/com/josycom/mayorjay/flowoverstack/di/module/QuestionViewModelModule.java deleted file mode 100644 index 7e36f7a..0000000 --- a/app/src/main/java/com/josycom/mayorjay/flowoverstack/di/module/QuestionViewModelModule.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.josycom.mayorjay.flowoverstack.di.module; - -import androidx.lifecycle.ViewModel; - -import com.josycom.mayorjay.flowoverstack.data.repository.QuestionRepository; -import com.josycom.mayorjay.flowoverstack.di.ViewModelKey; -import com.josycom.mayorjay.flowoverstack.viewmodel.QuestionViewModel; - -import dagger.Module; -import dagger.Provides; -import dagger.multibindings.IntoMap; - -@Module -public class QuestionViewModelModule { - - @Provides - @IntoMap - @ViewModelKey(QuestionViewModel.class) - ViewModel bindQuestionViewModel(int page, int pageSize, String order, String sortCondition, String site, String tagged, String filter, String siteKey, QuestionRepository questionRepository) { - return new QuestionViewModel(questionRepository, page, pageSize, order, sortCondition, site, tagged, filter, siteKey); - } -} diff --git a/app/src/main/java/com/josycom/mayorjay/flowoverstack/di/module/QuestionsFragmentModule.java b/app/src/main/java/com/josycom/mayorjay/flowoverstack/di/module/QuestionsFragmentModule.java deleted file mode 100644 index 18721d4..0000000 --- a/app/src/main/java/com/josycom/mayorjay/flowoverstack/di/module/QuestionsFragmentModule.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.josycom.mayorjay.flowoverstack.di.module; - -import com.josycom.mayorjay.flowoverstack.view.home.QuestionsFragment; - -import dagger.Module; -import dagger.android.ContributesAndroidInjector; - -@Module -public abstract class QuestionsFragmentModule { - - @ContributesAndroidInjector - abstract QuestionsFragment contributeQuestionsByActivityFragment(); -} diff --git a/app/src/main/java/com/josycom/mayorjay/flowoverstack/di/module/SearchActivityModule.java b/app/src/main/java/com/josycom/mayorjay/flowoverstack/di/module/SearchActivityModule.java deleted file mode 100644 index a0dd2f1..0000000 --- a/app/src/main/java/com/josycom/mayorjay/flowoverstack/di/module/SearchActivityModule.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.josycom.mayorjay.flowoverstack.di.module; - -import com.josycom.mayorjay.flowoverstack.view.ocr.OcrActivity; -import com.josycom.mayorjay.flowoverstack.view.search.SearchActivity; - -import dagger.Module; -import dagger.android.ContributesAndroidInjector; - -@Module -public abstract class SearchActivityModule { - - @ContributesAndroidInjector - abstract SearchActivity searchAndroidInjector(); - - @ContributesAndroidInjector - abstract OcrActivity ocrAndroidInjector(); -} diff --git a/app/src/main/java/com/josycom/mayorjay/flowoverstack/di/module/SearchViewModelFactoryModule.java b/app/src/main/java/com/josycom/mayorjay/flowoverstack/di/module/SearchViewModelFactoryModule.java deleted file mode 100644 index 4582240..0000000 --- a/app/src/main/java/com/josycom/mayorjay/flowoverstack/di/module/SearchViewModelFactoryModule.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.josycom.mayorjay.flowoverstack.di.module; - -import androidx.lifecycle.ViewModelProvider; - -import com.josycom.mayorjay.flowoverstack.viewmodel.CustomSearchViewModelFactory; - -import dagger.Binds; -import dagger.Module; - -@Module(includes = SearchViewModelModule.class) -public abstract class SearchViewModelFactoryModule { - - @Binds - abstract ViewModelProvider.Factory bindViewModelFactory(CustomSearchViewModelFactory customSearchViewModelFactory); -} diff --git a/app/src/main/java/com/josycom/mayorjay/flowoverstack/di/module/SearchViewModelModule.java b/app/src/main/java/com/josycom/mayorjay/flowoverstack/di/module/SearchViewModelModule.java deleted file mode 100644 index eb97755..0000000 --- a/app/src/main/java/com/josycom/mayorjay/flowoverstack/di/module/SearchViewModelModule.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.josycom.mayorjay.flowoverstack.di.module; - -import androidx.lifecycle.ViewModel; - -import com.josycom.mayorjay.flowoverstack.data.repository.SearchRepository; -import com.josycom.mayorjay.flowoverstack.di.ViewModelKey; -import com.josycom.mayorjay.flowoverstack.viewmodel.SearchViewModel; - -import dagger.Module; -import dagger.Provides; -import dagger.multibindings.IntoMap; - -@Module -public class SearchViewModelModule { - - @Provides - @IntoMap - @ViewModelKey(SearchViewModel.class) - ViewModel bindSearchViewModel(int page, int pageSize, SearchRepository searchRepository) { - return new SearchViewModel(page, pageSize, searchRepository); - } -} diff --git a/app/src/main/java/com/josycom/mayorjay/flowoverstack/di/module/TagsDialogFragmentModule.java b/app/src/main/java/com/josycom/mayorjay/flowoverstack/di/module/TagsDialogFragmentModule.java deleted file mode 100644 index 1d80ecc..0000000 --- a/app/src/main/java/com/josycom/mayorjay/flowoverstack/di/module/TagsDialogFragmentModule.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.josycom.mayorjay.flowoverstack.di.module; - -import com.josycom.mayorjay.flowoverstack.view.tag.TagsDialogFragment; - -import dagger.Module; -import dagger.android.ContributesAndroidInjector; - -@Module -public abstract class TagsDialogFragmentModule { - - @ContributesAndroidInjector - abstract TagsDialogFragment contributeTagsDialogFragment(); -} diff --git a/app/src/main/java/com/josycom/mayorjay/flowoverstack/di/module/TagsDialogViewModelModule.java b/app/src/main/java/com/josycom/mayorjay/flowoverstack/di/module/TagsDialogViewModelModule.java deleted file mode 100644 index fc65b8b..0000000 --- a/app/src/main/java/com/josycom/mayorjay/flowoverstack/di/module/TagsDialogViewModelModule.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.josycom.mayorjay.flowoverstack.di.module; - -import androidx.lifecycle.ViewModel; - -import com.josycom.mayorjay.flowoverstack.data.repository.TagRepository; -import com.josycom.mayorjay.flowoverstack.di.ViewModelKey; -import com.josycom.mayorjay.flowoverstack.viewmodel.TagsDialogViewModel; - -import dagger.Module; -import dagger.Provides; -import dagger.multibindings.IntoMap; - -@Module -public class TagsDialogViewModelModule { - - @Provides - @IntoMap - @ViewModelKey(TagsDialogViewModel.class) - ViewModel bindTagsDialogViewModel(int page, int pageSize, String siteKey, TagRepository tagRepository) { - return new TagsDialogViewModel(tagRepository, page, pageSize, siteKey); - } -} diff --git a/app/src/main/java/com/josycom/mayorjay/flowoverstack/di/module/TagsViewModelFactoryModule.java b/app/src/main/java/com/josycom/mayorjay/flowoverstack/di/module/TagsViewModelFactoryModule.java deleted file mode 100644 index 711e4ac..0000000 --- a/app/src/main/java/com/josycom/mayorjay/flowoverstack/di/module/TagsViewModelFactoryModule.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.josycom.mayorjay.flowoverstack.di.module; - -import androidx.lifecycle.ViewModelProvider; - -import com.josycom.mayorjay.flowoverstack.viewmodel.CustomTagsViewModelFactory; - -import dagger.Binds; -import dagger.Module; - -@Module(includes = TagsDialogViewModelModule.class) -public abstract class TagsViewModelFactoryModule { - - @Binds - abstract ViewModelProvider.Factory bindViewModelFactory(CustomTagsViewModelFactory customTagsViewModelFactory); -} diff --git a/app/src/main/java/com/josycom/mayorjay/flowoverstack/view/answer/AnswerActivity.kt b/app/src/main/java/com/josycom/mayorjay/flowoverstack/view/answer/AnswerActivity.kt index cf83d80..5d43552 100644 --- a/app/src/main/java/com/josycom/mayorjay/flowoverstack/view/answer/AnswerActivity.kt +++ b/app/src/main/java/com/josycom/mayorjay/flowoverstack/view/answer/AnswerActivity.kt @@ -4,11 +4,11 @@ import android.content.Intent import android.os.Bundle import android.view.MenuItem import android.view.View +import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity import androidx.core.view.isInvisible import androidx.core.view.isVisible import androidx.lifecycle.Lifecycle -import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import androidx.paging.LoadState @@ -23,39 +23,32 @@ import com.josycom.mayorjay.flowoverstack.util.AppConstants import com.josycom.mayorjay.flowoverstack.util.AppUtils import com.josycom.mayorjay.flowoverstack.view.home.PagingLoadStateAdapter import com.josycom.mayorjay.flowoverstack.viewmodel.AnswerViewModel -import com.josycom.mayorjay.flowoverstack.viewmodel.CustomAnswerViewModelFactory -import dagger.android.AndroidInjection -import kotlinx.coroutines.flow.collect +import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch import org.jsoup.Jsoup -import javax.inject.Inject +@AndroidEntryPoint class AnswerActivity : AppCompatActivity() { private lateinit var binding: ActivityAnswerBinding - private lateinit var answerViewModel: AnswerViewModel + private val answerViewModel: AnswerViewModel by viewModels() private var ownerQuestionLink: String? = null private var questionId = 0 - - @Inject - lateinit var viewModelFactory: CustomAnswerViewModelFactory private var questionLink: String? = null override fun onCreate(savedInstanceState: Bundle?) { - AndroidInjection.inject(this) super.onCreate(savedInstanceState) binding = ActivityAnswerBinding.inflate(layoutInflater) setContentView(binding.root) setupViewContents(intent) - viewModelFactory.setInputs(questionId, + answerViewModel.init(questionId, AppConstants.ORDER_DESCENDING, AppConstants.SORT_BY_ACTIVITY, AppConstants.SITE, AppConstants.ANSWER_FILTER, AppConstants.API_KEY) - answerViewModel = ViewModelProvider(this, viewModelFactory).get(AnswerViewModel::class.java) fetchAndDisplayAnswers() setupListeners() diff --git a/app/src/main/java/com/josycom/mayorjay/flowoverstack/view/answer/WebViewActivity.kt b/app/src/main/java/com/josycom/mayorjay/flowoverstack/view/answer/WebViewActivity.kt index db50c60..591ea53 100644 --- a/app/src/main/java/com/josycom/mayorjay/flowoverstack/view/answer/WebViewActivity.kt +++ b/app/src/main/java/com/josycom/mayorjay/flowoverstack/view/answer/WebViewActivity.kt @@ -7,7 +7,6 @@ import android.content.Intent import android.graphics.Bitmap import android.graphics.Color import android.graphics.PorterDuff -import android.os.Build import android.os.Bundle import android.view.Menu import android.view.MenuItem @@ -23,21 +22,26 @@ import androidx.appcompat.widget.Toolbar import androidx.core.view.isInvisible import androidx.core.view.isVisible import com.josycom.mayorjay.flowoverstack.R +import com.josycom.mayorjay.flowoverstack.databinding.ActivityWebViewBinding import com.josycom.mayorjay.flowoverstack.util.AppConstants import com.josycom.mayorjay.flowoverstack.util.AppUtils +import dagger.hilt.android.AndroidEntryPoint import timber.log.Timber import java.net.URI import java.net.URISyntaxException +@AndroidEntryPoint class WebViewActivity : AppCompatActivity() { - private var url: String? = null + private lateinit var binding: ActivityWebViewBinding private lateinit var webView: WebView private lateinit var progressBar: ProgressBar + private var url: String? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContentView(R.layout.activity_web_view) + binding = ActivityWebViewBinding.inflate(layoutInflater) + setContentView(binding.root) // get extra object url = intent.getStringExtra(AppConstants.WEBVIEW_EXTRA_OBJECT) @@ -47,8 +51,8 @@ class WebViewActivity : AppCompatActivity() { } private fun initComponent() { - webView = findViewById(R.id.webView) - progressBar = findViewById(R.id.progressBar) + webView = binding.webView + progressBar = binding.progressBar progressBar.progressDrawable.setColorFilter(resources.getColor(R.color.colorPrimaryLight), PorterDuff.Mode.SRC_IN) progressBar.setBackgroundColor(Color.parseColor("#1A000000")) } @@ -88,7 +92,7 @@ class WebViewActivity : AppCompatActivity() { progressBar.isInvisible = true } } - webView.loadUrl(url ?: "") + webView.loadUrl(url.orEmpty()) webView.webChromeClient = object : WebChromeClient() { override fun onProgressChanged(view: WebView, progress: Int) { progressBar.progress = progress + 10 @@ -137,12 +141,10 @@ class WebViewActivity : AppCompatActivity() { } private fun setSystemBarColor(act: Activity) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - val window = act.window - window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) - window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) - window.statusBarColor = Color.parseColor("#f2f2f2") - } + val window = act.window + window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) + window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) + window.statusBarColor = Color.parseColor("#f2f2f2") } private fun changeOverflowMenuIconColor(toolbar: Toolbar, @ColorInt color: Int) { @@ -155,7 +157,7 @@ class WebViewActivity : AppCompatActivity() { } } - fun getHostName(url: String): String { + private fun getHostName(url: String): String { return try { val uri = URI(url) var newUrl = uri.host @@ -167,24 +169,22 @@ class WebViewActivity : AppCompatActivity() { } } - companion object { - fun setSystemBarLight(act: Activity) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - val view = act.findViewById(android.R.id.content) - var flags = view.systemUiVisibility - flags = flags or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR - view.systemUiVisibility = flags - } - } + private fun setSystemBarLight(act: Activity) { + val view = act.findViewById(android.R.id.content) + var flags = view.systemUiVisibility + flags = flags or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR + view.systemUiVisibility = flags + } - fun changeMenuIconColor(menu: Menu, @ColorInt color: Int) { - for (i in 0 until menu.size()) { - val drawable = menu.getItem(i).icon ?: continue - drawable.mutate() - drawable.setColorFilter(color, PorterDuff.Mode.SRC_ATOP) - } + private fun changeMenuIconColor(menu: Menu, @ColorInt color: Int) { + for (i in 0 until menu.size()) { + val drawable = menu.getItem(i).icon ?: continue + drawable.mutate() + drawable.setColorFilter(color, PorterDuff.Mode.SRC_ATOP) } + } + companion object { fun navigate(activity: Activity, url: String?) { activity.startActivity(getIntent(activity, url)) } diff --git a/app/src/main/java/com/josycom/mayorjay/flowoverstack/view/home/QuestionActivity.kt b/app/src/main/java/com/josycom/mayorjay/flowoverstack/view/home/QuestionActivity.kt index 5e0ee3f..c2c6919 100644 --- a/app/src/main/java/com/josycom/mayorjay/flowoverstack/view/home/QuestionActivity.kt +++ b/app/src/main/java/com/josycom/mayorjay/flowoverstack/view/home/QuestionActivity.kt @@ -8,6 +8,7 @@ import android.view.Menu import android.view.MenuItem import android.view.animation.Animation import android.view.animation.AnimationUtils +import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.viewModels import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity @@ -20,14 +21,13 @@ import com.google.android.material.snackbar.Snackbar import com.google.android.play.core.appupdate.AppUpdateInfo import com.google.android.play.core.appupdate.AppUpdateManager import com.google.android.play.core.appupdate.AppUpdateManagerFactory +import com.google.android.play.core.appupdate.AppUpdateOptions import com.google.android.play.core.install.InstallState import com.google.android.play.core.install.InstallStateUpdatedListener import com.google.android.play.core.install.model.AppUpdateType import com.google.android.play.core.install.model.InstallStatus import com.google.android.play.core.install.model.UpdateAvailability import com.josycom.mayorjay.flowoverstack.R -import com.josycom.mayorjay.flowoverstack.data.repository.PreferenceRepositoryImpl -import com.josycom.mayorjay.flowoverstack.data.repository.dataStore import com.josycom.mayorjay.flowoverstack.databinding.ActivityQuestionBinding import com.josycom.mayorjay.flowoverstack.databinding.LayoutInfoDialogBinding import com.josycom.mayorjay.flowoverstack.util.AppConstants @@ -36,35 +36,32 @@ import com.josycom.mayorjay.flowoverstack.view.ocr.OcrActivity import com.josycom.mayorjay.flowoverstack.view.search.SearchActivity import com.josycom.mayorjay.flowoverstack.view.tag.TagsDialogFragment import com.josycom.mayorjay.flowoverstack.viewmodel.QuestionActivityViewModel -import com.josycom.mayorjay.flowoverstack.viewmodel.ViewModelProviderFactory -import dagger.android.AndroidInjection -import dagger.android.AndroidInjector -import dagger.android.DispatchingAndroidInjector -import dagger.android.HasAndroidInjector +import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.launch import timber.log.Timber -import javax.inject.Inject -class QuestionActivity : AppCompatActivity(), HasAndroidInjector { - - @Inject - lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector +@AndroidEntryPoint +class QuestionActivity : AppCompatActivity() { private var fragmentTransaction: FragmentTransaction? = null private var isFragmentDisplayed = false private var isFabOpen = false + private var isRecencySelected = false private lateinit var binding: ActivityQuestionBinding private var fabOpen: Animation? = null private var fabClose: Animation? = null private var appUpdateManager: AppUpdateManager? = null private var job: Job? = null - private val viewModel: QuestionActivityViewModel by viewModels { - ViewModelProviderFactory(PreferenceRepositoryImpl(applicationContext.dataStore)) + private val viewModel: QuestionActivityViewModel by viewModels() + + private val appUpdateLauncher = registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) { result -> + if (result.resultCode != RESULT_OK) { + checkForUpdate() + } } override fun onCreate(savedInstanceState: Bundle?) { - AndroidInjection.inject(this) super.onCreate(savedInstanceState) binding = ActivityQuestionBinding.inflate(layoutInflater) setContentView(binding.root) @@ -170,6 +167,8 @@ class QuestionActivity : AppCompatActivity(), HasAndroidInjector { override fun onCreateOptionsMenu(menu: Menu): Boolean { menuInflater.inflate(R.menu.menu_main, menu) + val menuItem = menu.findItem(R.id.action_filter_by_recency) + menuItem.setTitle(if (isRecencySelected) R.string.action_filter_by_activity else R.string.action_filter_by_recency) return true } @@ -178,10 +177,10 @@ class QuestionActivity : AppCompatActivity(), HasAndroidInjector { R.id.action_filter_by_recency -> { if (item.title == getString(R.string.action_filter_by_recency)) { switchView(getString(R.string.recent_questions), AppConstants.SORT_BY_CREATION) - item.setTitle(R.string.action_filter_by_activity) + isRecencySelected = true } else if (item.title == getString(R.string.action_filter_by_activity)) { switchView(getString(R.string.active_questions), AppConstants.SORT_BY_ACTIVITY) - item.setTitle(R.string.action_filter_by_recency) + isRecencySelected = false } return true } @@ -235,10 +234,6 @@ class QuestionActivity : AppCompatActivity(), HasAndroidInjector { finish() } - override fun androidInjector(): AndroidInjector { - return dispatchingAndroidInjector - } - private fun checkForTablet() { val isTablet = resources.getBoolean(R.bool.isTablet) if (isTablet) { @@ -283,9 +278,8 @@ class QuestionActivity : AppCompatActivity(), HasAndroidInjector { try { appUpdateManager?.startUpdateFlowForResult( appUpdateInfo, - AppUpdateType.FLEXIBLE, - this@QuestionActivity, - APP_UPDATE + appUpdateLauncher, + AppUpdateOptions.newBuilder(AppUpdateType.FLEXIBLE).build(), ) } catch (e: SendIntentException) { Timber.e(e) @@ -312,17 +306,6 @@ class QuestionActivity : AppCompatActivity(), HasAndroidInjector { } } - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - if (requestCode == APP_UPDATE && resultCode != RESULT_OK) { - checkForUpdate() - } - } - - companion object { - private const val APP_UPDATE = 10 - } - private val tagSelectionListener = object : TagsDialogFragment.TagSelectionCallback { override fun onTagSelected(tagName: String) { switchView( diff --git a/app/src/main/java/com/josycom/mayorjay/flowoverstack/view/home/QuestionsFragment.kt b/app/src/main/java/com/josycom/mayorjay/flowoverstack/view/home/QuestionsFragment.kt index 83f2a70..223cf2e 100644 --- a/app/src/main/java/com/josycom/mayorjay/flowoverstack/view/home/QuestionsFragment.kt +++ b/app/src/main/java/com/josycom/mayorjay/flowoverstack/view/home/QuestionsFragment.kt @@ -1,6 +1,5 @@ package com.josycom.mayorjay.flowoverstack.view.home -import android.content.Context import android.content.Intent import android.os.Bundle import android.view.LayoutInflater @@ -10,8 +9,8 @@ import androidx.appcompat.app.AppCompatActivity import androidx.core.view.isInvisible import androidx.core.view.isVisible import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels import androidx.lifecycle.Lifecycle -import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import androidx.paging.LoadState @@ -24,52 +23,42 @@ import com.josycom.mayorjay.flowoverstack.databinding.FragmentQuestionsBinding import com.josycom.mayorjay.flowoverstack.util.AppConstants import com.josycom.mayorjay.flowoverstack.util.AppUtils import com.josycom.mayorjay.flowoverstack.view.answer.AnswerActivity -import com.josycom.mayorjay.flowoverstack.viewmodel.CustomQuestionViewModelFactory import com.josycom.mayorjay.flowoverstack.viewmodel.QuestionViewModel -import dagger.android.support.AndroidSupportInjection +import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch -import javax.inject.Inject /** * This [Fragment] houses all Questions */ +@AndroidEntryPoint class QuestionsFragment : Fragment() { private lateinit var binding: FragmentQuestionsBinding - private lateinit var viewModel: QuestionViewModel - @Inject - lateinit var viewModelFactory: CustomQuestionViewModelFactory - - override fun onAttach(context: Context) { - AndroidSupportInjection.inject(this) - super.onAttach(context) - - viewModelFactory.setInputs( - AppConstants.FIRST_PAGE, - AppConstants.PAGE_SIZE, - AppConstants.ORDER_DESCENDING, - sortCondition, - AppConstants.SITE, - tagName, - AppConstants.QUESTION_FILTER, - AppConstants.API_KEY - ) - } + private val viewModel: QuestionViewModel by viewModels() override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { - binding = FragmentQuestionsBinding.inflate(inflater, container, false) + binding = FragmentQuestionsBinding.inflate(layoutInflater) return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - viewModel = ViewModelProvider(this, viewModelFactory).get(QuestionViewModel::class.java) + viewModel.init( + AppConstants.FIRST_PAGE, + AppConstants.PAGE_SIZE, + AppConstants.ORDER_DESCENDING, + sortCondition, + AppConstants.SITE, + tagName, + AppConstants.QUESTION_FILTER, + AppConstants.API_KEY + ) initViews() fetchAndDisplayQuestions() diff --git a/app/src/main/java/com/josycom/mayorjay/flowoverstack/view/init/SplashActivity.kt b/app/src/main/java/com/josycom/mayorjay/flowoverstack/view/init/SplashActivity.kt index b666854..46ec003 100644 --- a/app/src/main/java/com/josycom/mayorjay/flowoverstack/view/init/SplashActivity.kt +++ b/app/src/main/java/com/josycom/mayorjay/flowoverstack/view/init/SplashActivity.kt @@ -7,11 +7,13 @@ import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.lifecycleScope import com.josycom.mayorjay.flowoverstack.util.AppConstants import com.josycom.mayorjay.flowoverstack.view.home.QuestionActivity +import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.launch @SuppressLint("CustomSplashScreen") +@AndroidEntryPoint class SplashActivity : AppCompatActivity() { private var job: Job? = null diff --git a/app/src/main/java/com/josycom/mayorjay/flowoverstack/view/ocr/OcrActivity.kt b/app/src/main/java/com/josycom/mayorjay/flowoverstack/view/ocr/OcrActivity.kt index 4b19ac5..f14aba0 100644 --- a/app/src/main/java/com/josycom/mayorjay/flowoverstack/view/ocr/OcrActivity.kt +++ b/app/src/main/java/com/josycom/mayorjay/flowoverstack/view/ocr/OcrActivity.kt @@ -1,74 +1,77 @@ package com.josycom.mayorjay.flowoverstack.view.ocr import android.Manifest +import android.content.ContentValues import android.content.DialogInterface import android.content.Intent import android.content.pm.PackageManager -import android.graphics.BitmapFactory +import android.graphics.Bitmap +import android.graphics.ImageDecoder import android.net.Uri import android.os.Build import android.os.Bundle -import android.os.Environment import android.provider.MediaStore import android.text.TextUtils import android.view.View +import android.view.inputmethod.InputMethodManager +import androidx.activity.result.ActivityResult +import androidx.activity.result.contract.ActivityResultContracts +import androidx.activity.viewModels import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity +import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat -import androidx.core.content.FileProvider import androidx.core.view.isGone import androidx.core.view.isInvisible import androidx.core.view.isVisible import androidx.core.widget.NestedScrollView -import androidx.lifecycle.ViewModelProvider import androidx.recyclerview.widget.DefaultItemAnimator import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.bumptech.glide.Glide +import com.canhub.cropper.CropImageContract +import com.canhub.cropper.CropImageContractOptions +import com.canhub.cropper.CropImageOptions +import com.canhub.cropper.CropImageView import com.google.mlkit.vision.common.InputImage import com.google.mlkit.vision.text.Text import com.google.mlkit.vision.text.TextRecognition import com.google.mlkit.vision.text.latin.TextRecognizerOptions import com.josycom.mayorjay.flowoverstack.R -import com.josycom.mayorjay.flowoverstack.databinding.ActivityOcrBinding import com.josycom.mayorjay.flowoverstack.data.model.Question -import com.josycom.mayorjay.flowoverstack.view.search.SearchAdapter -import com.josycom.mayorjay.flowoverstack.viewmodel.CustomSearchViewModelFactory -import com.josycom.mayorjay.flowoverstack.viewmodel.SearchViewModel +import com.josycom.mayorjay.flowoverstack.databinding.ActivityOcrBinding import com.josycom.mayorjay.flowoverstack.util.AppConstants import com.josycom.mayorjay.flowoverstack.util.AppUtils import com.josycom.mayorjay.flowoverstack.view.answer.AnswerActivity import com.josycom.mayorjay.flowoverstack.view.home.QuestionActivity -import com.theartofdev.edmodo.cropper.CropImage -import com.theartofdev.edmodo.cropper.CropImageView -import dagger.android.AndroidInjection +import com.josycom.mayorjay.flowoverstack.view.search.SearchAdapter +import com.josycom.mayorjay.flowoverstack.viewmodel.SearchViewModel +import dagger.hilt.android.AndroidEntryPoint import timber.log.Timber -import java.io.File import java.io.IOException -import java.text.SimpleDateFormat -import java.util.Date -import java.util.Locale -import javax.inject.Inject +@AndroidEntryPoint class OcrActivity : AppCompatActivity() { private lateinit var binding: ActivityOcrBinding - private var photoPath: String? = null + private var photoUri: Uri? = null private val requiredPermissions = arrayOf(Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE) private var questions: List? = listOf() private var searchInput: String = "" - private lateinit var searchViewModel: SearchViewModel + private val searchViewModel: SearchViewModel by viewModels() - @Inject - lateinit var viewModelFactory: CustomSearchViewModelFactory + private val imageCaptureLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> + beginImageCropping(result) + } + + private val imageCropperLauncher = registerForActivityResult(CropImageContract()) { result -> + beginImageAnalysis(result) + } override fun onCreate(savedInstanceState: Bundle?) { - AndroidInjection.inject(this) super.onCreate(savedInstanceState) binding = ActivityOcrBinding.inflate(layoutInflater) setContentView(binding.root) - viewModelFactory.setInputs(AppConstants.FIRST_PAGE, AppConstants.SEARCH_PAGE_SIZE) - searchViewModel = ViewModelProvider(this, viewModelFactory).get(SearchViewModel::class.java) checkPermissionAndStartCamera() setupRecyclerView() hideAndShowScrollFab() @@ -100,14 +103,40 @@ class OcrActivity : AppCompatActivity() { startActivity(Intent(this, QuestionActivity::class.java)) } .setPositiveButton(getString(R.string.ask_me)) { _: DialogInterface?, _: Int -> - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - requestPermissions(requiredPermissions, PERMISSION_REQUEST_CODE) - } + ActivityCompat.requestPermissions(this, requiredPermissions, PERMISSION_REQUEST_CODE) }.show() } else { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - requestPermissions(requiredPermissions, PERMISSION_REQUEST_CODE) - } + ActivityCompat.requestPermissions(this, requiredPermissions, PERMISSION_REQUEST_CODE) + } + } + + private fun beginImageCropping(result: ActivityResult) { + if (result.resultCode == RESULT_CANCELED) { + finish() + } else { + imageCropperLauncher.launch( + CropImageContractOptions( + uri = photoUri, + cropImageOptions = CropImageOptions().apply { + guidelines = CropImageView.Guidelines.ON + outputCompressFormat = Bitmap.CompressFormat.JPEG + } + ) + ) + } + } + + private fun beginImageAnalysis(result: CropImageView.CropResult) { + if (result.isSuccessful) { + binding.ivCroppedImage.isVisible = true + val croppedImageUri = result.uriContent + Glide.with(this) + .load(croppedImageUri) + .into(binding.ivCroppedImage) + croppedImageUri?.let { analyseImage(it) } + } else { + Timber.e(result.error) + AppUtils.showToast(this, getString(R.string.an_error_occurred)) } } @@ -121,31 +150,13 @@ class OcrActivity : AppCompatActivity() { } val captureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE) if (captureIntent.resolveActivity(packageManager) != null) { - var photo: File? = null - try { - photo = createImageFile() - } catch (e: IOException) { - Timber.e(e) - } - if (photo != null) { - val photoUri = FileProvider.getUriForFile(this, "com.josycom.mayorjay.flowoverstack.fileprovider", photo) - captureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri) - startActivityForResult(captureIntent, CAMERA_REQUEST_CODE) + val values = ContentValues().apply { + put(MediaStore.Images.Media.TITLE, "OCR Image") + put(MediaStore.Images.Media.DESCRIPTION, "Captured OCR Image") } - } - } - - private fun createImageFile(): File? { - return try { - val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date()) - val imageFileName = "JPEG_" + timeStamp + "_" - val storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES) - val image = File.createTempFile(imageFileName, ".jpg", storageDir) - photoPath = image.absolutePath - image - } catch (ex: IOException) { - Timber.e(ex) - null + photoUri = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values) + captureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri) + imageCaptureLauncher.launch(captureIntent) } } @@ -155,60 +166,38 @@ class OcrActivity : AppCompatActivity() { } private fun allPermissionsGranted(): Boolean { - var granted = false - for (item in requiredPermissions) { - granted = ContextCompat.checkSelfPermission(this, item) == PackageManager.PERMISSION_GRANTED + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + // Android is 10 (Q) or above + val camera = ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) + camera == PackageManager.PERMISSION_GRANTED + } else { + // Below Android 10 + val camera = ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) + val write = ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) + camera == PackageManager.PERMISSION_GRANTED && write == PackageManager.PERMISSION_GRANTED } - return granted } private fun shouldShowRequestPermissionRationale(): Boolean { var shouldRequest = false - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - for (item in requiredPermissions) { - shouldRequest = shouldShowRequestPermissionRationale(item) - } + for (item in requiredPermissions) { + shouldRequest = shouldShowRequestPermissionRationale(item) } return shouldRequest } - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - if (resultCode == RESULT_CANCELED) { - finish() - return - } - binding.ivCroppedImage.isVisible = true - val bitmap = BitmapFactory.decodeFile(photoPath) - if (requestCode == CAMERA_REQUEST_CODE) { - if (resultCode == RESULT_OK && bitmap != null && photoPath != null) { - val file = File(photoPath ?: "") - val uri = Uri.fromFile(file) - CropImage.activity(uri) - .setGuidelines(CropImageView.Guidelines.ON) - .start(this) - } - } else if (requestCode == CropImage.CROP_IMAGE_ACTIVITY_REQUEST_CODE) { - val result = CropImage.getActivityResult(data) - if (resultCode == RESULT_OK) { - val resultUri = result.uri - Glide.with(this) - .load(resultUri) - .into(binding.ivCroppedImage) - analyseImage(resultUri) - } else if (resultCode == CropImage.CROP_IMAGE_ACTIVITY_RESULT_ERROR_CODE) { - Timber.e(result.error) - AppUtils.showToast(this, getString(R.string.an_error_occurred)) - } - } - } - private fun analyseImage(resultUri: Uri) { binding.btRecognise.isVisible = true binding.btRecognise.setOnClickListener { binding.ocrProgressBar.isVisible = true try { - val bitmap = MediaStore.Images.Media.getBitmap(contentResolver, resultUri) + val bitmap = when { + Build.VERSION.SDK_INT >= Build.VERSION_CODES.P -> { + val source = ImageDecoder.createSource(contentResolver, resultUri) + ImageDecoder.decodeBitmap(source) + } + else -> MediaStore.Images.Media.getBitmap(contentResolver, resultUri) + } val image = InputImage.fromBitmap(bitmap, 0) val recognizer = TextRecognition.getClient(TextRecognizerOptions.DEFAULT_OPTIONS) recognizer.process(image) @@ -310,7 +299,9 @@ class OcrActivity : AppCompatActivity() { if (TextUtils.isEmpty(binding.ocrTextInputEditText.text.toString())) { binding.ocrTextInputEditText.error = getString(R.string.ocr_et_error_message) } else { - searchInput = binding.ocrTextInputEditText.text.toString() + searchInput = binding.ocrTextInputEditText.text.toString().trim() + val inputMethodManager = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager + inputMethodManager.hideSoftInputFromWindow(currentFocus?.windowToken, 0) setQuery() } } @@ -360,6 +351,5 @@ class OcrActivity : AppCompatActivity() { companion object { private const val PERMISSION_REQUEST_CODE = 100 - private const val CAMERA_REQUEST_CODE = 101 } } \ No newline at end of file diff --git a/app/src/main/java/com/josycom/mayorjay/flowoverstack/view/search/SearchActivity.kt b/app/src/main/java/com/josycom/mayorjay/flowoverstack/view/search/SearchActivity.kt index 980c535..5217d66 100644 --- a/app/src/main/java/com/josycom/mayorjay/flowoverstack/view/search/SearchActivity.kt +++ b/app/src/main/java/com/josycom/mayorjay/flowoverstack/view/search/SearchActivity.kt @@ -6,11 +6,11 @@ import android.text.TextUtils import android.view.MenuItem import android.view.View import android.view.inputmethod.InputMethodManager +import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity import androidx.core.view.isInvisible import androidx.core.view.isVisible import androidx.core.widget.NestedScrollView -import androidx.lifecycle.ViewModelProvider import androidx.recyclerview.widget.DefaultItemAnimator import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView @@ -18,31 +18,24 @@ import com.josycom.mayorjay.flowoverstack.R import com.josycom.mayorjay.flowoverstack.databinding.ActivitySearchBinding import com.josycom.mayorjay.flowoverstack.data.model.Question import com.josycom.mayorjay.flowoverstack.data.remote.model.SearchResponse -import com.josycom.mayorjay.flowoverstack.viewmodel.CustomSearchViewModelFactory import com.josycom.mayorjay.flowoverstack.viewmodel.SearchViewModel import com.josycom.mayorjay.flowoverstack.util.AppConstants import com.josycom.mayorjay.flowoverstack.util.AppUtils import com.josycom.mayorjay.flowoverstack.view.answer.AnswerActivity -import dagger.android.AndroidInjection -import javax.inject.Inject +import dagger.hilt.android.AndroidEntryPoint +@AndroidEntryPoint class SearchActivity : AppCompatActivity() { private lateinit var binding: ActivitySearchBinding private var searchInput: String = "" private var questions: List? = listOf() - private lateinit var searchViewModel: SearchViewModel - - @Inject - lateinit var viewModelFactory: CustomSearchViewModelFactory + private val searchViewModel: SearchViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { - AndroidInjection.inject(this) super.onCreate(savedInstanceState) binding = ActivitySearchBinding.inflate(layoutInflater) setContentView(binding.root) - viewModelFactory.setInputs(AppConstants.FIRST_PAGE, AppConstants.SEARCH_PAGE_SIZE) - searchViewModel = ViewModelProvider(this, viewModelFactory).get(SearchViewModel::class.java) binding.apply { rvSearchResults.layoutManager = LinearLayoutManager(this@SearchActivity) rvSearchResults.setHasFixedSize(true) @@ -80,6 +73,7 @@ class SearchActivity : AppCompatActivity() { } AppConstants.NO_MATCHING_RESULT -> onNoMatchingResult() AppConstants.FAILED -> onError() + else -> searchAdapter.setQuestions(null) } } binding.rvSearchResults.adapter = searchAdapter diff --git a/app/src/main/java/com/josycom/mayorjay/flowoverstack/view/tag/TagsDialogFragment.kt b/app/src/main/java/com/josycom/mayorjay/flowoverstack/view/tag/TagsDialogFragment.kt index 74ae5cc..27ee747 100644 --- a/app/src/main/java/com/josycom/mayorjay/flowoverstack/view/tag/TagsDialogFragment.kt +++ b/app/src/main/java/com/josycom/mayorjay/flowoverstack/view/tag/TagsDialogFragment.kt @@ -1,6 +1,5 @@ package com.josycom.mayorjay.flowoverstack.view.tag -import android.content.Context import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -10,8 +9,8 @@ import androidx.appcompat.app.AppCompatActivity import androidx.core.view.isInvisible import androidx.core.view.isVisible import androidx.fragment.app.DialogFragment +import androidx.fragment.app.viewModels import androidx.lifecycle.Lifecycle -import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import androidx.paging.LoadState @@ -21,45 +20,33 @@ import androidx.recyclerview.widget.RecyclerView import com.josycom.mayorjay.flowoverstack.R import com.josycom.mayorjay.flowoverstack.databinding.TagsDialogFragmentBinding import com.josycom.mayorjay.flowoverstack.view.home.PagingLoadStateAdapter -import com.josycom.mayorjay.flowoverstack.viewmodel.CustomTagsViewModelFactory import com.josycom.mayorjay.flowoverstack.viewmodel.TagsDialogViewModel import com.josycom.mayorjay.flowoverstack.util.AppConstants -import dagger.android.support.AndroidSupportInjection -import kotlinx.coroutines.flow.collect +import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch import org.jsoup.internal.StringUtil import java.util.Locale -import javax.inject.Inject +@AndroidEntryPoint class TagsDialogFragment : DialogFragment() { private lateinit var activity: AppCompatActivity - private lateinit var viewModel: TagsDialogViewModel + private val viewModel: TagsDialogViewModel by viewModels() private lateinit var binding: TagsDialogFragmentBinding - @Inject - lateinit var viewModelFactory: CustomTagsViewModelFactory private var tagSelectionCallback: TagSelectionCallback? = null private var title: String = "" private var isPopularTagOption: Boolean = false - override fun onAttach(context: Context) { - AndroidSupportInjection.inject(this) - super.onAttach(context) - - viewModelFactory.setInputs(AppConstants.FIRST_PAGE, AppConstants.PAGE_SIZE, AppConstants.API_KEY) - } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { activity = getActivity() as AppCompatActivity - binding = TagsDialogFragmentBinding.inflate(LayoutInflater.from(activity)) + binding = TagsDialogFragmentBinding.inflate(layoutInflater) return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - viewModel = ViewModelProvider(this, viewModelFactory).get(TagsDialogViewModel::class.java) initViews() if (isPopularTagOption) { fetchAndDisplayTags("") @@ -88,7 +75,7 @@ class TagsDialogFragment : DialogFragment() { } private fun fetchAndDisplayTags(inName: String) { - viewModel.fetchTags(inName) + viewModel.fetchTags(inName, AppConstants.FIRST_PAGE, AppConstants.PAGE_SIZE, AppConstants.API_KEY) binding.ivLookup.isInvisible = true val popularTagAdapter = TagsAdapter() binding.rvPopularTags.apply { diff --git a/app/src/main/java/com/josycom/mayorjay/flowoverstack/viewmodel/AnswerViewModel.kt b/app/src/main/java/com/josycom/mayorjay/flowoverstack/viewmodel/AnswerViewModel.kt index 0bc8ccd..a190225 100644 --- a/app/src/main/java/com/josycom/mayorjay/flowoverstack/viewmodel/AnswerViewModel.kt +++ b/app/src/main/java/com/josycom/mayorjay/flowoverstack/viewmodel/AnswerViewModel.kt @@ -6,13 +6,16 @@ import androidx.lifecycle.viewModelScope import androidx.paging.PagingData import androidx.paging.cachedIn import com.josycom.mayorjay.flowoverstack.data.model.Answer +import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.Flow +import javax.inject.Inject -class AnswerViewModel(answerRepository: AnswerRepository, questionId: Int, order: String, sortCondition: String, site: String, filter: String, siteKey: String) : ViewModel() { +@HiltViewModel +class AnswerViewModel @Inject constructor(private val answerRepository: AnswerRepository) : ViewModel() { - val answerDataFlow: Flow>? + var answerDataFlow: Flow>? = null - init { + fun init(questionId: Int, order: String, sortCondition: String, site: String, filter: String, siteKey: String) { answerRepository.init(questionId, order, sortCondition, site, filter, siteKey) answerDataFlow = answerRepository.answerDataFlow?.cachedIn(viewModelScope) } diff --git a/app/src/main/java/com/josycom/mayorjay/flowoverstack/viewmodel/CustomAnswerViewModelFactory.kt b/app/src/main/java/com/josycom/mayorjay/flowoverstack/viewmodel/CustomAnswerViewModelFactory.kt deleted file mode 100644 index b68791b..0000000 --- a/app/src/main/java/com/josycom/mayorjay/flowoverstack/viewmodel/CustomAnswerViewModelFactory.kt +++ /dev/null @@ -1,36 +0,0 @@ -package com.josycom.mayorjay.flowoverstack.viewmodel - -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider -import com.josycom.mayorjay.flowoverstack.data.repository.AnswerRepository -import javax.inject.Inject - -class CustomAnswerViewModelFactory @Inject constructor(private val answerRepository: AnswerRepository) : ViewModelProvider.Factory { - - private var order: String = "" - private var sortCondition: String = "" - private var site: String = "" - private var filter: String = "" - private var questionId = 0 - private var siteKey: String = "" - - fun setInputs(questionId: Int, order: String, sortCondition: String, site: String, filter: String, siteKey: String) { - this.questionId = questionId - this.order = order - this.sortCondition = sortCondition - this.site = site - this.filter = filter - this.siteKey = siteKey - } - - @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T { - when { - modelClass.isAssignableFrom(AnswerViewModel::class.java) -> return AnswerViewModel( - answerRepository, questionId, order, - sortCondition, site, filter, siteKey - ) as T - else -> throw IllegalArgumentException("Wrong Class Provided $modelClass") - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/josycom/mayorjay/flowoverstack/viewmodel/CustomQuestionViewModelFactory.kt b/app/src/main/java/com/josycom/mayorjay/flowoverstack/viewmodel/CustomQuestionViewModelFactory.kt deleted file mode 100644 index f522860..0000000 --- a/app/src/main/java/com/josycom/mayorjay/flowoverstack/viewmodel/CustomQuestionViewModelFactory.kt +++ /dev/null @@ -1,40 +0,0 @@ -package com.josycom.mayorjay.flowoverstack.viewmodel - -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider -import com.josycom.mayorjay.flowoverstack.data.repository.QuestionRepository -import javax.inject.Inject - -class CustomQuestionViewModelFactory @Inject constructor(private val questionRepository: QuestionRepository) : ViewModelProvider.Factory { - - private var page = 0 - private var pageSize = 0 - private var order: String = "" - private var sortCondition: String = "" - private var site: String = "" - private var tagged: String = "" - private var filter: String = "" - private var siteKey: String = "" - - fun setInputs(page: Int, pageSize: Int, order: String, sortCondition: String, site: String, tagged: String, filter: String, siteKey: String) { - this.page = page - this.pageSize = pageSize - this.order = order - this.sortCondition = sortCondition - this.site = site - this.tagged = tagged - this.filter = filter - this.siteKey = siteKey - } - - @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T { - when { - modelClass.isAssignableFrom(QuestionViewModel::class.java) -> return QuestionViewModel( - questionRepository, page, pageSize, - order, sortCondition, site, tagged, filter, siteKey - ) as T - else -> throw IllegalArgumentException("Wrong Class Provided $modelClass") - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/josycom/mayorjay/flowoverstack/viewmodel/CustomSearchViewModelFactory.kt b/app/src/main/java/com/josycom/mayorjay/flowoverstack/viewmodel/CustomSearchViewModelFactory.kt deleted file mode 100644 index c25e842..0000000 --- a/app/src/main/java/com/josycom/mayorjay/flowoverstack/viewmodel/CustomSearchViewModelFactory.kt +++ /dev/null @@ -1,29 +0,0 @@ -package com.josycom.mayorjay.flowoverstack.viewmodel - -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider -import com.josycom.mayorjay.flowoverstack.data.repository.SearchRepository -import javax.inject.Inject - -class CustomSearchViewModelFactory @Inject constructor(private val searchRepository: SearchRepository) : ViewModelProvider.Factory { - - private var page = 0 - private var pageSize = 0 - - fun setInputs(page: Int, pageSize: Int) { - this.page = page - this.pageSize = pageSize - } - - @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T { - when { - modelClass.isAssignableFrom(SearchViewModel::class.java) -> return SearchViewModel( - page, - pageSize, - searchRepository - ) as T - else -> throw IllegalArgumentException("Wrong Class Provided $modelClass") - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/josycom/mayorjay/flowoverstack/viewmodel/CustomTagsViewModelFactory.kt b/app/src/main/java/com/josycom/mayorjay/flowoverstack/viewmodel/CustomTagsViewModelFactory.kt deleted file mode 100644 index a020b58..0000000 --- a/app/src/main/java/com/josycom/mayorjay/flowoverstack/viewmodel/CustomTagsViewModelFactory.kt +++ /dev/null @@ -1,32 +0,0 @@ -package com.josycom.mayorjay.flowoverstack.viewmodel - -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider -import com.josycom.mayorjay.flowoverstack.data.repository.TagRepository -import javax.inject.Inject - -class CustomTagsViewModelFactory @Inject constructor(private val tagRepository: TagRepository) : ViewModelProvider.Factory { - - private var page = 0 - private var pageSize = 0 - private var siteKey: String = "" - - fun setInputs(page: Int, pageSize: Int, siteKey: String) { - this.page = page - this.pageSize = pageSize - this.siteKey = siteKey - } - - @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T { - when { - modelClass.isAssignableFrom(TagsDialogViewModel::class.java) -> return TagsDialogViewModel( - tagRepository, - page, - pageSize, - siteKey - ) as T - else -> throw IllegalArgumentException("Wrong Class Provided $modelClass") - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/josycom/mayorjay/flowoverstack/viewmodel/QuestionActivityViewModel.kt b/app/src/main/java/com/josycom/mayorjay/flowoverstack/viewmodel/QuestionActivityViewModel.kt index 98c192c..e053adf 100644 --- a/app/src/main/java/com/josycom/mayorjay/flowoverstack/viewmodel/QuestionActivityViewModel.kt +++ b/app/src/main/java/com/josycom/mayorjay/flowoverstack/viewmodel/QuestionActivityViewModel.kt @@ -2,14 +2,16 @@ package com.josycom.mayorjay.flowoverstack.viewmodel import androidx.lifecycle.LiveData import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.asLiveData import androidx.lifecycle.viewModelScope import com.josycom.mayorjay.flowoverstack.data.repository.PreferenceRepository +import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.launch +import javax.inject.Inject -class QuestionActivityViewModel(private val preferenceRepository: PreferenceRepository) : +@HiltViewModel +class QuestionActivityViewModel @Inject constructor(private val preferenceRepository: PreferenceRepository) : ViewModel() { var appOpenCountLiveData: LiveData? = null @@ -32,18 +34,4 @@ class QuestionActivityViewModel(private val preferenceRepository: PreferenceRepo preferenceRepository.deleteAllPreferences() } } -} - -class ViewModelProviderFactory(private val preferenceRepository: PreferenceRepository) : - ViewModelProvider.Factory { - - @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T { - when { - modelClass.isAssignableFrom(QuestionActivityViewModel::class.java) -> return QuestionActivityViewModel( - preferenceRepository - ) as T - else -> throw IllegalArgumentException("Wrong Class Provided $modelClass") - } - } } \ No newline at end of file diff --git a/app/src/main/java/com/josycom/mayorjay/flowoverstack/viewmodel/QuestionViewModel.kt b/app/src/main/java/com/josycom/mayorjay/flowoverstack/viewmodel/QuestionViewModel.kt index 58c5e49..fff7d37 100644 --- a/app/src/main/java/com/josycom/mayorjay/flowoverstack/viewmodel/QuestionViewModel.kt +++ b/app/src/main/java/com/josycom/mayorjay/flowoverstack/viewmodel/QuestionViewModel.kt @@ -6,14 +6,16 @@ import androidx.paging.PagingData import androidx.paging.cachedIn import com.josycom.mayorjay.flowoverstack.data.model.Question import com.josycom.mayorjay.flowoverstack.data.repository.QuestionRepository +import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.Flow import javax.inject.Inject -class QuestionViewModel @Inject constructor(questionRepository: QuestionRepository, page: Int, pageSize: Int, order: String, sortCondition: String, site: String, tagged: String, filter: String, siteKey: String) : ViewModel() { +@HiltViewModel +class QuestionViewModel @Inject constructor(private val questionRepository: QuestionRepository) : ViewModel() { - val questionDataFlow: Flow>? + var questionDataFlow: Flow>? = null - init { + fun init(page: Int, pageSize: Int, order: String, sortCondition: String, site: String, tagged: String, filter: String, siteKey: String) { questionRepository.init(page, pageSize, order, sortCondition, site, tagged, filter, siteKey) questionDataFlow = questionRepository.questionDataFlow?.cachedIn(viewModelScope) } diff --git a/app/src/main/java/com/josycom/mayorjay/flowoverstack/viewmodel/SearchViewModel.kt b/app/src/main/java/com/josycom/mayorjay/flowoverstack/viewmodel/SearchViewModel.kt index c06b720..aa10da0 100644 --- a/app/src/main/java/com/josycom/mayorjay/flowoverstack/viewmodel/SearchViewModel.kt +++ b/app/src/main/java/com/josycom/mayorjay/flowoverstack/viewmodel/SearchViewModel.kt @@ -1,17 +1,22 @@ package com.josycom.mayorjay.flowoverstack.viewmodel -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.Transformations import androidx.lifecycle.ViewModel +import com.josycom.mayorjay.flowoverstack.data.remote.model.SearchResponse import com.josycom.mayorjay.flowoverstack.data.repository.SearchRepository +import com.josycom.mayorjay.flowoverstack.util.AppConstants +import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject -class SearchViewModel @Inject constructor(private val page: Int, private val pageSize: Int, private val mSearchRepository: SearchRepository) : ViewModel() { +@HiltViewModel +class SearchViewModel @Inject constructor(private val searchRepository: SearchRepository) : ViewModel() { - private val mSearchLiveData = MutableLiveData() - val responseLiveData = Transformations.switchMap(mSearchLiveData) { query: String? -> mSearchRepository.getResponse(query, page, pageSize) } + val responseLiveData = searchRepository.searchResponse + + init { + responseLiveData.value = SearchResponse() + } fun setQuery(query: String) { - mSearchLiveData.value = query + searchRepository.performSearch(query, AppConstants.FIRST_PAGE, AppConstants.SEARCH_PAGE_SIZE) } } \ No newline at end of file diff --git a/app/src/main/java/com/josycom/mayorjay/flowoverstack/viewmodel/TagsDialogViewModel.kt b/app/src/main/java/com/josycom/mayorjay/flowoverstack/viewmodel/TagsDialogViewModel.kt index 4712e12..4225782 100644 --- a/app/src/main/java/com/josycom/mayorjay/flowoverstack/viewmodel/TagsDialogViewModel.kt +++ b/app/src/main/java/com/josycom/mayorjay/flowoverstack/viewmodel/TagsDialogViewModel.kt @@ -6,14 +6,16 @@ import androidx.paging.PagingData import androidx.paging.cachedIn import com.josycom.mayorjay.flowoverstack.data.repository.TagRepository import com.josycom.mayorjay.flowoverstack.data.model.Tag +import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.Flow import javax.inject.Inject -class TagsDialogViewModel @Inject constructor(private val tagRepository: TagRepository, val page: Int, val pageSize: Int, val siteKey: String) : ViewModel() { +@HiltViewModel +class TagsDialogViewModel @Inject constructor(private val tagRepository: TagRepository) : ViewModel() { var tagDataFlow: Flow>? = null - fun fetchTags(inName: String) { + fun fetchTags(inName: String, page: Int, pageSize: Int, siteKey: String) { tagRepository.init(page, pageSize, inName, siteKey) tagDataFlow = tagRepository.tagDataFlow?.cachedIn(viewModelScope) } diff --git a/build.gradle b/build.gradle index 5201e9e..d1c8552 100644 --- a/build.gradle +++ b/build.gradle @@ -2,18 +2,21 @@ buildscript { - ext.kotlin_version = "1.5.31" + ext.kotlin_version = "1.9.24" + ext.hilt_version = '2.52' repositories { google() - jcenter() + mavenCentral() + maven { url "https://jitpack.io" } } dependencies { - classpath 'com.android.tools.build:gradle:4.0.2' + classpath 'com.android.tools.build:gradle:8.7.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath 'org.robolectric:robolectric-gradle-plugin:1.0.1' classpath 'com.google.gms:google-services:4.3.10' classpath 'com.google.firebase:firebase-crashlytics-gradle:2.8.0' + classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } @@ -22,11 +25,11 @@ buildscript { allprojects { repositories { google() - jcenter() + mavenCentral() maven { url "https://jitpack.io" } } } -task clean(type: Delete) { - delete rootProject.buildDir +tasks.register('clean', Delete) { + delete rootProject.layout.buildDirectory } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index b598004..8027569 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip