From f04dc683508c070f32cd9d17acb27e9cd61e93a3 Mon Sep 17 00:00:00 2001 From: kxxhyorim Date: Fri, 1 Dec 2023 18:24:17 +0900 Subject: [PATCH 01/11] [REFACTOR] refactor ShareActivity (#903) --- .../org/sopt/havit/ui/share/ShareActivity.kt | 69 +++++++++---------- 1 file changed, 34 insertions(+), 35 deletions(-) diff --git a/app/src/main/java/org/sopt/havit/ui/share/ShareActivity.kt b/app/src/main/java/org/sopt/havit/ui/share/ShareActivity.kt index 2b2ca957..52426326 100644 --- a/app/src/main/java/org/sopt/havit/ui/share/ShareActivity.kt +++ b/app/src/main/java/org/sopt/havit/ui/share/ShareActivity.kt @@ -1,5 +1,6 @@ package org.sopt.havit.ui.share +import android.annotation.SuppressLint import android.app.Activity import android.content.Intent import android.content.pm.ActivityInfo @@ -20,76 +21,74 @@ import java.io.Serializable @AndroidEntryPoint class ShareActivity : BaseBindingActivity(R.layout.activity_share) { - private val viewModel: ShareViewModel by viewModels() - private lateinit var splashWithSignActivityLauncher: ActivityResultLauncher + private val shareViewModel: ShareViewModel by viewModels() + private lateinit var splashWithSignLauncher: ActivityResultLauncher override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - binding = ActivityShareBinding.inflate(layoutInflater) - setContentView(binding.root) + + setScreenOrientation() + initializeActivityResultLauncher() + handleShareFlow() + } + + @SuppressLint("SourceLockedOrientationActivity") + private fun setScreenOrientation() { requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT - initActivityLauncher() - makeSignIn() - setUrlOnViewModel() - viewModel.setCrawlingContents() } - private fun initActivityLauncher() { - splashWithSignActivityLauncher = + private fun initializeActivityResultLauncher() { + splashWithSignLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { - onSlashWithSignActivityFinish(it) + handleSplashActivityResult(it) } } - private fun onSlashWithSignActivityFinish(result: ActivityResult) { + private fun handleSplashActivityResult(result: ActivityResult) { when (result.resultCode) { - Activity.RESULT_OK -> showBottomSheetShareFragment() + Activity.RESULT_OK -> showShareBottomSheet() else -> finish() } } - private fun isEnterWithShareProcess(intent: Intent?): Boolean { - // 공유하기 버튼으로 진입하면 return true - // MainActivity 의 + 버튼으로 진입하면 return false - return (intent?.action == Intent.ACTION_SEND) && (intent.type == "text/plain") + private fun handleShareFlow() { + initiateSignIn() + extractAndSetUrl() + shareViewModel.setCrawlingContents() } - private fun makeSignIn() { - viewModel.makeSignIn( - internetError = { showBottomSheetNetworkErrorFragment() }, - onUnAuthorized = { moveToSplashWithSignActivity() }, - onAuthorized = { showBottomSheetShareFragment() } + private fun initiateSignIn() { + shareViewModel.makeSignIn( + internetError = { showNetworkErrorBottomSheet() }, + onUnAuthorized = { moveToSplashWithSign() }, + onAuthorized = { showShareBottomSheet() } ) } - private fun moveToSplashWithSignActivity() { + private fun moveToSplashWithSign() { val intent = Intent(this, SplashWithSignActivity::class.java).apply { putExtra(WHERE_SPLASH_COME_FROM, SPLASH_FROM_SHARE) } - splashWithSignActivityLauncher.launch(intent) + splashWithSignLauncher.launch(intent) } - private fun showBottomSheetNetworkErrorFragment() { + private fun showNetworkErrorBottomSheet() { val bottomSheet = BottomSheetNetworkErrorFragment().apply { arguments = Bundle().apply { - putSerializable(ON_NETWORK_ERROR_DISMISS, { makeSignIn() } as Serializable) + putSerializable(ON_NETWORK_ERROR_DISMISS, { initiateSignIn() } as Serializable) } } bottomSheet.show(supportFragmentManager, bottomSheet.tag) } - private fun showBottomSheetShareFragment() { + private fun showShareBottomSheet() { val bottomSheet = BottomSheetShareFragment() bottomSheet.show(supportFragmentManager, bottomSheet.tag) } - private fun setUrlOnViewModel() { - val intent = this.intent - val url = - if (isEnterWithShareProcess(intent)) // 공유하기 버튼으로 진입시 - intent?.getStringExtra(Intent.EXTRA_TEXT).toString() - else intent?.getStringExtra("url").toString() // MainActivity + 로 진입시 - viewModel.setUrl(url) + private fun extractAndSetUrl() { + val url = intent?.getStringExtra(Intent.EXTRA_TEXT) ?: intent?.getStringExtra("url") + shareViewModel.setUrl(url.toString()) } override fun setRequestedOrientation(requestedOrientation: Int) { @@ -97,7 +96,7 @@ class ShareActivity : BaseBindingActivity(R.layout.activit super.setRequestedOrientation(requestedOrientation) } } - + companion object { const val WHERE_SPLASH_COME_FROM = "WHERE_SPLASH_COME_FROM" const val ON_NETWORK_ERROR_DISMISS = "ON_NETWORK_ERROR_DISMISS" From 702bf6ca05ce6e09e7d1d0b9a34bfd9d3377b80a Mon Sep 17 00:00:00 2001 From: kxxhyorim Date: Fri, 1 Dec 2023 18:25:56 +0900 Subject: [PATCH 02/11] =?UTF-8?q?[CHORE]=20gitignore=20=ED=95=AD=EB=AA=A9?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80=20(#903)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 98f4581a..6254be1c 100644 --- a/.gitignore +++ b/.gitignore @@ -170,4 +170,7 @@ release/ !/gradle/wrapper/gradle-wrapper.jar -# End of https://www.toptal.com/developers/gitignore/api/kotlin,androidstudio \ No newline at end of file +# End of https://www.toptal.com/developers/gitignore/api/kotlin,androidstudio +/.idea/sonarlint/* +/.idea/dbnavigator.xml +/.idea/migrations.xml From 2c6d88e43921b2168110ab5424e21f3e05459755 Mon Sep 17 00:00:00 2001 From: kxxhyorim Date: Fri, 1 Dec 2023 19:13:01 +0900 Subject: [PATCH 03/11] [FEAT] Set SystemMaintenance Files temporarily (#903) --- app/src/main/AndroidManifest.xml | 18 ++++++++++---- .../SystemMaintenanceRepositoryImpl.kt | 17 +++++++++++++ .../remote/SystemMaintenanceDataSource.kt | 8 +++++++ .../remote/SystemMaintenanceDataSourceImpl.kt | 17 +++++++++++++ .../org/sopt/havit/di/DataSourceModule.kt | 9 +++++++ .../org/sopt/havit/di/RepositoryModule.kt | 18 ++++++++++---- .../repository/SystemMaintenanceRepository.kt | 8 +++++++ .../SystemMaintenanceActivity.kt | 24 +++++++++++++++++++ .../SystemMaintenanceViewModel.kt | 20 ++++++++++++++++ .../layout/activity_system_maintenance.xml | 15 ++++++++++++ 10 files changed, 144 insertions(+), 10 deletions(-) create mode 100644 app/src/main/java/org/sopt/havit/data/repository/SystemMaintenanceRepositoryImpl.kt create mode 100644 app/src/main/java/org/sopt/havit/data/source/remote/SystemMaintenanceDataSource.kt create mode 100644 app/src/main/java/org/sopt/havit/data/source/remote/SystemMaintenanceDataSourceImpl.kt create mode 100644 app/src/main/java/org/sopt/havit/domain/repository/SystemMaintenanceRepository.kt create mode 100644 app/src/main/java/org/sopt/havit/ui/system_maintenance/SystemMaintenanceActivity.kt create mode 100644 app/src/main/java/org/sopt/havit/ui/system_maintenance/SystemMaintenanceViewModel.kt create mode 100644 app/src/main/res/layout/activity_system_maintenance.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 692aaca8..40176bc2 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,9 +1,10 @@ - + @@ -24,7 +25,14 @@ android:roundIcon="@mipmap/ic_launcher_havit_round" android:supportsRtl="true" android:theme="@style/Theme.Havit" - android:usesCleartextTraffic="true"> + android:usesCleartextTraffic="true" + tools:ignore="LockedOrientationActivity"> + + + + + android:screenOrientation="portrait" + android:windowSoftInputMode="stateVisible" /> - + \ No newline at end of file diff --git a/app/src/main/java/org/sopt/havit/data/repository/SystemMaintenanceRepositoryImpl.kt b/app/src/main/java/org/sopt/havit/data/repository/SystemMaintenanceRepositoryImpl.kt new file mode 100644 index 00000000..3ccdcada --- /dev/null +++ b/app/src/main/java/org/sopt/havit/data/repository/SystemMaintenanceRepositoryImpl.kt @@ -0,0 +1,17 @@ +package org.sopt.havit.data.repository + +import org.sopt.havit.data.source.remote.SystemMaintenanceDataSource +import org.sopt.havit.domain.repository.SystemMaintenanceRepository +import javax.inject.Inject + +class SystemMaintenanceRepositoryImpl @Inject constructor( + private val systemMaintenanceRemoteDataSource: SystemMaintenanceDataSource, +) : SystemMaintenanceRepository { + override fun isSystemMaintenance(): Boolean { + return systemMaintenanceRemoteDataSource.isSystemMaintenance() ?: false + } + + override fun getSystemMaintenanceMessage(): String { + return systemMaintenanceRemoteDataSource.getSystemMaintenanceMessage() ?: "unknown error" + } +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/havit/data/source/remote/SystemMaintenanceDataSource.kt b/app/src/main/java/org/sopt/havit/data/source/remote/SystemMaintenanceDataSource.kt new file mode 100644 index 00000000..c10d5caf --- /dev/null +++ b/app/src/main/java/org/sopt/havit/data/source/remote/SystemMaintenanceDataSource.kt @@ -0,0 +1,8 @@ +package org.sopt.havit.data.source.remote + +interface SystemMaintenanceDataSource { + + fun isSystemMaintenance(): Boolean? + + fun getSystemMaintenanceMessage(): String? +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/havit/data/source/remote/SystemMaintenanceDataSourceImpl.kt b/app/src/main/java/org/sopt/havit/data/source/remote/SystemMaintenanceDataSourceImpl.kt new file mode 100644 index 00000000..227bff06 --- /dev/null +++ b/app/src/main/java/org/sopt/havit/data/source/remote/SystemMaintenanceDataSourceImpl.kt @@ -0,0 +1,17 @@ +package org.sopt.havit.data.source.remote + +import android.content.Context +import dagger.hilt.android.qualifiers.ApplicationContext +import javax.inject.Inject + +class SystemMaintenanceDataSourceImpl @Inject constructor( + @ApplicationContext private val context: Context, +) : SystemMaintenanceDataSource { + override fun isSystemMaintenance(): Boolean? { + return true + } + + override fun getSystemMaintenanceMessage(): String? { + return "test" + } +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/havit/di/DataSourceModule.kt b/app/src/main/java/org/sopt/havit/di/DataSourceModule.kt index 553c7478..c5fdc7bf 100644 --- a/app/src/main/java/org/sopt/havit/di/DataSourceModule.kt +++ b/app/src/main/java/org/sopt/havit/di/DataSourceModule.kt @@ -1,8 +1,10 @@ package org.sopt.havit.di +import android.content.Context import dagger.Module import dagger.Provides import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent import org.sopt.havit.data.api.HavitApi import org.sopt.havit.data.local.HavitAuthLocalPreferences @@ -12,6 +14,8 @@ import org.sopt.havit.data.source.remote.AuthRemoteDataSource import org.sopt.havit.data.source.remote.AuthRemoteDataSourceImpl import org.sopt.havit.data.source.remote.SearchRemoteDataSource import org.sopt.havit.data.source.remote.SearchRemoteDataSourceImpl +import org.sopt.havit.data.source.remote.SystemMaintenanceDataSource +import org.sopt.havit.data.source.remote.SystemMaintenanceDataSourceImpl import org.sopt.havit.data.source.remote.category.CategoryRemoteDataSource import org.sopt.havit.data.source.remote.category.CategoryRemoteDataSourceImpl import org.sopt.havit.data.source.remote.contents.ContentsRemoteDataSource @@ -46,4 +50,9 @@ object DataSourceModule { @Singleton fun provideCategoryRemoteDataSource(api: HavitApi): CategoryRemoteDataSource = CategoryRemoteDataSourceImpl(api) + + @Provides + @Singleton + fun provideSystemMaintenanceDataSource(@ApplicationContext context: Context): SystemMaintenanceDataSource = + SystemMaintenanceDataSourceImpl(context = context) } diff --git a/app/src/main/java/org/sopt/havit/di/RepositoryModule.kt b/app/src/main/java/org/sopt/havit/di/RepositoryModule.kt index ac1b1284..644857fc 100644 --- a/app/src/main/java/org/sopt/havit/di/RepositoryModule.kt +++ b/app/src/main/java/org/sopt/havit/di/RepositoryModule.kt @@ -10,6 +10,7 @@ import org.sopt.havit.data.repository.* import org.sopt.havit.data.source.local.AuthLocalDataSourceImpl import org.sopt.havit.data.source.remote.AuthRemoteDataSourceImpl import org.sopt.havit.data.source.remote.SearchRemoteDataSourceImpl +import org.sopt.havit.data.source.remote.SystemMaintenanceDataSourceImpl import org.sopt.havit.data.source.remote.category.CategoryRemoteDataSourceImpl import org.sopt.havit.data.source.remote.contents.ContentsRemoteDataSourceImpl import org.sopt.havit.domain.repository.* @@ -22,34 +23,41 @@ object RepositoryModule { @Singleton fun provideSearchRepository( searchRemoteDataSourceImpl: SearchRemoteDataSourceImpl, - contentsMapper: ContentsMapper + contentsMapper: ContentsMapper, ): SearchRepository = SearchRepositoryImpl(searchRemoteDataSourceImpl, contentsMapper) @Provides @Singleton fun provideMyPageRepository( - havitApi: HavitApi + havitApi: HavitApi, ): MyPageRepository = MyPageRepositoryImpl(havitApi) @Provides @Singleton fun provideContentsRepository( contentsRemoteDataSourceImpl: ContentsRemoteDataSourceImpl, - havitApi: HavitApi + havitApi: HavitApi, ): ContentsRepository = ContentsRepositoryImpl(contentsRemoteDataSourceImpl, havitApi) @Provides @Singleton fun provideCategoryRepository( - categoryRemoteDataSourceImpl: CategoryRemoteDataSourceImpl + categoryRemoteDataSourceImpl: CategoryRemoteDataSourceImpl, ): CategoryRepository = CategoryRepositoryImpl(categoryRemoteDataSourceImpl) @Provides @Singleton fun provideAuthRepository( authRemoteDataSourceImpl: AuthRemoteDataSourceImpl, - authLocalDataSourceImpl: AuthLocalDataSourceImpl + authLocalDataSourceImpl: AuthLocalDataSourceImpl, ): AuthRepository = AuthRepositoryImpl(authRemoteDataSourceImpl, authLocalDataSourceImpl) + + + @Provides + @Singleton + fun provideSystemMaintenanceRepository( + systemMaintenanceDataSource: SystemMaintenanceDataSourceImpl, + ): SystemMaintenanceRepository = SystemMaintenanceRepositoryImpl(systemMaintenanceDataSource) } diff --git a/app/src/main/java/org/sopt/havit/domain/repository/SystemMaintenanceRepository.kt b/app/src/main/java/org/sopt/havit/domain/repository/SystemMaintenanceRepository.kt new file mode 100644 index 00000000..fc739d2d --- /dev/null +++ b/app/src/main/java/org/sopt/havit/domain/repository/SystemMaintenanceRepository.kt @@ -0,0 +1,8 @@ +package org.sopt.havit.domain.repository + +interface SystemMaintenanceRepository { + + fun isSystemMaintenance(): Boolean + + fun getSystemMaintenanceMessage(): String +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/havit/ui/system_maintenance/SystemMaintenanceActivity.kt b/app/src/main/java/org/sopt/havit/ui/system_maintenance/SystemMaintenanceActivity.kt new file mode 100644 index 00000000..b0fbe669 --- /dev/null +++ b/app/src/main/java/org/sopt/havit/ui/system_maintenance/SystemMaintenanceActivity.kt @@ -0,0 +1,24 @@ +package org.sopt.havit.ui.system_maintenance + +import android.os.Bundle +import android.util.Log +import androidx.activity.viewModels +import dagger.hilt.android.AndroidEntryPoint +import org.sopt.havit.HavitFirebaseMessagingService.Companion.TAG +import org.sopt.havit.R +import org.sopt.havit.databinding.ActivitySystemMaintenanceBinding +import org.sopt.havit.ui.base.BaseBindingActivity + +@AndroidEntryPoint +class SystemMaintenanceActivity : + BaseBindingActivity(R.layout.activity_system_maintenance) { + + private val systemMaintenanceViewModel: SystemMaintenanceViewModel by viewModels() + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + Log.d(TAG, "onCreate: ${systemMaintenanceViewModel.isSystemMaintenance()}") + Log.d(TAG, "onCreate: ${systemMaintenanceViewModel.getSystemMaintenanceMessage()}") + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/havit/ui/system_maintenance/SystemMaintenanceViewModel.kt b/app/src/main/java/org/sopt/havit/ui/system_maintenance/SystemMaintenanceViewModel.kt new file mode 100644 index 00000000..01c566ac --- /dev/null +++ b/app/src/main/java/org/sopt/havit/ui/system_maintenance/SystemMaintenanceViewModel.kt @@ -0,0 +1,20 @@ +package org.sopt.havit.ui.system_maintenance + +import androidx.lifecycle.ViewModel +import dagger.hilt.android.lifecycle.HiltViewModel +import org.sopt.havit.domain.repository.SystemMaintenanceRepository +import javax.inject.Inject + +@HiltViewModel +class SystemMaintenanceViewModel @Inject constructor( + private val systemMaintenanceRepository: SystemMaintenanceRepository, +) : ViewModel() { + + fun isSystemMaintenance(): Boolean { + return systemMaintenanceRepository.isSystemMaintenance() + } + + fun getSystemMaintenanceMessage(): String { + return systemMaintenanceRepository.getSystemMaintenanceMessage() + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_system_maintenance.xml b/app/src/main/res/layout/activity_system_maintenance.xml new file mode 100644 index 00000000..267fc8e1 --- /dev/null +++ b/app/src/main/res/layout/activity_system_maintenance.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + \ No newline at end of file From 41b04bd0c26dcf5b32e95c5890e2ff2daea299f7 Mon Sep 17 00:00:00 2001 From: kxxhyorim Date: Sat, 2 Dec 2023 20:28:54 +0900 Subject: [PATCH 04/11] [CHORE] Add firebase remote config dependency (#903) --- app/build.gradle | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index e22dc898..60ce2a7c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -137,13 +137,14 @@ dependencies { implementation 'com.google.firebase:firebase-messaging-ktx' implementation 'com.google.firebase:firebase-crashlytics-ktx' implementation 'com.google.firebase:firebase-analytics-ktx' + implementation 'com.google.firebase:firebase-config-ktx' + // Jsoup implementation 'org.jsoup:jsoup:1.13.1' // Splash Screen implementation 'androidx.core:core-splashscreen:1.0.0-rc01' - // gson implementation "com.google.code.gson:gson:2.8.8" //to use SerializedName From fdd029db2b0b9afae8a35ea92463093796c305f3 Mon Sep 17 00:00:00 2001 From: kxxhyorim Date: Sat, 2 Dec 2023 20:33:28 +0900 Subject: [PATCH 05/11] [FEAT] Get Remote Config Value using coroutine (#903) --- .../SystemMaintenanceRepositoryImpl.kt | 18 ++++++---- .../source/remote/RemoteConfigDataSource.kt | 7 ++++ .../remote/RemoteConfigDataSourceImpl.kt | 34 +++++++++++++++++++ .../remote/SystemMaintenanceDataSource.kt | 8 ----- .../remote/SystemMaintenanceDataSourceImpl.kt | 17 ---------- .../org/sopt/havit/di/DataSourceModule.kt | 10 +++--- .../org/sopt/havit/di/RepositoryModule.kt | 4 +-- .../repository/SystemMaintenanceRepository.kt | 4 +-- 8 files changed, 61 insertions(+), 41 deletions(-) create mode 100644 app/src/main/java/org/sopt/havit/data/source/remote/RemoteConfigDataSource.kt create mode 100644 app/src/main/java/org/sopt/havit/data/source/remote/RemoteConfigDataSourceImpl.kt delete mode 100644 app/src/main/java/org/sopt/havit/data/source/remote/SystemMaintenanceDataSource.kt delete mode 100644 app/src/main/java/org/sopt/havit/data/source/remote/SystemMaintenanceDataSourceImpl.kt diff --git a/app/src/main/java/org/sopt/havit/data/repository/SystemMaintenanceRepositoryImpl.kt b/app/src/main/java/org/sopt/havit/data/repository/SystemMaintenanceRepositoryImpl.kt index 3ccdcada..dc1e7b56 100644 --- a/app/src/main/java/org/sopt/havit/data/repository/SystemMaintenanceRepositoryImpl.kt +++ b/app/src/main/java/org/sopt/havit/data/repository/SystemMaintenanceRepositoryImpl.kt @@ -1,17 +1,23 @@ package org.sopt.havit.data.repository -import org.sopt.havit.data.source.remote.SystemMaintenanceDataSource +import org.sopt.havit.data.source.remote.RemoteConfigDataSource import org.sopt.havit.domain.repository.SystemMaintenanceRepository import javax.inject.Inject class SystemMaintenanceRepositoryImpl @Inject constructor( - private val systemMaintenanceRemoteDataSource: SystemMaintenanceDataSource, + private val systemMaintenanceRemoteDataSource: RemoteConfigDataSource, ) : SystemMaintenanceRepository { - override fun isSystemMaintenance(): Boolean { - return systemMaintenanceRemoteDataSource.isSystemMaintenance() ?: false + override suspend fun isSystemMaintenance(): Boolean { + val isSystemUnderMaintenance = systemMaintenanceRemoteDataSource.fetchRemoteConfig( + "isSystemUnderMaintenance", Boolean::class.java + ) as? Boolean + return isSystemUnderMaintenance ?: false } - override fun getSystemMaintenanceMessage(): String { - return systemMaintenanceRemoteDataSource.getSystemMaintenanceMessage() ?: "unknown error" + override suspend fun getSystemMaintenanceMessage(): String { + val systemMaintenanceMessage = systemMaintenanceRemoteDataSource.fetchRemoteConfig( + "systemMaintenanceMessage", String::class.java + ) as? String + return systemMaintenanceMessage ?: "" } } \ No newline at end of file diff --git a/app/src/main/java/org/sopt/havit/data/source/remote/RemoteConfigDataSource.kt b/app/src/main/java/org/sopt/havit/data/source/remote/RemoteConfigDataSource.kt new file mode 100644 index 00000000..4424d113 --- /dev/null +++ b/app/src/main/java/org/sopt/havit/data/source/remote/RemoteConfigDataSource.kt @@ -0,0 +1,7 @@ +package org.sopt.havit.data.source.remote + +import java.lang.reflect.Type + +interface RemoteConfigDataSource { + suspend fun fetchRemoteConfig(configKey: String, valueType: Type): Any +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/havit/data/source/remote/RemoteConfigDataSourceImpl.kt b/app/src/main/java/org/sopt/havit/data/source/remote/RemoteConfigDataSourceImpl.kt new file mode 100644 index 00000000..acf3b9b5 --- /dev/null +++ b/app/src/main/java/org/sopt/havit/data/source/remote/RemoteConfigDataSourceImpl.kt @@ -0,0 +1,34 @@ +package org.sopt.havit.data.source.remote + +import com.google.firebase.ktx.Firebase +import com.google.firebase.remoteconfig.FirebaseRemoteConfig +import com.google.firebase.remoteconfig.ktx.remoteConfig +import com.google.firebase.remoteconfig.ktx.remoteConfigSettings +import kotlinx.coroutines.suspendCancellableCoroutine +import java.lang.reflect.Type +import javax.inject.Inject + +class RemoteConfigDataSourceImpl @Inject constructor() : RemoteConfigDataSource { + + private val remoteConfig: FirebaseRemoteConfig = Firebase.remoteConfig.apply { + setConfigSettingsAsync(remoteConfigSettings { minimumFetchIntervalInSeconds = 180 }) + } + + override suspend fun fetchRemoteConfig(configKey: String, valueType: Type): Any { + return suspendCancellableCoroutine { continuation -> + remoteConfig.fetchAndActivate().addOnCompleteListener { task -> + if (task.isSuccessful) { + val remoteConfigValue = when (valueType) { + String::class.java -> remoteConfig.getString(configKey) + Boolean::class.java -> remoteConfig.getBoolean(configKey) + Long::class.java -> remoteConfig.getLong(configKey) + else -> throw IllegalArgumentException("Not supported type. Please check valueType") + } + continuation.resumeWith(Result.success(remoteConfigValue)) + } else continuation.resumeWith( + Result.failure(task.exception ?: Exception("fetchRemoteConfig failed")) + ) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/havit/data/source/remote/SystemMaintenanceDataSource.kt b/app/src/main/java/org/sopt/havit/data/source/remote/SystemMaintenanceDataSource.kt deleted file mode 100644 index c10d5caf..00000000 --- a/app/src/main/java/org/sopt/havit/data/source/remote/SystemMaintenanceDataSource.kt +++ /dev/null @@ -1,8 +0,0 @@ -package org.sopt.havit.data.source.remote - -interface SystemMaintenanceDataSource { - - fun isSystemMaintenance(): Boolean? - - fun getSystemMaintenanceMessage(): String? -} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/havit/data/source/remote/SystemMaintenanceDataSourceImpl.kt b/app/src/main/java/org/sopt/havit/data/source/remote/SystemMaintenanceDataSourceImpl.kt deleted file mode 100644 index 227bff06..00000000 --- a/app/src/main/java/org/sopt/havit/data/source/remote/SystemMaintenanceDataSourceImpl.kt +++ /dev/null @@ -1,17 +0,0 @@ -package org.sopt.havit.data.source.remote - -import android.content.Context -import dagger.hilt.android.qualifiers.ApplicationContext -import javax.inject.Inject - -class SystemMaintenanceDataSourceImpl @Inject constructor( - @ApplicationContext private val context: Context, -) : SystemMaintenanceDataSource { - override fun isSystemMaintenance(): Boolean? { - return true - } - - override fun getSystemMaintenanceMessage(): String? { - return "test" - } -} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/havit/di/DataSourceModule.kt b/app/src/main/java/org/sopt/havit/di/DataSourceModule.kt index c5fdc7bf..4131481c 100644 --- a/app/src/main/java/org/sopt/havit/di/DataSourceModule.kt +++ b/app/src/main/java/org/sopt/havit/di/DataSourceModule.kt @@ -1,10 +1,8 @@ package org.sopt.havit.di -import android.content.Context import dagger.Module import dagger.Provides import dagger.hilt.InstallIn -import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent import org.sopt.havit.data.api.HavitApi import org.sopt.havit.data.local.HavitAuthLocalPreferences @@ -12,10 +10,10 @@ import org.sopt.havit.data.source.local.AuthLocalDataSource import org.sopt.havit.data.source.local.AuthLocalDataSourceImpl import org.sopt.havit.data.source.remote.AuthRemoteDataSource import org.sopt.havit.data.source.remote.AuthRemoteDataSourceImpl +import org.sopt.havit.data.source.remote.RemoteConfigDataSource +import org.sopt.havit.data.source.remote.RemoteConfigDataSourceImpl import org.sopt.havit.data.source.remote.SearchRemoteDataSource import org.sopt.havit.data.source.remote.SearchRemoteDataSourceImpl -import org.sopt.havit.data.source.remote.SystemMaintenanceDataSource -import org.sopt.havit.data.source.remote.SystemMaintenanceDataSourceImpl import org.sopt.havit.data.source.remote.category.CategoryRemoteDataSource import org.sopt.havit.data.source.remote.category.CategoryRemoteDataSourceImpl import org.sopt.havit.data.source.remote.contents.ContentsRemoteDataSource @@ -53,6 +51,6 @@ object DataSourceModule { @Provides @Singleton - fun provideSystemMaintenanceDataSource(@ApplicationContext context: Context): SystemMaintenanceDataSource = - SystemMaintenanceDataSourceImpl(context = context) + fun provideRemoteConfigDataSource(): RemoteConfigDataSource = RemoteConfigDataSourceImpl() + } diff --git a/app/src/main/java/org/sopt/havit/di/RepositoryModule.kt b/app/src/main/java/org/sopt/havit/di/RepositoryModule.kt index 644857fc..a2fa4979 100644 --- a/app/src/main/java/org/sopt/havit/di/RepositoryModule.kt +++ b/app/src/main/java/org/sopt/havit/di/RepositoryModule.kt @@ -9,8 +9,8 @@ import org.sopt.havit.data.mapper.ContentsMapper import org.sopt.havit.data.repository.* import org.sopt.havit.data.source.local.AuthLocalDataSourceImpl import org.sopt.havit.data.source.remote.AuthRemoteDataSourceImpl +import org.sopt.havit.data.source.remote.RemoteConfigDataSourceImpl import org.sopt.havit.data.source.remote.SearchRemoteDataSourceImpl -import org.sopt.havit.data.source.remote.SystemMaintenanceDataSourceImpl import org.sopt.havit.data.source.remote.category.CategoryRemoteDataSourceImpl import org.sopt.havit.data.source.remote.contents.ContentsRemoteDataSourceImpl import org.sopt.havit.domain.repository.* @@ -58,6 +58,6 @@ object RepositoryModule { @Provides @Singleton fun provideSystemMaintenanceRepository( - systemMaintenanceDataSource: SystemMaintenanceDataSourceImpl, + systemMaintenanceDataSource: RemoteConfigDataSourceImpl, ): SystemMaintenanceRepository = SystemMaintenanceRepositoryImpl(systemMaintenanceDataSource) } diff --git a/app/src/main/java/org/sopt/havit/domain/repository/SystemMaintenanceRepository.kt b/app/src/main/java/org/sopt/havit/domain/repository/SystemMaintenanceRepository.kt index fc739d2d..7365544d 100644 --- a/app/src/main/java/org/sopt/havit/domain/repository/SystemMaintenanceRepository.kt +++ b/app/src/main/java/org/sopt/havit/domain/repository/SystemMaintenanceRepository.kt @@ -2,7 +2,7 @@ package org.sopt.havit.domain.repository interface SystemMaintenanceRepository { - fun isSystemMaintenance(): Boolean + suspend fun isSystemMaintenance(): Boolean - fun getSystemMaintenanceMessage(): String + suspend fun getSystemMaintenanceMessage(): String } \ No newline at end of file From f3fa669f82fff965ea22b8e99bc98662100219a9 Mon Sep 17 00:00:00 2001 From: kxxhyorim Date: Sat, 2 Dec 2023 20:34:14 +0900 Subject: [PATCH 06/11] [UI] Implement UI of System Maintenance Screen (#903) --- .../SystemMaintenanceActivity.kt | 9 ++-- .../SystemMaintenanceViewModel.kt | 24 ++++++++-- app/src/main/res/drawable/ic_notice.xml | 12 +++++ .../layout/activity_system_maintenance.xml | 45 +++++++++++++++++++ app/src/main/res/values/strings.xml | 1 + 5 files changed, 82 insertions(+), 9 deletions(-) create mode 100644 app/src/main/res/drawable/ic_notice.xml diff --git a/app/src/main/java/org/sopt/havit/ui/system_maintenance/SystemMaintenanceActivity.kt b/app/src/main/java/org/sopt/havit/ui/system_maintenance/SystemMaintenanceActivity.kt index b0fbe669..2b5b76e1 100644 --- a/app/src/main/java/org/sopt/havit/ui/system_maintenance/SystemMaintenanceActivity.kt +++ b/app/src/main/java/org/sopt/havit/ui/system_maintenance/SystemMaintenanceActivity.kt @@ -1,10 +1,8 @@ package org.sopt.havit.ui.system_maintenance import android.os.Bundle -import android.util.Log import androidx.activity.viewModels import dagger.hilt.android.AndroidEntryPoint -import org.sopt.havit.HavitFirebaseMessagingService.Companion.TAG import org.sopt.havit.R import org.sopt.havit.databinding.ActivitySystemMaintenanceBinding import org.sopt.havit.ui.base.BaseBindingActivity @@ -17,8 +15,9 @@ class SystemMaintenanceActivity : override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - Log.d(TAG, "onCreate: ${systemMaintenanceViewModel.isSystemMaintenance()}") - Log.d(TAG, "onCreate: ${systemMaintenanceViewModel.getSystemMaintenanceMessage()}") - } + binding.viewModel = systemMaintenanceViewModel + systemMaintenanceViewModel.fetchIsSystemMaintenance() + systemMaintenanceViewModel.fetchSystemMaintenanceMessage() + } } \ No newline at end of file diff --git a/app/src/main/java/org/sopt/havit/ui/system_maintenance/SystemMaintenanceViewModel.kt b/app/src/main/java/org/sopt/havit/ui/system_maintenance/SystemMaintenanceViewModel.kt index 01c566ac..80d0fbc3 100644 --- a/app/src/main/java/org/sopt/havit/ui/system_maintenance/SystemMaintenanceViewModel.kt +++ b/app/src/main/java/org/sopt/havit/ui/system_maintenance/SystemMaintenanceViewModel.kt @@ -1,7 +1,11 @@ package org.sopt.havit.ui.system_maintenance +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.launch import org.sopt.havit.domain.repository.SystemMaintenanceRepository import javax.inject.Inject @@ -10,11 +14,23 @@ class SystemMaintenanceViewModel @Inject constructor( private val systemMaintenanceRepository: SystemMaintenanceRepository, ) : ViewModel() { - fun isSystemMaintenance(): Boolean { - return systemMaintenanceRepository.isSystemMaintenance() + private val _isSystemMaintenance: MutableLiveData = MutableLiveData() + val isSystemMaintenance: LiveData = _isSystemMaintenance + + private var _systemMaintenanceMessage: MutableLiveData = MutableLiveData() + val systemMaintenanceMessage: LiveData = _systemMaintenanceMessage + + fun fetchIsSystemMaintenance() { + viewModelScope.launch { + _isSystemMaintenance.postValue(systemMaintenanceRepository.isSystemMaintenance()) + } } - fun getSystemMaintenanceMessage(): String { - return systemMaintenanceRepository.getSystemMaintenanceMessage() + fun fetchSystemMaintenanceMessage() { + viewModelScope.launch { + val message = systemMaintenanceRepository.getSystemMaintenanceMessage() + .replace("\\n", "\n") + _systemMaintenanceMessage.postValue(message) + } } } \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_notice.xml b/app/src/main/res/drawable/ic_notice.xml new file mode 100644 index 00000000..6f585fac --- /dev/null +++ b/app/src/main/res/drawable/ic_notice.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/layout/activity_system_maintenance.xml b/app/src/main/res/layout/activity_system_maintenance.xml index 267fc8e1..66ff91ee 100644 --- a/app/src/main/res/layout/activity_system_maintenance.xml +++ b/app/src/main/res/layout/activity_system_maintenance.xml @@ -1,9 +1,13 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 41951d83..a681f9ae 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -284,5 +284,6 @@ 닉네임에 띄어쓰기는 사용할 수 없습니다. 제목에 띄어쓰기만 사용할 수 없습니다. 닉네임에 띄어쓰기는 사용할 수 없습니다. + 시스템 점검 안내 From a8c7b0dea7cbc7538e814f170202c7dc4620c44c Mon Sep 17 00:00:00 2001 From: kxxhyorim Date: Sat, 9 Dec 2023 18:13:29 +0900 Subject: [PATCH 07/11] [FEAT] Redirect to SystemMaintenance Activity upon app entry (#903) --- app/src/main/AndroidManifest.xml | 3 +-- .../sopt/havit/ui/base/BaseBindingActivity.kt | 17 ++++++++++-- .../org/sopt/havit/ui/base/BaseViewModel.kt | 27 +++++++++++++++++++ .../org/sopt/havit/ui/share/ShareActivity.kt | 6 +++++ .../org/sopt/havit/ui/share/ShareViewModel.kt | 13 ++++----- .../org/sopt/havit/ui/sign/SignInViewModel.kt | 8 +++--- .../havit/ui/sign/SplashWithSignActivity.kt | 10 +++++-- .../java/org/sopt/havit/ui/web/WebActivity.kt | 16 ++++++++--- .../org/sopt/havit/ui/web/WebViewModel.kt | 9 ++++--- 9 files changed, 88 insertions(+), 21 deletions(-) create mode 100644 app/src/main/java/org/sopt/havit/ui/base/BaseViewModel.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 40176bc2..621eb38d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -28,10 +28,9 @@ android:usesCleartextTraffic="true" tools:ignore="LockedOrientationActivity"> - + android:exported="false" /> (@LayoutRes private val layoutRes: Int) : - AppCompatActivity() { + +abstract class BaseBindingActivity( + @LayoutRes private val layoutRes: Int, +) : AppCompatActivity() { lateinit var binding: T override fun onCreate(savedInstanceState: Bundle?) { @@ -16,5 +21,13 @@ abstract class BaseBindingActivity(@LayoutRes private val l binding.lifecycleOwner = this } + val systemMaintenanceObserver = Observer { isSystemMaintenance -> + if (isSystemMaintenance) startSystemMaintenanceActivity() + } + + private fun startSystemMaintenanceActivity() { + startActivity(Intent(this, SystemMaintenanceActivity::class.java)) + finish() + } } diff --git a/app/src/main/java/org/sopt/havit/ui/base/BaseViewModel.kt b/app/src/main/java/org/sopt/havit/ui/base/BaseViewModel.kt new file mode 100644 index 00000000..4015fc71 --- /dev/null +++ b/app/src/main/java/org/sopt/havit/ui/base/BaseViewModel.kt @@ -0,0 +1,27 @@ +package org.sopt.havit.ui.base + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.launch +import org.sopt.havit.domain.repository.SystemMaintenanceRepository +import javax.inject.Inject + +@HiltViewModel +open class BaseViewModel @Inject constructor( + private val systemMaintenanceRepository: SystemMaintenanceRepository, +) : ViewModel() { + + + private val _isSystemMaintenance: MutableLiveData = MutableLiveData() + val isSystemMaintenance: LiveData = _isSystemMaintenance + + + fun fetchIsSystemMaintenance() { + viewModelScope.launch { + _isSystemMaintenance.postValue(systemMaintenanceRepository.isSystemMaintenance()) + } + } +} diff --git a/app/src/main/java/org/sopt/havit/ui/share/ShareActivity.kt b/app/src/main/java/org/sopt/havit/ui/share/ShareActivity.kt index 52426326..3a396fa7 100644 --- a/app/src/main/java/org/sopt/havit/ui/share/ShareActivity.kt +++ b/app/src/main/java/org/sopt/havit/ui/share/ShareActivity.kt @@ -27,6 +27,8 @@ class ShareActivity : BaseBindingActivity(R.layout.activit override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + shareViewModel.fetchIsSystemMaintenance() + observeSystemUnderMaintenance() setScreenOrientation() initializeActivityResultLauncher() handleShareFlow() @@ -97,6 +99,10 @@ class ShareActivity : BaseBindingActivity(R.layout.activit } } + private fun observeSystemUnderMaintenance() { + shareViewModel.isSystemMaintenance.observe(this, systemMaintenanceObserver) + } + companion object { const val WHERE_SPLASH_COME_FROM = "WHERE_SPLASH_COME_FROM" const val ON_NETWORK_ERROR_DISMISS = "ON_NETWORK_ERROR_DISMISS" diff --git a/app/src/main/java/org/sopt/havit/ui/share/ShareViewModel.kt b/app/src/main/java/org/sopt/havit/ui/share/ShareViewModel.kt index 7267840f..21afe369 100644 --- a/app/src/main/java/org/sopt/havit/ui/share/ShareViewModel.kt +++ b/app/src/main/java/org/sopt/havit/ui/share/ShareViewModel.kt @@ -2,10 +2,8 @@ package org.sopt.havit.ui.share import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.jsoup.Jsoup import org.jsoup.nodes.Document @@ -17,6 +15,8 @@ import org.sopt.havit.data.remote.CreateContentsRequest import org.sopt.havit.domain.entity.CategoryWithSelected import org.sopt.havit.domain.model.NetworkStatus import org.sopt.havit.domain.repository.AuthRepository +import org.sopt.havit.domain.repository.SystemMaintenanceRepository +import org.sopt.havit.ui.base.BaseViewModel import org.sopt.havit.ui.share.notification.AfterTime import org.sopt.havit.util.CalenderUtil import org.sopt.havit.util.HavitAuthUtil @@ -29,8 +29,9 @@ import javax.inject.Inject class ShareViewModel @Inject constructor( private val authRepository: AuthRepository, private val categoryMapper: CategoryMapper, - private val havitApi: HavitApi -) : ViewModel() { + private val havitApi: HavitApi, + systemMaintenanceRepository: SystemMaintenanceRepository, +) : BaseViewModel(systemMaintenanceRepository) { /** token */ fun getAccessToken() = authRepository.getAccessToken() @@ -38,7 +39,7 @@ class ShareViewModel @Inject constructor( fun makeSignIn( internetError: () -> Unit, onUnAuthorized: () -> Unit, - onAuthorized: () -> Unit + onAuthorized: () -> Unit, ) { HavitAuthUtil.isLoginNow({ isInternetNotConnected -> if (isInternetNotConnected) internetError() @@ -202,7 +203,7 @@ class ShareViewModel @Inject constructor( } private suspend fun getOgData() { - viewModelScope.launch(Dispatchers.IO) { + viewModelScope.launch { kotlin.runCatching { Jsoup.connect(url.value).get() }.onSuccess { diff --git a/app/src/main/java/org/sopt/havit/ui/sign/SignInViewModel.kt b/app/src/main/java/org/sopt/havit/ui/sign/SignInViewModel.kt index 9d3e6bc2..fa4bbab8 100644 --- a/app/src/main/java/org/sopt/havit/ui/sign/SignInViewModel.kt +++ b/app/src/main/java/org/sopt/havit/ui/sign/SignInViewModel.kt @@ -3,7 +3,6 @@ package org.sopt.havit.ui.sign import android.util.Log import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.kakao.sdk.auth.model.OAuthToken import dagger.hilt.android.lifecycle.HiltViewModel @@ -11,14 +10,17 @@ import kotlinx.coroutines.launch import org.sopt.havit.data.remote.SignInResponse import org.sopt.havit.domain.entity.NetworkState import org.sopt.havit.domain.repository.AuthRepository +import org.sopt.havit.domain.repository.SystemMaintenanceRepository +import org.sopt.havit.ui.base.BaseViewModel import org.sopt.havit.util.Event import retrofit2.HttpException import javax.inject.Inject @HiltViewModel class SignInViewModel @Inject constructor( - private val authRepository: AuthRepository -) : ViewModel() { + private val authRepository: AuthRepository, + systemMaintenanceRepository: SystemMaintenanceRepository, +) : BaseViewModel(systemMaintenanceRepository) { companion object { const val SPLASH_FROM_SHARE = true diff --git a/app/src/main/java/org/sopt/havit/ui/sign/SplashWithSignActivity.kt b/app/src/main/java/org/sopt/havit/ui/sign/SplashWithSignActivity.kt index 14c12a95..41a84982 100644 --- a/app/src/main/java/org/sopt/havit/ui/sign/SplashWithSignActivity.kt +++ b/app/src/main/java/org/sopt/havit/ui/sign/SplashWithSignActivity.kt @@ -69,12 +69,14 @@ class SplashWithSignActivity : override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContentView(binding.root) this.onBackPressedDispatcher.addCallback(this, callback) requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT binding.main = signInViewModel + + signInViewModel.fetchIsSystemMaintenance() + observeSystemUnderMaintenance() initFcmToken() initSuccessKakaoLoginObserver() initWhereSplashComesFrom() @@ -84,6 +86,10 @@ class SplashWithSignActivity : isAlreadyUserObserver() } + private fun observeSystemUnderMaintenance() { + signInViewModel.isSystemMaintenance.observe(this, systemMaintenanceObserver) + } + private fun initFcmToken() { signInViewModel.initFcmToken() } @@ -201,7 +207,7 @@ class SplashWithSignActivity : override fun onRequestPermissionsResult( requestCode: Int, permissions: Array, - grantResults: IntArray + grantResults: IntArray, ) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) when (requestCode) { diff --git a/app/src/main/java/org/sopt/havit/ui/web/WebActivity.kt b/app/src/main/java/org/sopt/havit/ui/web/WebActivity.kt index d14f0a3f..b293f69c 100644 --- a/app/src/main/java/org/sopt/havit/ui/web/WebActivity.kt +++ b/app/src/main/java/org/sopt/havit/ui/web/WebActivity.kt @@ -5,7 +5,11 @@ import android.os.Bundle import android.os.SystemClock import android.view.View.GONE import android.view.animation.AnimationUtils -import android.webkit.* +import android.webkit.URLUtil +import android.webkit.WebChromeClient +import android.webkit.WebResourceRequest +import android.webkit.WebView +import android.webkit.WebViewClient import android.widget.Toast import androidx.activity.OnBackPressedCallback import androidx.activity.viewModels @@ -37,12 +41,14 @@ class WebActivity : BaseBindingActivity(R.layout.activity_we override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContentView(binding.root) this.onBackPressedDispatcher.addCallback(this, callback) binding.vm = webViewModel startTime = SystemClock.elapsedRealtime().toInt() + + webViewModel.fetchIsSystemMaintenance() + observeSystemUnderMaintenance() initIsHavit() initHavitSeen() setUrlCheck() @@ -77,7 +83,7 @@ class WebActivity : BaseBindingActivity(R.layout.activity_we webViewClient = object : WebViewClient() { override fun shouldOverrideUrlLoading( view: WebView?, - request: WebResourceRequest? + request: WebResourceRequest?, ): Boolean { if (request?.url.toString().startsWith("towneers:")) { startActivity( @@ -167,4 +173,8 @@ class WebActivity : BaseBindingActivity(R.layout.activity_we setWebViewDurationTimeLogging() GoogleAnalyticsUtil.logClickEvent(CLICK_GO_BACK) } + + private fun observeSystemUnderMaintenance() { + webViewModel.isSystemMaintenance.observe(this, systemMaintenanceObserver) + } } diff --git a/app/src/main/java/org/sopt/havit/ui/web/WebViewModel.kt b/app/src/main/java/org/sopt/havit/ui/web/WebViewModel.kt index 9afbc868..9869f83c 100644 --- a/app/src/main/java/org/sopt/havit/ui/web/WebViewModel.kt +++ b/app/src/main/java/org/sopt/havit/ui/web/WebViewModel.kt @@ -2,18 +2,21 @@ package org.sopt.havit.ui.web import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch import org.sopt.havit.domain.entity.NetworkState import org.sopt.havit.domain.repository.ContentsRepository +import org.sopt.havit.domain.repository.SystemMaintenanceRepository +import org.sopt.havit.ui.base.BaseViewModel import org.sopt.havit.util.Event import javax.inject.Inject @HiltViewModel -class WebViewModel @Inject constructor(private val contentsRepository: ContentsRepository) : - ViewModel() { +class WebViewModel @Inject constructor( + private val contentsRepository: ContentsRepository, + systemMaintenanceRepository: SystemMaintenanceRepository, +) : BaseViewModel(systemMaintenanceRepository) { private var _isHavit = MutableLiveData>() val isHavit: LiveData> = _isHavit From 2fa92c3334a797f0cc2403ce3fe3d3617da18d3d Mon Sep 17 00:00:00 2001 From: kxxhyorim Date: Sun, 10 Dec 2023 00:22:13 +0900 Subject: [PATCH 08/11] [FEAT] Set Default Info Message (#903) --- .../SystemMaintenanceRepositoryImpl.kt | 18 +++++++++++++----- .../remote/RemoteConfigDataSourceImpl.kt | 2 +- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/org/sopt/havit/data/repository/SystemMaintenanceRepositoryImpl.kt b/app/src/main/java/org/sopt/havit/data/repository/SystemMaintenanceRepositoryImpl.kt index dc1e7b56..b5755eae 100644 --- a/app/src/main/java/org/sopt/havit/data/repository/SystemMaintenanceRepositoryImpl.kt +++ b/app/src/main/java/org/sopt/havit/data/repository/SystemMaintenanceRepositoryImpl.kt @@ -9,15 +9,23 @@ class SystemMaintenanceRepositoryImpl @Inject constructor( ) : SystemMaintenanceRepository { override suspend fun isSystemMaintenance(): Boolean { val isSystemUnderMaintenance = systemMaintenanceRemoteDataSource.fetchRemoteConfig( - "isSystemUnderMaintenance", Boolean::class.java + IS_SYSTEM_UNDER_MAINTENANCE, + Boolean::class.java ) as? Boolean return isSystemUnderMaintenance ?: false } override suspend fun getSystemMaintenanceMessage(): String { - val systemMaintenanceMessage = systemMaintenanceRemoteDataSource.fetchRemoteConfig( - "systemMaintenanceMessage", String::class.java - ) as? String - return systemMaintenanceMessage ?: "" + val message = systemMaintenanceRemoteDataSource.fetchRemoteConfig( + SYSTEM_MAINTENANCE_MESSAGE, + String::class.java + ).toString() + return message.ifEmpty { DEFAULT_MESSAGE } + } + + companion object { + private const val IS_SYSTEM_UNDER_MAINTENANCE = "isSystemUnderMaintenance" + private const val SYSTEM_MAINTENANCE_MESSAGE = "systemMaintenanceMessage" + private const val DEFAULT_MESSAGE = "현재 시스템 점검중입니다.\\n불편을 끼쳐드려 죄송합니다." } } \ No newline at end of file diff --git a/app/src/main/java/org/sopt/havit/data/source/remote/RemoteConfigDataSourceImpl.kt b/app/src/main/java/org/sopt/havit/data/source/remote/RemoteConfigDataSourceImpl.kt index acf3b9b5..05149fac 100644 --- a/app/src/main/java/org/sopt/havit/data/source/remote/RemoteConfigDataSourceImpl.kt +++ b/app/src/main/java/org/sopt/havit/data/source/remote/RemoteConfigDataSourceImpl.kt @@ -11,7 +11,7 @@ import javax.inject.Inject class RemoteConfigDataSourceImpl @Inject constructor() : RemoteConfigDataSource { private val remoteConfig: FirebaseRemoteConfig = Firebase.remoteConfig.apply { - setConfigSettingsAsync(remoteConfigSettings { minimumFetchIntervalInSeconds = 180 }) + setConfigSettingsAsync(remoteConfigSettings { minimumFetchIntervalInSeconds = 60 }) } override suspend fun fetchRemoteConfig(configKey: String, valueType: Type): Any { From 7ac570ab110fa10f9adb8085b7904f35bd78391a Mon Sep 17 00:00:00 2001 From: kxxhyorim Date: Wed, 6 Mar 2024 18:02:01 +0900 Subject: [PATCH 09/11] =?UTF-8?q?[FEAT]=20=EC=BD=98=ED=85=90=EC=B8=A0=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5=20=EC=8B=9C=20=EC=9C=A0=ED=9A=A8=ED=95=9C=20?= =?UTF-8?q?URL=EC=9D=B4=20=EC=95=84=EB=8B=90=20=EA=B2=BD=EC=9A=B0=20Toast?= =?UTF-8?q?=20=EB=9D=84=EC=9A=B0=EA=B3=A0=20=EC=A2=85=EB=A3=8C=20(#876)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/sopt/havit/ui/share/ShareActivity.kt | 24 +++++++++++++++++-- .../org/sopt/havit/ui/share/ShareViewModel.kt | 5 ++-- .../java/org/sopt/havit/util/ToastUtil.kt | 9 ++++++- app/src/main/res/values/strings.xml | 1 + 4 files changed, 33 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/org/sopt/havit/ui/share/ShareActivity.kt b/app/src/main/java/org/sopt/havit/ui/share/ShareActivity.kt index 3a396fa7..71cf1647 100644 --- a/app/src/main/java/org/sopt/havit/ui/share/ShareActivity.kt +++ b/app/src/main/java/org/sopt/havit/ui/share/ShareActivity.kt @@ -16,6 +16,8 @@ import org.sopt.havit.databinding.ActivityShareBinding import org.sopt.havit.ui.base.BaseBindingActivity import org.sopt.havit.ui.sign.SignInViewModel.Companion.SPLASH_FROM_SHARE import org.sopt.havit.ui.sign.SplashWithSignActivity +import org.sopt.havit.util.INVALID_URL_TYPE +import org.sopt.havit.util.ToastUtil import java.io.Serializable @AndroidEntryPoint @@ -89,8 +91,26 @@ class ShareActivity : BaseBindingActivity(R.layout.activit } private fun extractAndSetUrl() { - val url = intent?.getStringExtra(Intent.EXTRA_TEXT) ?: intent?.getStringExtra("url") - shareViewModel.setUrl(url.toString()) + val url = getUrlFromExtra() + try { + checkUrlNotNull(url) + shareViewModel.setUrl(url.toString()) + } catch (e: IllegalStateException) { + onUrlInvalid() + } + } + + private fun getUrlFromExtra(): String? { + return intent?.getStringExtra(Intent.EXTRA_TEXT) ?: intent?.getStringExtra("url") + } + + private fun checkUrlNotNull(url: String?) { + requireNotNull(url) { throw IllegalStateException() } + } + + private fun onUrlInvalid() { + ToastUtil(this).makeToast(INVALID_URL_TYPE) + finish() } override fun setRequestedOrientation(requestedOrientation: Int) { diff --git a/app/src/main/java/org/sopt/havit/ui/share/ShareViewModel.kt b/app/src/main/java/org/sopt/havit/ui/share/ShareViewModel.kt index 21afe369..d2fd73aa 100644 --- a/app/src/main/java/org/sopt/havit/ui/share/ShareViewModel.kt +++ b/app/src/main/java/org/sopt/havit/ui/share/ShareViewModel.kt @@ -114,7 +114,7 @@ class ShareViewModel @Inject constructor( _url.value = extractUrl(url) } - private fun extractUrl(content: String?): String { + private fun extractUrl(content: String): String { val urlPattern = Pattern.compile( "(?:^|\\W)((ht|f)tp(s?)://|www\\.)" + "(([\\w\\-]+\\.)+?([\\w\\-.~]+/?)*" @@ -125,9 +125,8 @@ class ShareViewModel @Inject constructor( while (matcher.find()) { val matchStart = matcher.start(1) val matchEnd = matcher.end() - return content?.substring(matchStart, matchEnd) ?: "" + return content.substring(matchStart, matchEnd) } - if (BuildConfig.IS_DEV) return "https://www.havit.app/" throw IllegalStateException() } diff --git a/app/src/main/java/org/sopt/havit/util/ToastUtil.kt b/app/src/main/java/org/sopt/havit/util/ToastUtil.kt index 2ea4ed4e..9e411d1d 100644 --- a/app/src/main/java/org/sopt/havit/util/ToastUtil.kt +++ b/app/src/main/java/org/sopt/havit/util/ToastUtil.kt @@ -54,6 +54,7 @@ class ToastUtil @Inject constructor(@ApplicationContext private val context: Con val textView: TextView = view.findViewById(R.id.tv_toast) textView.text = categoryName } + else -> { val textView: TextView = view.findViewById(R.id.tv_toast) textView.text = getTitle(context) @@ -78,7 +79,7 @@ enum class ToastCase( @StringRes val text: Int, val viewType: Int, val gravity: Int = Gravity.BOTTOM, - val yOffsetDp: Int = MARGIN_NORMAL + val yOffsetDp: Int = MARGIN_NORMAL, ) { CONTENT_DELETE( R.layout.toast_text, @@ -158,6 +159,11 @@ enum class ToastCase( R.layout.toast_text, R.string.request_delete_notification, REQUEST_DELETE_NOTIFICATION_TYPE + ), + INVALID_URL( + R.layout.toast_text, + R.string.invalid_url, + INVALID_URL_TYPE ); companion object { @@ -182,6 +188,7 @@ const val CATEGORY_MODIFY_COMPLETE_TYPE = 11 const val MODIFY_TITLE_COMPLETE_TYPE = 13 const val DELETE_NOTIFICATION_COMPLETE_TYPE = 14 const val REQUEST_DELETE_NOTIFICATION_TYPE = 15 +const val INVALID_URL_TYPE = 16 const val MARGIN_CONTENT_ADDED = 30 const val MARGIN_HAVIT_COMPLETE = 40 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a681f9ae..5a5cdf76 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -285,5 +285,6 @@ 제목에 띄어쓰기만 사용할 수 없습니다. 닉네임에 띄어쓰기는 사용할 수 없습니다. 시스템 점검 안내 + 유효하지 않은 URL입니다. From 8f23efe5d8a3619839caf39cb274e0a7e4a3e8c4 Mon Sep 17 00:00:00 2001 From: kxxhyorim Date: Wed, 6 Mar 2024 21:05:50 +0900 Subject: [PATCH 10/11] =?UTF-8?q?[FIX]=20=EC=BD=98=ED=85=90=EC=B8=A0?= =?UTF-8?q?=EC=9D=98=20meta=20data=EA=B0=80=20=EC=97=86=EB=8A=94=20?= =?UTF-8?q?=EA=B2=BD=EC=9A=B0=20timeout=EC=9D=84=205=EC=B4=88=EB=A1=9C=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=EC=9D=B4=ED=9B=84=20'=EC=A0=9C=EB=AA=A9?= =?UTF-8?q?=EC=97=86=EB=8A=94=20=EC=BD=98=ED=85=90=EC=B8=A0'=EB=A1=9C=20?= =?UTF-8?q?=ED=91=9C=EC=8B=9C=20(#906)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/sopt/havit/ui/share/ShareViewModel.kt | 41 ++++++++++--------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/org/sopt/havit/ui/share/ShareViewModel.kt b/app/src/main/java/org/sopt/havit/ui/share/ShareViewModel.kt index d2fd73aa..bd3f37fa 100644 --- a/app/src/main/java/org/sopt/havit/ui/share/ShareViewModel.kt +++ b/app/src/main/java/org/sopt/havit/ui/share/ShareViewModel.kt @@ -4,10 +4,10 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.jsoup.Jsoup import org.jsoup.nodes.Document -import org.sopt.havit.BuildConfig import org.sopt.havit.data.api.HavitApi import org.sopt.havit.data.mapper.CategoryMapper import org.sopt.havit.data.remote.ContentsSummeryData @@ -144,7 +144,7 @@ class ShareViewModel @Inject constructor( get() = _tempIndex private var _finalIndex = MutableLiveData() - val finalIndex: LiveData + private val finalIndex: LiveData get() = _finalIndex fun syncTempDataWithFinalData() { @@ -196,15 +196,10 @@ class ShareViewModel @Inject constructor( } } - private fun setDefaultIfTitleDataNotExist() { - if (ogData.value?.ogTitle.isNullOrBlank()) - _ogData.value?.ogTitle = NO_TITLE_CONTENTS - } - private suspend fun getOgData() { - viewModelScope.launch { + viewModelScope.launch(Dispatchers.IO) { kotlin.runCatching { - Jsoup.connect(url.value).get() + Jsoup.connect(url.value).timeout(5000).get() }.onSuccess { val contentsSummeryData = getDataByOgTags(it) _ogData.postValue(contentsSummeryData) @@ -214,19 +209,25 @@ class ShareViewModel @Inject constructor( }.join() } - private fun getDataByOgTags(it: Document): ContentsSummeryData { - val doc = it.select("meta[property^=og:]") - return ContentsSummeryData(ogUrl = url.value.toString()).apply { - doc.forEachIndexed { index, _ -> - val tag = doc[index] - when (doc[index].attr("property")) { - "og:image" -> ogImage = tag.attr("content") - "og:description" -> ogDescription = tag.attr("content") - "og:title" -> ogTitle = tag.attr("content") - } + private fun setDefaultIfTitleDataNotExist() { + val ogData = ogData.value + if (ogData?.ogTitle.isNullOrBlank()) + ogData?.ogTitle = NO_TITLE_CONTENTS + } + + private fun getDataByOgTags(document: Document): ContentsSummeryData { + val ogTags = document.select("meta[property^=og:]") + val summaryData = ContentsSummeryData(ogUrl = url.value.toString()) + ogTags.forEach { tag -> + val content = tag.attr("content") + when (tag.attr("property")) { + "og:image" -> summaryData.ogImage = content + "og:description" -> summaryData.ogDescription = content + "og:title" -> summaryData.ogTitle = content } - if (this.ogTitle == "") this.ogTitle = it.title() } + if (summaryData.ogTitle.isEmpty()) summaryData.ogTitle = document.title() + return summaryData } private val _saveContentsViewState = MutableLiveData(NetworkStatus.Init()) From 3f4e6d9dfc898b7a9b7be6f6729c293bd96b0dc1 Mon Sep 17 00:00:00 2001 From: kxxhyorim Date: Wed, 6 Mar 2024 21:18:23 +0900 Subject: [PATCH 11/11] [CHORE] version code, name v1.0.10 -> v1.0.11 (#908) --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 60ce2a7c..0230dbf1 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -20,8 +20,8 @@ android { applicationId "org.sopt.havit" minSdk 23 targetSdk 33 - versionCode 110 - versionName "1.0.10" + versionCode 111 + versionName "1.0.11" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" buildConfigField("String", "HAVIT_BASE_URL_DEV", properties["HAVIT_BASE_URL_DEV"]) buildConfigField("String", "HAVIT_BASE_URL_PROD", properties["HAVIT_BASE_URL_PROD"])