diff --git a/.gitignore b/.gitignore
index 98f4581a5..6254be1c5 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
diff --git a/app/build.gradle b/app/build.gradle
index e22dc8981..0230dbf1f 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"])
@@ -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
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 692aaca8a..621eb38da 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,9 +1,10 @@
-
+
@@ -24,7 +25,13 @@
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 000000000..b5755eae2
--- /dev/null
+++ b/app/src/main/java/org/sopt/havit/data/repository/SystemMaintenanceRepositoryImpl.kt
@@ -0,0 +1,31 @@
+package org.sopt.havit.data.repository
+
+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: RemoteConfigDataSource,
+) : SystemMaintenanceRepository {
+ override suspend fun isSystemMaintenance(): Boolean {
+ val isSystemUnderMaintenance = systemMaintenanceRemoteDataSource.fetchRemoteConfig(
+ IS_SYSTEM_UNDER_MAINTENANCE,
+ Boolean::class.java
+ ) as? Boolean
+ return isSystemUnderMaintenance ?: false
+ }
+
+ override suspend fun getSystemMaintenanceMessage(): String {
+ 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/RemoteConfigDataSource.kt b/app/src/main/java/org/sopt/havit/data/source/remote/RemoteConfigDataSource.kt
new file mode 100644
index 000000000..4424d1135
--- /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 000000000..05149fac1
--- /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 = 60 })
+ }
+
+ 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/di/DataSourceModule.kt b/app/src/main/java/org/sopt/havit/di/DataSourceModule.kt
index 553c7478a..4131481c7 100644
--- a/app/src/main/java/org/sopt/havit/di/DataSourceModule.kt
+++ b/app/src/main/java/org/sopt/havit/di/DataSourceModule.kt
@@ -10,6 +10,8 @@ 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.category.CategoryRemoteDataSource
@@ -46,4 +48,9 @@ object DataSourceModule {
@Singleton
fun provideCategoryRemoteDataSource(api: HavitApi): CategoryRemoteDataSource =
CategoryRemoteDataSourceImpl(api)
+
+ @Provides
+ @Singleton
+ 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 ac1b1284d..a2fa49790 100644
--- a/app/src/main/java/org/sopt/havit/di/RepositoryModule.kt
+++ b/app/src/main/java/org/sopt/havit/di/RepositoryModule.kt
@@ -9,6 +9,7 @@ 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.category.CategoryRemoteDataSourceImpl
import org.sopt.havit.data.source.remote.contents.ContentsRemoteDataSourceImpl
@@ -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: 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
new file mode 100644
index 000000000..7365544d4
--- /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 {
+
+ suspend fun isSystemMaintenance(): Boolean
+
+ suspend fun getSystemMaintenanceMessage(): String
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/sopt/havit/ui/base/BaseBindingActivity.kt b/app/src/main/java/org/sopt/havit/ui/base/BaseBindingActivity.kt
index 64fae682d..ff289213b 100644
--- a/app/src/main/java/org/sopt/havit/ui/base/BaseBindingActivity.kt
+++ b/app/src/main/java/org/sopt/havit/ui/base/BaseBindingActivity.kt
@@ -1,13 +1,18 @@
package org.sopt.havit.ui.base
+import android.content.Intent
import android.os.Bundle
import androidx.annotation.LayoutRes
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import androidx.databinding.ViewDataBinding
+import androidx.lifecycle.Observer
+import org.sopt.havit.ui.system_maintenance.SystemMaintenanceActivity
-abstract class BaseBindingActivity(@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 000000000..4015fc714
--- /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 2b2ca9571..71cf16476 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
@@ -15,81 +16,101 @@ 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
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)
+
+ shareViewModel.fetchIsSystemMaintenance()
+ observeSystemUnderMaintenance()
+ 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 = 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) {
@@ -97,7 +118,11 @@ class ShareActivity : BaseBindingActivity(R.layout.activit
super.setRequestedOrientation(requestedOrientation)
}
}
-
+
+ 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 7267840f6..bd3f37fa1 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,14 +2,12 @@ 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
-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
@@ -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()
@@ -113,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\\-.~]+/?)*"
@@ -124,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()
}
@@ -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(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())
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 9d3e6bc2e..fa4bbab8e 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 14c12a951..41a84982b 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/system_maintenance/SystemMaintenanceActivity.kt b/app/src/main/java/org/sopt/havit/ui/system_maintenance/SystemMaintenanceActivity.kt
new file mode 100644
index 000000000..2b5b76e14
--- /dev/null
+++ b/app/src/main/java/org/sopt/havit/ui/system_maintenance/SystemMaintenanceActivity.kt
@@ -0,0 +1,23 @@
+package org.sopt.havit.ui.system_maintenance
+
+import android.os.Bundle
+import androidx.activity.viewModels
+import dagger.hilt.android.AndroidEntryPoint
+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)
+
+ 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
new file mode 100644
index 000000000..80d0fbc3d
--- /dev/null
+++ b/app/src/main/java/org/sopt/havit/ui/system_maintenance/SystemMaintenanceViewModel.kt
@@ -0,0 +1,36 @@
+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
+
+@HiltViewModel
+class SystemMaintenanceViewModel @Inject constructor(
+ private val systemMaintenanceRepository: SystemMaintenanceRepository,
+) : ViewModel() {
+
+ 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 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/java/org/sopt/havit/ui/web/WebActivity.kt b/app/src/main/java/org/sopt/havit/ui/web/WebActivity.kt
index d14f0a3f6..b293f69c5 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 9afbc8689..9869f83cd 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
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 2ea4ed4e7..9e411d1d7 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/drawable/ic_notice.xml b/app/src/main/res/drawable/ic_notice.xml
new file mode 100644
index 000000000..6f585faca
--- /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
new file mode 100644
index 000000000..66ff91ee3
--- /dev/null
+++ b/app/src/main/res/layout/activity_system_maintenance.xml
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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 41951d836..5a5cdf766 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -284,5 +284,7 @@
닉네임에 띄어쓰기는 사용할 수 없습니다.
제목에 띄어쓰기만 사용할 수 없습니다.
닉네임에 띄어쓰기는 사용할 수 없습니다.
+ 시스템 점검 안내
+ 유효하지 않은 URL입니다.