diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml
new file mode 100644
index 00000000..7047cf08
--- /dev/null
+++ b/.idea/deploymentTargetDropDown.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
index d2bd69d9..a27ad946 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -15,8 +15,8 @@ android {
applicationId "com.teamsparker.android"
minSdk 26
targetSdk 31
- versionCode 4
- versionName "1.0.2"
+ versionCode 5
+ versionName "1.1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
diff --git a/app/src/main/java/com/teamsparker/android/data/local/datasource/LocalPreferencesDataSourceImpl.kt b/app/src/main/java/com/teamsparker/android/data/local/datasource/LocalPreferencesDataSourceImpl.kt
index 080aa41e..31bb5a77 100644
--- a/app/src/main/java/com/teamsparker/android/data/local/datasource/LocalPreferencesDataSourceImpl.kt
+++ b/app/src/main/java/com/teamsparker/android/data/local/datasource/LocalPreferencesDataSourceImpl.kt
@@ -105,10 +105,10 @@ class LocalPreferencesDataSourceImpl @Inject constructor(
}
companion object {
- private const val ACCESS_TOKEN = "ACCESS_TOKEN"
- private const val USER_KAKAO_USER_ID = "USER_KAKAO_USER_ID"
- private const val USER_NICKNAME = "USER_NAME"
- private const val ALARM_LOCAL_SAVED = "ALARM_LOCAL_SAVED"
+ const val ACCESS_TOKEN = "ACCESS_TOKEN"
+ const val USER_KAKAO_USER_ID = "USER_KAKAO_USER_ID"
+ const val USER_NICKNAME = "USER_NAME"
+ const val ALARM_LOCAL_SAVED = "ALARM_LOCAL_SAVED"
const val DEFAULT_STRING_VALUE = ""
}
}
diff --git a/app/src/main/java/com/teamsparker/android/data/remote/RetrofitBuilder.kt b/app/src/main/java/com/teamsparker/android/data/remote/RetrofitBuilder.kt
index 410e778e..b41134b5 100644
--- a/app/src/main/java/com/teamsparker/android/data/remote/RetrofitBuilder.kt
+++ b/app/src/main/java/com/teamsparker/android/data/remote/RetrofitBuilder.kt
@@ -1,5 +1,6 @@
package com.teamsparker.android.data.remote
+import com.teamsparker.android.data.remote.calladapter.CustomCallAdapterFactory
import com.teamsparker.android.data.remote.service.*
import okhttp3.Interceptor
import okhttp3.OkHttpClient
@@ -28,19 +29,20 @@ object RetrofitBuilder {
.addInterceptor(headerInterceptor)
.build()
-
private val retrofit: Retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.client(okHttpClient)
+ .addCallAdapterFactory(CustomCallAdapterFactory())
.addConverterFactory(GsonConverterFactory.create())
.build()
// 이 밑에다가 이런식으로 서비스 객체 생성하기
// val sampleService: SampleService = retrofit.create(SampleService::class.java)
- val habitService : HabitService = retrofit.create(HabitService::class.java)
+ val habitService: HabitService = retrofit.create(HabitService::class.java)
val storageService: StorageService = retrofit.create(StorageService::class.java)
- val photoCollectionService: PhotoCollectionService = retrofit.create(PhotoCollectionService::class.java)
+ val photoCollectionService: PhotoCollectionService =
+ retrofit.create(PhotoCollectionService::class.java)
val photoMainService: PhotoMainService = retrofit.create(PhotoMainService::class.java)
val setStatusService: SetStatusService = retrofit.create(SetStatusService::class.java)
val sendSparkService: SendSparkService = retrofit.create(SendSparkService::class.java)
diff --git a/app/src/main/java/com/teamsparker/android/data/remote/calladapter/CustomCall.kt b/app/src/main/java/com/teamsparker/android/data/remote/calladapter/CustomCall.kt
new file mode 100644
index 00000000..75b66486
--- /dev/null
+++ b/app/src/main/java/com/teamsparker/android/data/remote/calladapter/CustomCall.kt
@@ -0,0 +1,69 @@
+package com.teamsparker.android.data.remote.calladapter
+
+import okhttp3.Request
+import okio.Timeout
+import retrofit2.Call
+import retrofit2.Callback
+import retrofit2.Response
+import java.io.IOException
+
+class CustomCall(private val call: Call) : Call> {
+
+ override fun enqueue(callback: Callback>) {
+ call.enqueue(object : Callback {
+ override fun onResponse(call: Call, response: Response) {
+ val body = response.body()
+ val code = response.code()
+ val error = response.errorBody()?.string()
+
+ if (response.isSuccessful) {
+ if (body != null) {
+ callback.onResponse(
+ this@CustomCall,
+ Response.success(NetworkState.Success(body))
+ )
+ } else {
+ callback.onResponse(
+ this@CustomCall,
+ Response.success(
+ NetworkState.UnknownError(
+ IllegalStateException("body값이 null로 넘어옴"),
+ "body값이 null로 넘어옴"
+ )
+ )
+ )
+ }
+ } else {
+ callback.onResponse(
+ this@CustomCall,
+ Response.success(NetworkState.Failure(code, error))
+ )
+ }
+ }
+
+ override fun onFailure(call: Call, t: Throwable) {
+ val errorResponse = when (t) {
+ is IOException -> NetworkState.NetworkError(t)
+ else -> NetworkState.UnknownError(t, "onFailure에 진입,IoException 이외의 에러")
+ }
+ callback.onResponse(this@CustomCall, Response.success(errorResponse))
+ }
+ })
+ }
+
+ override fun clone(): Call> = CustomCall(call.clone())
+
+ override fun execute(): Response> {
+ throw UnsupportedOperationException("커스텀한 callAdapter에서는 execute를 사용하지 않습니다 ")
+ }
+
+ override fun isExecuted(): Boolean = call.isExecuted
+
+ override fun cancel() = call.cancel()
+
+ override fun isCanceled(): Boolean = call.isCanceled
+
+ override fun request(): Request = call.request()
+
+ override fun timeout(): Timeout = call.timeout()
+}
diff --git a/app/src/main/java/com/teamsparker/android/data/remote/calladapter/CustomCallAdapter.kt b/app/src/main/java/com/teamsparker/android/data/remote/calladapter/CustomCallAdapter.kt
new file mode 100644
index 00000000..171e9a27
--- /dev/null
+++ b/app/src/main/java/com/teamsparker/android/data/remote/calladapter/CustomCallAdapter.kt
@@ -0,0 +1,12 @@
+package com.teamsparker.android.data.remote.calladapter
+
+import retrofit2.Call
+import retrofit2.CallAdapter
+import java.lang.reflect.Type
+
+class CustomCallAdapter(private val responseType: Type) :
+ CallAdapter>> {
+ override fun responseType(): Type = responseType
+
+ override fun adapt(call: Call): Call> = CustomCall(call)
+}
diff --git a/app/src/main/java/com/teamsparker/android/data/remote/calladapter/CustomCallAdapterFactory.kt b/app/src/main/java/com/teamsparker/android/data/remote/calladapter/CustomCallAdapterFactory.kt
new file mode 100644
index 00000000..f9127f75
--- /dev/null
+++ b/app/src/main/java/com/teamsparker/android/data/remote/calladapter/CustomCallAdapterFactory.kt
@@ -0,0 +1,38 @@
+package com.teamsparker.android.data.remote.calladapter
+
+import retrofit2.Call
+import retrofit2.CallAdapter
+import retrofit2.Retrofit
+import java.lang.reflect.ParameterizedType
+import java.lang.reflect.Type
+
+class CustomCallAdapterFactory : CallAdapter.Factory() {
+
+ override fun get(
+ returnType: Type,
+ annotations: Array,
+ retrofit: Retrofit
+ ): CallAdapter<*, *>? {
+ if (Call::class.java != getRawType(returnType)) {
+ return null
+ }
+
+ check(returnType is ParameterizedType) {
+ "return type must be parameterized as Call> or Call>"
+ }
+
+ val responseType = getParameterUpperBound(0, returnType)
+
+ if (getRawType(responseType) != NetworkState::class.java) {
+ return null
+ }
+
+ check(responseType is ParameterizedType) {
+ "Response must be parameterized as NetworkState or NetworkState"
+ }
+
+ val bodyType = getParameterUpperBound(0, responseType)
+
+ return CustomCallAdapter(bodyType)
+ }
+}
diff --git a/app/src/main/java/com/teamsparker/android/data/remote/calladapter/NetworkState.kt b/app/src/main/java/com/teamsparker/android/data/remote/calladapter/NetworkState.kt
new file mode 100644
index 00000000..ab91c64b
--- /dev/null
+++ b/app/src/main/java/com/teamsparker/android/data/remote/calladapter/NetworkState.kt
@@ -0,0 +1,18 @@
+package com.teamsparker.android.data.remote.calladapter
+
+import java.io.IOException
+
+sealed class NetworkState {
+
+ // 200대 응답 성공한것
+ data class Success(val body: T) : NetworkState()
+
+ // isSuccessful 이 false인 경우(200~300대 응답이 아닌경우)
+ data class Failure(val code: Int, val error: String?) : NetworkState()
+
+ // onFailure로 넘어간경우(네트워크 오류,timeout 같은거)
+ data class NetworkError(val error: IOException) : NetworkState()
+
+ // 예상 못한에러(기타 모든 에러처리)
+ data class UnknownError(val t: Throwable?, val errorState: String) : NetworkState()
+}
diff --git a/app/src/main/java/com/teamsparker/android/data/remote/calladapter/RetrofitFaliureStateException.kt b/app/src/main/java/com/teamsparker/android/data/remote/calladapter/RetrofitFaliureStateException.kt
new file mode 100644
index 00000000..1837f886
--- /dev/null
+++ b/app/src/main/java/com/teamsparker/android/data/remote/calladapter/RetrofitFaliureStateException.kt
@@ -0,0 +1,3 @@
+package com.teamsparker.android.data.remote.calladapter
+
+class RetrofitFailureStateException(error: String?, val code: Int) : Exception(error)
diff --git a/app/src/main/java/com/teamsparker/android/data/remote/datasource/HabitRoomTImeLineDataSourceImpl.kt b/app/src/main/java/com/teamsparker/android/data/remote/datasource/HabitRoomTImeLineDataSourceImpl.kt
new file mode 100644
index 00000000..e27f7819
--- /dev/null
+++ b/app/src/main/java/com/teamsparker/android/data/remote/datasource/HabitRoomTImeLineDataSourceImpl.kt
@@ -0,0 +1,14 @@
+package com.teamsparker.android.data.remote.datasource
+
+import com.teamsparker.android.data.remote.calladapter.NetworkState
+import com.teamsparker.android.data.remote.entity.response.BaseResponse
+import com.teamsparker.android.data.remote.entity.response.HabitRoomTimeLine
+import com.teamsparker.android.data.remote.service.HabitRoomTimeLineService
+import javax.inject.Inject
+
+class HabitRoomTImeLineDataSourceImpl @Inject constructor(
+ private val habitRoomTimeLineService: HabitRoomTimeLineService
+) : HabitRoomTimeLineDataSource {
+ override suspend fun getHabitRoomTimeLine(roomId: Int): NetworkState> =
+ habitRoomTimeLineService.getHabitRoomRimeLine(roomId)
+}
diff --git a/app/src/main/java/com/teamsparker/android/data/remote/datasource/HabitRoomTimeLineDataSource.kt b/app/src/main/java/com/teamsparker/android/data/remote/datasource/HabitRoomTimeLineDataSource.kt
new file mode 100644
index 00000000..f5e17aac
--- /dev/null
+++ b/app/src/main/java/com/teamsparker/android/data/remote/datasource/HabitRoomTimeLineDataSource.kt
@@ -0,0 +1,10 @@
+package com.teamsparker.android.data.remote.datasource
+
+import com.teamsparker.android.data.remote.calladapter.NetworkState
+import com.teamsparker.android.data.remote.entity.response.BaseResponse
+import com.teamsparker.android.data.remote.entity.response.HabitRoomTimeLine
+
+interface HabitRoomTimeLineDataSource {
+
+ suspend fun getHabitRoomTimeLine(roomId: Int): NetworkState>
+}
diff --git a/app/src/main/java/com/teamsparker/android/data/remote/entity/response/HabitResponse.kt b/app/src/main/java/com/teamsparker/android/data/remote/entity/response/HabitResponse.kt
index e4a54784..e914cf70 100644
--- a/app/src/main/java/com/teamsparker/android/data/remote/entity/response/HabitResponse.kt
+++ b/app/src/main/java/com/teamsparker/android/data/remote/entity/response/HabitResponse.kt
@@ -5,7 +5,6 @@ data class HabitResponse(
val roomName: String,
val leftDay: Int,
val life: Int,
- val lifeDeductionCount : Int,
val startDate: String,
val endDate: String,
val fromStart: Boolean,
@@ -13,6 +12,8 @@ data class HabitResponse(
val purpose: String,
val myRecord: HabitRecord,
val otherRecords: List,
+ val isTimelineNew: Boolean,
+ val isTermNew: Boolean
)
data class HabitRecord(
@@ -22,7 +23,7 @@ data class HabitRecord(
val recordId: Int,
val rest: Int = -1,
val status: String,
- val userId: Int,
+ val userId: Int
)
data class OtherRecord(
@@ -30,5 +31,5 @@ data class OtherRecord(
val profileImg: String,
val recordId: Int,
val status: String,
- val userId: Int,
+ val userId: Int
)
diff --git a/app/src/main/java/com/teamsparker/android/data/remote/entity/response/HabitRoomTimeLineResponse.kt b/app/src/main/java/com/teamsparker/android/data/remote/entity/response/HabitRoomTimeLineResponse.kt
new file mode 100644
index 00000000..96bb3ba0
--- /dev/null
+++ b/app/src/main/java/com/teamsparker/android/data/remote/entity/response/HabitRoomTimeLineResponse.kt
@@ -0,0 +1,21 @@
+package com.teamsparker.android.data.remote.entity.response
+
+import com.google.gson.annotations.SerializedName
+
+data class HabitRoomTimeLine(
+ @SerializedName("timelines")
+ val timelines: List
+)
+
+data class Timeline(
+ @SerializedName("content")
+ val content: String,
+ @SerializedName("day")
+ val day: String,
+ @SerializedName("isNew")
+ val isNew: Boolean,
+ @SerializedName("profiles")
+ val profiles: List,
+ @SerializedName("title")
+ val title: String
+)
diff --git a/app/src/main/java/com/teamsparker/android/data/remote/repository/HabitRepository.kt b/app/src/main/java/com/teamsparker/android/data/remote/repository/HabitRepository.kt
index 157cf409..7f822c75 100644
--- a/app/src/main/java/com/teamsparker/android/data/remote/repository/HabitRepository.kt
+++ b/app/src/main/java/com/teamsparker/android/data/remote/repository/HabitRepository.kt
@@ -1,8 +1,12 @@
package com.teamsparker.android.data.remote.repository
+import com.teamsparker.android.data.remote.entity.response.HabitRoomTimeLine
+
interface HabitRepository {
fun setHabitUserGuideState(state: Boolean)
fun getHabitUserGuideState(): Boolean
+
+ suspend fun getHabitRoomTimeLine(roomId: Int): Result
}
diff --git a/app/src/main/java/com/teamsparker/android/data/remote/repository/HabitRepositoryImpl.kt b/app/src/main/java/com/teamsparker/android/data/remote/repository/HabitRepositoryImpl.kt
index 238fb60a..9fd19c8d 100644
--- a/app/src/main/java/com/teamsparker/android/data/remote/repository/HabitRepositoryImpl.kt
+++ b/app/src/main/java/com/teamsparker/android/data/remote/repository/HabitRepositoryImpl.kt
@@ -1,10 +1,17 @@
package com.teamsparker.android.data.remote.repository
import com.teamsparker.android.data.local.datasource.LocalPreferencesHabitDataSource
+import com.teamsparker.android.data.remote.calladapter.NetworkState
+import com.teamsparker.android.data.remote.calladapter.RetrofitFailureStateException
+import com.teamsparker.android.data.remote.datasource.HabitRoomTimeLineDataSource
+import com.teamsparker.android.data.remote.entity.response.HabitRoomTimeLine
+import timber.log.Timber.Forest.tag
+import java.security.cert.CertificateException
import javax.inject.Inject
class HabitRepositoryImpl @Inject constructor(
- private val localPreferencesHabitDataSource: LocalPreferencesHabitDataSource
+ private val localPreferencesHabitDataSource: LocalPreferencesHabitDataSource,
+ private val habitRoomTimeLineDataSource: HabitRoomTimeLineDataSource
) : HabitRepository {
override fun setHabitUserGuideState(state: Boolean) {
@@ -13,4 +20,22 @@ class HabitRepositoryImpl @Inject constructor(
override fun getHabitUserGuideState(): Boolean =
localPreferencesHabitDataSource.getHabitUserGuideState()
+
+ override suspend fun getHabitRoomTimeLine(roomId: Int): Result {
+ when (val response = habitRoomTimeLineDataSource.getHabitRoomTimeLine(roomId)) {
+ is NetworkState.Success -> return Result.success(
+ response.body.data
+ )
+ is NetworkState.Failure ->
+ if (response.code == 401) throw CertificateException("토큰 만료 오류")
+ else return Result.failure(
+ RetrofitFailureStateException(response.error, response.code)
+ )
+ is NetworkState.NetworkError -> tag("${this.javaClass.name}_getHabitRoomTimeLine")
+ .d(response.error)
+ is NetworkState.UnknownError -> tag("${this.javaClass.name}_getHabitRoomTimeLine")
+ .d(response.t)
+ }
+ return Result.failure(IllegalStateException("NetworkError or UnKnownError please check timber"))
+ }
}
diff --git a/app/src/main/java/com/teamsparker/android/data/remote/service/HabitRoomTimeLineService.kt b/app/src/main/java/com/teamsparker/android/data/remote/service/HabitRoomTimeLineService.kt
new file mode 100644
index 00000000..f28860a2
--- /dev/null
+++ b/app/src/main/java/com/teamsparker/android/data/remote/service/HabitRoomTimeLineService.kt
@@ -0,0 +1,14 @@
+package com.teamsparker.android.data.remote.service
+
+import com.teamsparker.android.data.remote.calladapter.NetworkState
+import com.teamsparker.android.data.remote.entity.response.BaseResponse
+import com.teamsparker.android.data.remote.entity.response.HabitRoomTimeLine
+import retrofit2.http.GET
+import retrofit2.http.Path
+
+interface HabitRoomTimeLineService {
+ @GET("room/{roomId}/timeline")
+ suspend fun getHabitRoomRimeLine(
+ @Path("roomId") roomId: Int
+ ): NetworkState>
+}
diff --git a/app/src/main/java/com/teamsparker/android/di/RemoteDataSourceModule.kt b/app/src/main/java/com/teamsparker/android/di/RemoteDataSourceModule.kt
index 332f68c0..513777c0 100644
--- a/app/src/main/java/com/teamsparker/android/di/RemoteDataSourceModule.kt
+++ b/app/src/main/java/com/teamsparker/android/di/RemoteDataSourceModule.kt
@@ -2,6 +2,7 @@ package com.teamsparker.android.di
import com.teamsparker.android.data.remote.datasource.*
import com.teamsparker.android.data.remote.service.*
+import dagger.Binds
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@@ -57,4 +58,11 @@ object RemoteDataSourceModule {
alarmCenterService: AlarmCenterService
): AlarmCenterDataSource =
AlarmCenterDataSourceImpl(alarmCenterService)
+
+ @Provides
+ @Singleton
+ fun provideHabitRoomTimeLineDataSource(
+ habitRoomTimeLineService: HabitRoomTimeLineService
+ ): HabitRoomTimeLineDataSource =
+ HabitRoomTImeLineDataSourceImpl(habitRoomTimeLineService)
}
diff --git a/app/src/main/java/com/teamsparker/android/di/RepositoryModule.kt b/app/src/main/java/com/teamsparker/android/di/RepositoryModule.kt
index 76fcd9e9..558280b1 100644
--- a/app/src/main/java/com/teamsparker/android/di/RepositoryModule.kt
+++ b/app/src/main/java/com/teamsparker/android/di/RepositoryModule.kt
@@ -3,12 +3,7 @@ package com.teamsparker.android.di
import com.teamsparker.android.data.local.datasource.*
import com.teamsparker.android.data.remote.datasource.*
import com.teamsparker.android.data.remote.repository.*
-import com.teamsparker.android.data.remote.service.JoinCodeRoomDoneService
-import com.teamsparker.android.data.remote.service.JoinCodeRoomInfoService
-import com.teamsparker.android.data.remote.service.MakeRoomService
-import com.teamsparker.android.data.remote.service.RefreshService
-import com.teamsparker.android.data.remote.service.SetPurposeService
-import com.teamsparker.android.data.remote.service.StartHabitService
+import com.teamsparker.android.data.remote.service.*
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@@ -120,9 +115,8 @@ object RepositoryModule {
@Provides
@Singleton
fun providesHabitRepository(
- localPreferencesHabitDataSource: LocalPreferencesHabitDataSource
+ localPreferencesHabitDataSource: LocalPreferencesHabitDataSource,
+ habitRoomTimeLineDataSource: HabitRoomTimeLineDataSource
): HabitRepository =
- HabitRepositoryImpl(localPreferencesHabitDataSource)
+ HabitRepositoryImpl(localPreferencesHabitDataSource, habitRoomTimeLineDataSource)
}
-
-
diff --git a/app/src/main/java/com/teamsparker/android/di/RetrofitModule.kt b/app/src/main/java/com/teamsparker/android/di/RetrofitModule.kt
index e2fe353a..3cbd94b2 100644
--- a/app/src/main/java/com/teamsparker/android/di/RetrofitModule.kt
+++ b/app/src/main/java/com/teamsparker/android/di/RetrofitModule.kt
@@ -1,6 +1,7 @@
package com.teamsparker.android.di
import com.teamsparker.android.data.local.datasource.LocalPreferencesDataSource
+import com.teamsparker.android.data.remote.calladapter.CustomCallAdapterFactory
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@@ -45,6 +46,7 @@ object RetrofitModule {
Retrofit.Builder()
.baseUrl("https://asia-northeast3-we-sopt-spark.cloudfunctions.net/api/")
.client(okHttpClient)
+ .addCallAdapterFactory(CustomCallAdapterFactory())
.addConverterFactory(GsonConverterFactory.create())
.build()
}
diff --git a/app/src/main/java/com/teamsparker/android/di/RetrofitServiceModule.kt b/app/src/main/java/com/teamsparker/android/di/RetrofitServiceModule.kt
index da53980a..ebc0078b 100644
--- a/app/src/main/java/com/teamsparker/android/di/RetrofitServiceModule.kt
+++ b/app/src/main/java/com/teamsparker/android/di/RetrofitServiceModule.kt
@@ -95,4 +95,9 @@ object RetrofitServiceModule {
@Singleton
fun provideHomeNoticeRedDotService(retrofit: Retrofit): HomeNoticeRedDotService =
retrofit.create(HomeNoticeRedDotService::class.java)
+
+ @Provides
+ @Singleton
+ fun provideHabitRoomTimeLineService(retrofit: Retrofit): HabitRoomTimeLineService =
+ retrofit.create(HabitRoomTimeLineService::class.java)
}
diff --git a/app/src/main/java/com/teamsparker/android/ui/base/BaseActivity.kt b/app/src/main/java/com/teamsparker/android/ui/base/BaseActivity.kt
index 6a6f066b..a0cf1e46 100644
--- a/app/src/main/java/com/teamsparker/android/ui/base/BaseActivity.kt
+++ b/app/src/main/java/com/teamsparker/android/ui/base/BaseActivity.kt
@@ -1,10 +1,28 @@
package com.teamsparker.android.ui.base
+import android.content.Intent
+import android.content.SharedPreferences
import android.os.Bundle
import androidx.annotation.LayoutRes
import androidx.appcompat.app.AppCompatActivity
+import androidx.core.content.edit
import androidx.databinding.DataBindingUtil
import androidx.databinding.ViewDataBinding
+import com.teamsparker.android.data.local.datasource.LocalPreferencesDataSourceImpl
+import com.teamsparker.android.data.local.datasource.LocalPreferencesDataSourceImpl.Companion.ACCESS_TOKEN
+import com.teamsparker.android.data.local.datasource.LocalPreferencesDataSourceImpl.Companion.ALARM_LOCAL_SAVED
+import com.teamsparker.android.data.local.datasource.LocalPreferencesDataSourceImpl.Companion.USER_KAKAO_USER_ID
+import com.teamsparker.android.ui.alarmsetting.AlarmSettingViewModel
+import com.teamsparker.android.ui.alarmsetting.AlarmSettingViewModel.Companion.ALARM_CERTIFICATION
+import com.teamsparker.android.ui.alarmsetting.AlarmSettingViewModel.Companion.ALARM_CONSIDER
+import com.teamsparker.android.ui.alarmsetting.AlarmSettingViewModel.Companion.ALARM_REMIND
+import com.teamsparker.android.ui.alarmsetting.AlarmSettingViewModel.Companion.ALARM_ROOM_START
+import com.teamsparker.android.ui.alarmsetting.AlarmSettingViewModel.Companion.ALARM_SPARK
+import com.teamsparker.android.ui.auth.AuthActivity
+import com.teamsparker.android.util.EventObserver
+import com.teamsparker.android.util.Injector
+import dagger.hilt.android.EntryPointAccessors
+import com.teamsparker.android.ui.alarmsetting.AlarmSettingViewModel.Companion as AlarmSettingViewModel1
abstract class BaseActivity(
@LayoutRes private val layoutRes: Int
@@ -13,7 +31,33 @@ abstract class BaseActivity(
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- binding = DataBindingUtil.setContentView(this,layoutRes)
+ binding = DataBindingUtil.setContentView(this, layoutRes)
binding.lifecycleOwner = this
}
+
+ private val sharedPreferences: SharedPreferences by lazy {
+ EntryPointAccessors.fromActivity(
+ this,
+ Injector.SharedPreferencesInjector::class.java
+ ).sharedPreferences()
+ }
+
+ protected fun terminationTokenHandling(viewModel: BaseViewModel) {
+ viewModel.moveToLogin.observe(
+ this,
+ EventObserver {
+ val intent = Intent(this, AuthActivity::class.java)
+ startActivity(intent)
+ sharedPreferences.edit { remove(ACCESS_TOKEN) }
+ sharedPreferences.edit { remove(USER_KAKAO_USER_ID) }
+ sharedPreferences.edit { remove(ALARM_LOCAL_SAVED) }
+ sharedPreferences.edit { remove(ALARM_ROOM_START) }
+ sharedPreferences.edit { remove(ALARM_SPARK) }
+ sharedPreferences.edit { remove(ALARM_CONSIDER) }
+ sharedPreferences.edit { remove(ALARM_CERTIFICATION) }
+ sharedPreferences.edit { remove(ALARM_REMIND) }
+ finishAffinity()
+ }
+ )
+ }
}
diff --git a/app/src/main/java/com/teamsparker/android/ui/base/BaseFragment.kt b/app/src/main/java/com/teamsparker/android/ui/base/BaseFragment.kt
index 507963c0..73140fef 100644
--- a/app/src/main/java/com/teamsparker/android/ui/base/BaseFragment.kt
+++ b/app/src/main/java/com/teamsparker/android/ui/base/BaseFragment.kt
@@ -1,13 +1,30 @@
package com.teamsparker.android.ui.base
+import android.content.Intent
+import android.content.SharedPreferences
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.LayoutRes
+import androidx.core.content.edit
import androidx.databinding.DataBindingUtil
import androidx.databinding.ViewDataBinding
import androidx.fragment.app.Fragment
+import com.teamsparker.android.data.local.datasource.LocalPreferencesDataSourceImpl
+import com.teamsparker.android.data.local.datasource.LocalPreferencesDataSourceImpl.Companion.ACCESS_TOKEN
+import com.teamsparker.android.data.local.datasource.LocalPreferencesDataSourceImpl.Companion.ALARM_LOCAL_SAVED
+import com.teamsparker.android.data.local.datasource.LocalPreferencesDataSourceImpl.Companion.USER_KAKAO_USER_ID
+import com.teamsparker.android.ui.alarmsetting.AlarmSettingViewModel
+import com.teamsparker.android.ui.alarmsetting.AlarmSettingViewModel.Companion.ALARM_CERTIFICATION
+import com.teamsparker.android.ui.alarmsetting.AlarmSettingViewModel.Companion.ALARM_CONSIDER
+import com.teamsparker.android.ui.alarmsetting.AlarmSettingViewModel.Companion.ALARM_REMIND
+import com.teamsparker.android.ui.alarmsetting.AlarmSettingViewModel.Companion.ALARM_ROOM_START
+import com.teamsparker.android.ui.alarmsetting.AlarmSettingViewModel.Companion.ALARM_SPARK
+import com.teamsparker.android.ui.auth.AuthActivity
+import com.teamsparker.android.util.EventObserver
+import com.teamsparker.android.util.Injector
+import dagger.hilt.android.EntryPointAccessors
abstract class BaseFragment(
@LayoutRes private val layoutRes: Int
@@ -26,6 +43,32 @@ abstract class BaseFragment(
return binding.root
}
+ private val sharedPreferences: SharedPreferences by lazy {
+ EntryPointAccessors.fromActivity(
+ requireActivity(),
+ Injector.SharedPreferencesInjector::class.java
+ ).sharedPreferences()
+ }
+
+ fun terminationTokenHandling(viewModel: BaseViewModel) {
+ viewModel.moveToLogin.observe(
+ viewLifecycleOwner,
+ EventObserver {
+ val intent = Intent(requireActivity(), AuthActivity::class.java)
+ startActivity(intent)
+ sharedPreferences.edit { remove(ACCESS_TOKEN) }
+ sharedPreferences.edit { remove(USER_KAKAO_USER_ID) }
+ sharedPreferences.edit { remove(ALARM_LOCAL_SAVED) }
+ sharedPreferences.edit { remove(ALARM_ROOM_START) }
+ sharedPreferences.edit { remove(ALARM_SPARK) }
+ sharedPreferences.edit { remove(ALARM_CONSIDER) }
+ sharedPreferences.edit { remove(ALARM_CERTIFICATION) }
+ sharedPreferences.edit { remove(ALARM_REMIND) }
+ requireActivity().finishAffinity()
+ }
+ )
+ }
+
override fun onDestroyView() {
super.onDestroyView()
_binding = null
diff --git a/app/src/main/java/com/teamsparker/android/ui/base/BaseViewModel.kt b/app/src/main/java/com/teamsparker/android/ui/base/BaseViewModel.kt
new file mode 100644
index 00000000..0dd69963
--- /dev/null
+++ b/app/src/main/java/com/teamsparker/android/ui/base/BaseViewModel.kt
@@ -0,0 +1,20 @@
+package com.teamsparker.android.ui.base
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import com.teamsparker.android.util.Event
+import kotlinx.coroutines.CoroutineExceptionHandler
+import java.security.cert.CertificateException
+
+abstract class BaseViewModel : ViewModel() {
+
+ private val _moveToLogin = MutableLiveData>()
+ val moveToLogin: LiveData> = _moveToLogin
+
+ val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
+ when (throwable) {
+ is CertificateException -> _moveToLogin.postValue(Event(true))
+ }
+ }
+}
diff --git a/app/src/main/java/com/teamsparker/android/ui/habit/FlameRoadMapDialogFragment.kt b/app/src/main/java/com/teamsparker/android/ui/habit/FlameRoadMapDialogFragment.kt
new file mode 100644
index 00000000..8a6c51b4
--- /dev/null
+++ b/app/src/main/java/com/teamsparker/android/ui/habit/FlameRoadMapDialogFragment.kt
@@ -0,0 +1,139 @@
+package com.teamsparker.android.ui.habit
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.app.DialogFragment
+import androidx.fragment.app.activityViewModels
+import androidx.viewpager2.widget.ViewPager2
+import com.teamsparker.android.R
+import com.teamsparker.android.databinding.FragmentFlameRoadMapDialogBinding
+import com.teamsparker.android.ui.habit.adapter.FlameRoadMapAdapter
+import com.teamsparker.android.ui.habit.flameroadmap.*
+import com.teamsparker.android.ui.habit.viewmodel.HabitViewModel
+import dagger.hilt.android.AndroidEntryPoint
+import kotlin.math.abs
+
+@AndroidEntryPoint
+class FlameRoadMapDialogFragment : DialogFragment() {
+
+ private val habitViewModel by activityViewModels()
+ private lateinit var flameRoadMapAdapter: FlameRoadMapAdapter
+
+ private var _binding: FragmentFlameRoadMapDialogBinding? = null
+ private val binding
+ get() = _binding ?: error(getString(com.teamsparker.android.R.string.binding_error))
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ isCancelable = true
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ // Inflate the layout for this fragment
+ _binding = FragmentFlameRoadMapDialogBinding.inflate(inflater, container, false)
+ binding.lifecycleOwner = viewLifecycleOwner
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ binding.habitViewModel = habitViewModel
+ initViewPagerAdapter()
+ setViewPagerOption()
+ initViewPagerPositionListener()
+ initCheckButtonOnclickListener()
+ initViewPagerPosition()
+ }
+
+ override fun onStart() {
+ super.onStart()
+ setLayout()
+ }
+
+ private fun setLayout() {
+ requireNotNull(dialog).apply {
+ requireNotNull(window).apply {
+ setLayout(
+ (resources.displayMetrics.widthPixels * 0.91).toInt(),
+ ViewGroup.LayoutParams.WRAP_CONTENT
+ )
+ setBackgroundDrawableResource(R.drawable.shape_spark_white_fill_2_rect)
+ }
+ }
+ }
+
+ private fun initViewPagerAdapter() {
+ val flakeList = listOf(
+ Level1Fragment(),
+ Level2Fragment(),
+ Level3Fragment(),
+ Level4Fragment(),
+ Level5Fragment(),
+ Level6Fragment()
+ )
+
+ flameRoadMapAdapter = FlameRoadMapAdapter(this)
+ flameRoadMapAdapter.fragments.addAll(flakeList)
+ binding.vpFlameRoadmap.adapter = flameRoadMapAdapter
+ }
+
+ private fun setViewPagerOption() {
+ val pageMarginPx = resources.getDimensionPixelOffset(R.dimen.flameRoadMapPageMargin)
+ val pagerWidth = resources.getDimensionPixelOffset(R.dimen.flameRoadMapPagerWidth)
+ val screenWidth = resources.displayMetrics.widthPixels
+ val offsetPx = screenWidth - pageMarginPx - pagerWidth
+
+ binding.vpFlameRoadmap.apply {
+ offscreenPageLimit = 3
+ setPageTransformer { page, position ->
+ // 미리보기 살짝 보이기
+ page.translationX = position * -offsetPx
+ // 미리보기 크기 조절
+ var focusedPageDistanceRatio = 1 - abs(position)
+ page.scaleY = 0.7f + focusedPageDistanceRatio * 0.3f
+ page.scaleX = 0.7f + focusedPageDistanceRatio * 0.3f
+ }
+ }
+ }
+
+ private fun initViewPagerPositionListener() {
+ binding.vpFlameRoadmap.registerOnPageChangeCallback(object :
+ ViewPager2.OnPageChangeCallback() {
+ override fun onPageSelected(position: Int) {
+ super.onPageSelected(position)
+ binding.level = position + 1
+ }
+ })
+ }
+
+ private fun initCheckButtonOnclickListener() {
+ binding.checkButton.setOnClickListener {
+ dismiss()
+ }
+ }
+
+ private fun initViewPagerPosition() {
+ binding.vpFlameRoadmap.currentItem
+ when (habitViewModel.habitInfo.value?.leftDay) {
+ 0 -> binding.vpFlameRoadmap.currentItem = 5
+ in 1..6 -> binding.vpFlameRoadmap.currentItem = 4
+ in 7..32 -> binding.vpFlameRoadmap.currentItem = 3
+ in 33..58 -> binding.vpFlameRoadmap.currentItem = 2
+ in 59..62 -> binding.vpFlameRoadmap.currentItem = 1
+ in 63..65 -> binding.vpFlameRoadmap.currentItem = 0
+ else -> throw IllegalArgumentException("leftDay 범위 에러")
+ }
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ _binding = null
+ }
+}
diff --git a/app/src/main/java/com/teamsparker/android/ui/habit/HabitActivity.kt b/app/src/main/java/com/teamsparker/android/ui/habit/HabitActivity.kt
index dd9b8922..05bb6830 100644
--- a/app/src/main/java/com/teamsparker/android/ui/habit/HabitActivity.kt
+++ b/app/src/main/java/com/teamsparker/android/ui/habit/HabitActivity.kt
@@ -1,6 +1,7 @@
package com.teamsparker.android.ui.habit
import android.os.Bundle
+import android.view.View
import androidx.activity.viewModels
import com.teamsparker.android.R
import com.teamsparker.android.data.remote.LocalPreferences
@@ -10,9 +11,13 @@ import com.teamsparker.android.ui.habit.adapter.HabitRecyclerViewAdapter
import com.teamsparker.android.ui.habit.userguide.UserGuideFragmentDialog
import com.teamsparker.android.ui.habit.viewmodel.HabitViewModel
import com.teamsparker.android.util.FirebaseLogUtil
+import com.teamsparker.android.util.FirebaseLogUtil.CLICK_TIMELINE_NEW_HABIT_ROOM
+import com.teamsparker.android.util.FirebaseLogUtil.CLICK_TIMELINE_NONE_HABIT_ROOM
import com.teamsparker.android.util.FirebaseLogUtil.SCREEN_HABIT_ROOM
+import com.teamsparker.android.util.ext.setOnSingleClickListener
import com.teamsparker.android.util.initStatusBarColor
import dagger.hilt.android.AndroidEntryPoint
+import java.io.Serializable
import kotlin.properties.Delegates
@AndroidEntryPoint
@@ -38,6 +43,7 @@ class HabitActivity : BaseActivity(R.layout.activity_habit
initHabitMoreBtnClickListener()
initHabitTodayBtnClickListener()
checkUserGuideDialog()
+ initGroupTeamLifeClickListener()
}
private fun initRoomId() {
@@ -48,7 +54,16 @@ class HabitActivity : BaseActivity(R.layout.activity_habit
habitViewModel.habitInfo.observe(this) {
habitRecyclerViewAdapter.response = it
binding.habitViewModel = habitViewModel
- initHabitLifeLessDialog()
+ if (habitViewModel.habitInfo.value?.isTermNew
+ ?: throw IllegalStateException("isTermNew 값 null로 옴")
+ ) {
+ FlameRoadMapDialogFragment().show(
+ supportFragmentManager,
+ "FlameRoadMapDialogFragment"
+ )
+ }
+// 1.1.0에서 삭제 다른기능으로 대체
+// initHabitLifeLessDialog()
}
}
@@ -69,7 +84,7 @@ class HabitActivity : BaseActivity(R.layout.activity_habit
toastMessage = toastMessage.chunked(8)[0] + "..."
}
habitViewModel.initExitSuccess(false)
- LocalPreferences.setExitHabitRoomHomeToastMessage("‘${toastMessage}’ 방을 나갔어요.")
+ LocalPreferences.setExitHabitRoomHomeToastMessage("‘$toastMessage’ 방을 나갔어요.")
LocalPreferences.setExitHabitRoomHomeToastMessageState(true)
finish()
}
@@ -134,12 +149,37 @@ class HabitActivity : BaseActivity(R.layout.activity_habit
}
}
- private fun initHabitLifeLessDialog() {
- val lifeDeductionCount = habitViewModel.habitInfo.value?.lifeDeductionCount ?: 0
- if (lifeDeductionCount != 0) {
- HabitLifeLessDialogFragment(lifeDeductionCount).show(
- supportFragmentManager, "LifeLessDialog"
- )
+// 1.1.0 에서 삭제 다른기능으로 대체됨
+// private fun initHabitLifeLessDialog() {
+// val lifeDeductionCount = habitViewModel.habitInfo.value?.lifeDeductionCount ?: 0
+// if (lifeDeductionCount != 0) {
+// HabitLifeLessDialogFragment(lifeDeductionCount).show(
+// supportFragmentManager,
+// "LifeLessDialog"
+// )
+// }
+// }
+
+ private fun initGroupTeamLifeClickListener() {
+ val lifeList = listOf(
+ binding.ivHabitTeamlifeFirst,
+ binding.ivHabitTeamlifeSecond,
+ binding.ivHabitTeamlifeThird
+ )
+
+ lifeList.forEach {
+ it.setOnSingleClickListener {
+ HabitTimeLineBottomSheet().apply {
+ arguments = Bundle().apply {
+ putSerializable(REFRESH_DATA, { refreshData() } as Serializable)
+ }
+ show(supportFragmentManager, this.javaClass.name)
+ }
+ if (habitViewModel.habitInfo.value?.isTimelineNew
+ ?: throw IllegalStateException("타임라인 클릭리스너 GA로그 관련오류")
+ ) FirebaseLogUtil.logClickEvent(CLICK_TIMELINE_NEW_HABIT_ROOM)
+ else FirebaseLogUtil.logClickEvent(CLICK_TIMELINE_NONE_HABIT_ROOM)
+ }
}
}
@@ -152,4 +192,8 @@ class HabitActivity : BaseActivity(R.layout.activity_habit
super.onPause()
overridePendingTransition(0, 0)
}
+
+ companion object {
+ const val REFRESH_DATA = "REFRESH_DATA"
+ }
}
diff --git a/app/src/main/java/com/teamsparker/android/ui/habit/HabitMoreBottomSheet.kt b/app/src/main/java/com/teamsparker/android/ui/habit/HabitMoreBottomSheet.kt
index dd63d455..d7a93545 100644
--- a/app/src/main/java/com/teamsparker/android/ui/habit/HabitMoreBottomSheet.kt
+++ b/app/src/main/java/com/teamsparker/android/ui/habit/HabitMoreBottomSheet.kt
@@ -13,6 +13,7 @@ import com.teamsparker.android.databinding.BottomSheetHabitMoreBinding
import com.teamsparker.android.ui.habit.userguide.UserGuideFragmentDialog
import com.teamsparker.android.ui.habit.viewmodel.HabitViewModel
import com.teamsparker.android.util.DialogEditTextUtil
+import com.teamsparker.android.util.ext.setOnSingleClickListener
class HabitMoreBottomSheet : BottomSheetDialogFragment() {
private var _binding: BottomSheetHabitMoreBinding? = null
@@ -22,7 +23,7 @@ class HabitMoreBottomSheet : BottomSheetDialogFragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
- savedInstanceState: Bundle?,
+ savedInstanceState: Bundle?
): View? {
_binding = BottomSheetHabitMoreBinding.inflate(inflater, container, false)
return binding.root
@@ -38,6 +39,7 @@ class HabitMoreBottomSheet : BottomSheetDialogFragment() {
initGoalBtnClickListener()
initExitBtnClickListener()
initUserGuideBtnClickListener()
+ initFlameRoadMapBtnClickListener()
}
private fun initGoalBtnClickListener() {
@@ -83,6 +85,13 @@ class HabitMoreBottomSheet : BottomSheetDialogFragment() {
}
}
+ private fun initFlameRoadMapBtnClickListener() {
+ binding.tvHabitMoreFlameRoadmap.setOnSingleClickListener {
+ FlameRoadMapDialogFragment().show(requireActivity().supportFragmentManager, "FlameRoadMapDialogFragment")
+ dismiss()
+ }
+ }
+
override fun onDestroyView() {
super.onDestroyView()
_binding = null
diff --git a/app/src/main/java/com/teamsparker/android/ui/habit/HabitTimeLineBottomSheet.kt b/app/src/main/java/com/teamsparker/android/ui/habit/HabitTimeLineBottomSheet.kt
new file mode 100644
index 00000000..6547486b
--- /dev/null
+++ b/app/src/main/java/com/teamsparker/android/ui/habit/HabitTimeLineBottomSheet.kt
@@ -0,0 +1,102 @@
+package com.teamsparker.android.ui.habit
+
+import android.app.Dialog
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.app.activityViewModels
+import com.google.android.material.bottomsheet.BottomSheetBehavior
+import com.google.android.material.bottomsheet.BottomSheetDialog
+import com.google.android.material.bottomsheet.BottomSheetDialogFragment
+import com.teamsparker.android.R
+import com.teamsparker.android.databinding.BottomSheetHabitTimeLineBinding
+import com.teamsparker.android.ui.habit.HabitActivity.Companion.REFRESH_DATA
+import com.teamsparker.android.ui.habit.adapter.HabitTimeLineRecyclerViewAdapter
+import com.teamsparker.android.ui.habit.viewmodel.HabitViewModel
+import dagger.hilt.android.AndroidEntryPoint
+
+@AndroidEntryPoint
+class HabitTimeLineBottomSheet : BottomSheetDialogFragment() {
+
+ private var _binding: BottomSheetHabitTimeLineBinding? = null
+ val binding get() = _binding ?: error(getString(R.string.binding_error))
+
+ private val habitViewModel by activityViewModels()
+ private lateinit var habitTimeLineRecyclerViewAdapter: HabitTimeLineRecyclerViewAdapter
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ refreshDataOnHabitActivity()
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ _binding = BottomSheetHabitTimeLineBinding.inflate(inflater, container, false)
+ binding.lifecycleOwner = viewLifecycleOwner
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ initTimeLineData()
+ initHabitTimeLineRecyclerViewAdapter()
+ updateRecyclerViewList()
+ }
+
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ val dialog = super.onCreateDialog(savedInstanceState)
+ dialog.setOnShowListener { dialogInterface ->
+ val bottomSheetDialog = dialogInterface as BottomSheetDialog
+ setupRatio(bottomSheetDialog)
+ }
+ return dialog
+ }
+
+ private fun refreshDataOnHabitActivity() {
+ val refreshDataOnHabitActivity = arguments?.getSerializable(REFRESH_DATA) as () -> Unit
+ refreshDataOnHabitActivity()
+ }
+
+ // 다이얼로그 높이 비율로 조정 코드
+ private fun setupRatio(bottomSheetDialog: BottomSheetDialog) {
+ val bottomSheet =
+ bottomSheetDialog.findViewById(com.google.android.material.R.id.design_bottom_sheet) as View
+ val behavior = BottomSheetBehavior.from(bottomSheet)
+ val layoutParams = bottomSheet!!.layoutParams
+ layoutParams.height = getBottomSheetDialogDefaultHeight()
+ bottomSheet.layoutParams = layoutParams
+ behavior.state = BottomSheetBehavior.STATE_EXPANDED
+ }
+
+ private fun getBottomSheetDialogDefaultHeight(): Int {
+ return getWindowHeight() * 449 / 736
+ // 기기 높이 대비 비율 설정 부분!!
+ }
+
+ private fun getWindowHeight(): Int = resources.displayMetrics.heightPixels
+
+ private fun initTimeLineData() {
+ habitViewModel.getHabitRoomTimeLine()
+ }
+
+ private fun initHabitTimeLineRecyclerViewAdapter() {
+ habitTimeLineRecyclerViewAdapter = HabitTimeLineRecyclerViewAdapter()
+ binding.rvTimeLine.adapter = habitTimeLineRecyclerViewAdapter
+ }
+
+ private fun updateRecyclerViewList() {
+ habitViewModel.timeLineList.observe(viewLifecycleOwner) {
+ habitTimeLineRecyclerViewAdapter.submitList(it.timelines)
+ }
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ _binding = null
+ }
+}
diff --git a/app/src/main/java/com/teamsparker/android/ui/habit/adapter/FlameRoadMapAdapter.kt b/app/src/main/java/com/teamsparker/android/ui/habit/adapter/FlameRoadMapAdapter.kt
new file mode 100644
index 00000000..fb2bdbb4
--- /dev/null
+++ b/app/src/main/java/com/teamsparker/android/ui/habit/adapter/FlameRoadMapAdapter.kt
@@ -0,0 +1,12 @@
+package com.teamsparker.android.ui.habit.adapter
+
+import androidx.fragment.app.Fragment
+import androidx.viewpager2.adapter.FragmentStateAdapter
+
+class FlameRoadMapAdapter(fragment: Fragment) : FragmentStateAdapter(fragment) {
+ val fragments = mutableListOf()
+
+ override fun getItemCount(): Int = fragments.size
+
+ override fun createFragment(position: Int): Fragment = fragments[position]
+}
diff --git a/app/src/main/java/com/teamsparker/android/ui/habit/adapter/HabitTimeLineRecyclerViewAdapter.kt b/app/src/main/java/com/teamsparker/android/ui/habit/adapter/HabitTimeLineRecyclerViewAdapter.kt
new file mode 100644
index 00000000..682060dc
--- /dev/null
+++ b/app/src/main/java/com/teamsparker/android/ui/habit/adapter/HabitTimeLineRecyclerViewAdapter.kt
@@ -0,0 +1,44 @@
+package com.teamsparker.android.ui.habit.adapter
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.recyclerview.widget.ListAdapter
+import androidx.recyclerview.widget.RecyclerView
+import com.teamsparker.android.data.remote.entity.response.Timeline
+import com.teamsparker.android.databinding.ItemHabitTimeLineBinding
+import com.teamsparker.android.util.ItemDiffCallback
+
+class HabitTimeLineRecyclerViewAdapter() :
+ ListAdapter(
+ ItemDiffCallback(
+ onContentsTheSame = { old, new -> old == new },
+ onItemsTheSame = { old, new -> old.title == new.title }
+ )
+ ) {
+
+ private lateinit var inflater: LayoutInflater
+
+ override fun onCreateViewHolder(
+ parent: ViewGroup,
+ viewType: Int
+ ): HabitTImeLineRecyclerViewHolder {
+ if (!::inflater.isInitialized) {
+ inflater = LayoutInflater.from(parent.context)
+ }
+
+ val binding = ItemHabitTimeLineBinding.inflate(inflater, parent, false)
+
+ return HabitTImeLineRecyclerViewHolder(binding)
+ }
+
+ override fun onBindViewHolder(holder: HabitTImeLineRecyclerViewHolder, position: Int) {
+ holder.onBInd(getItem(position))
+ }
+
+ class HabitTImeLineRecyclerViewHolder(private val binding: ItemHabitTimeLineBinding) :
+ RecyclerView.ViewHolder(binding.root) {
+ fun onBInd(data: Timeline) {
+ binding.timeLine = data
+ }
+ }
+}
diff --git a/app/src/main/java/com/teamsparker/android/ui/habit/flameroadmap/Level1Fragment.kt b/app/src/main/java/com/teamsparker/android/ui/habit/flameroadmap/Level1Fragment.kt
new file mode 100644
index 00000000..777d7d70
--- /dev/null
+++ b/app/src/main/java/com/teamsparker/android/ui/habit/flameroadmap/Level1Fragment.kt
@@ -0,0 +1,9 @@
+package com.teamsparker.android.ui.habit.flameroadmap
+
+import androidx.fragment.app.activityViewModels
+import com.teamsparker.android.R
+import com.teamsparker.android.databinding.FragmentLevel1Binding
+import com.teamsparker.android.ui.base.BaseFragment
+import com.teamsparker.android.ui.habit.viewmodel.HabitViewModel
+
+class Level1Fragment : BaseFragment(R.layout.fragment_level1)
diff --git a/app/src/main/java/com/teamsparker/android/ui/habit/flameroadmap/Level2Fragment.kt b/app/src/main/java/com/teamsparker/android/ui/habit/flameroadmap/Level2Fragment.kt
new file mode 100644
index 00000000..c86766b2
--- /dev/null
+++ b/app/src/main/java/com/teamsparker/android/ui/habit/flameroadmap/Level2Fragment.kt
@@ -0,0 +1,19 @@
+package com.teamsparker.android.ui.habit.flameroadmap
+
+import android.os.Bundle
+import android.view.View
+import androidx.fragment.app.activityViewModels
+import com.teamsparker.android.R
+import com.teamsparker.android.databinding.FragmentLevel2Binding
+import com.teamsparker.android.ui.base.BaseFragment
+import com.teamsparker.android.ui.habit.viewmodel.HabitViewModel
+
+class Level2Fragment : BaseFragment(R.layout.fragment_level2){
+
+ private val habitViewModel by activityViewModels()
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ binding.habitViewMdoel = habitViewModel
+ }
+}
diff --git a/app/src/main/java/com/teamsparker/android/ui/habit/flameroadmap/Level3Fragment.kt b/app/src/main/java/com/teamsparker/android/ui/habit/flameroadmap/Level3Fragment.kt
new file mode 100644
index 00000000..6b8bd234
--- /dev/null
+++ b/app/src/main/java/com/teamsparker/android/ui/habit/flameroadmap/Level3Fragment.kt
@@ -0,0 +1,19 @@
+package com.teamsparker.android.ui.habit.flameroadmap
+
+import android.os.Bundle
+import android.view.View
+import androidx.fragment.app.activityViewModels
+import com.teamsparker.android.R
+import com.teamsparker.android.databinding.FragmentLevel3Binding
+import com.teamsparker.android.ui.base.BaseFragment
+import com.teamsparker.android.ui.habit.viewmodel.HabitViewModel
+
+class Level3Fragment : BaseFragment(R.layout.fragment_level3){
+
+ private val habitViewModel by activityViewModels()
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ binding.habitViewMdoel = habitViewModel
+ }
+}
diff --git a/app/src/main/java/com/teamsparker/android/ui/habit/flameroadmap/Level4Fragment.kt b/app/src/main/java/com/teamsparker/android/ui/habit/flameroadmap/Level4Fragment.kt
new file mode 100644
index 00000000..d55d0ab4
--- /dev/null
+++ b/app/src/main/java/com/teamsparker/android/ui/habit/flameroadmap/Level4Fragment.kt
@@ -0,0 +1,19 @@
+package com.teamsparker.android.ui.habit.flameroadmap
+
+import android.os.Bundle
+import android.view.View
+import androidx.fragment.app.activityViewModels
+import com.teamsparker.android.R
+import com.teamsparker.android.databinding.FragmentLevel4Binding
+import com.teamsparker.android.ui.base.BaseFragment
+import com.teamsparker.android.ui.habit.viewmodel.HabitViewModel
+
+class Level4Fragment : BaseFragment(R.layout.fragment_level4){
+
+ private val habitViewModel by activityViewModels()
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ binding.habitViewMdoel = habitViewModel
+ }
+}
diff --git a/app/src/main/java/com/teamsparker/android/ui/habit/flameroadmap/Level5Fragment.kt b/app/src/main/java/com/teamsparker/android/ui/habit/flameroadmap/Level5Fragment.kt
new file mode 100644
index 00000000..e79bb9c4
--- /dev/null
+++ b/app/src/main/java/com/teamsparker/android/ui/habit/flameroadmap/Level5Fragment.kt
@@ -0,0 +1,19 @@
+package com.teamsparker.android.ui.habit.flameroadmap
+
+import android.os.Bundle
+import android.view.View
+import androidx.fragment.app.activityViewModels
+import com.teamsparker.android.R
+import com.teamsparker.android.databinding.FragmentLevel5Binding
+import com.teamsparker.android.ui.base.BaseFragment
+import com.teamsparker.android.ui.habit.viewmodel.HabitViewModel
+
+class Level5Fragment : BaseFragment(R.layout.fragment_level5){
+
+ private val habitViewModel by activityViewModels()
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ binding.habitViewMdoel = habitViewModel
+ }
+}
diff --git a/app/src/main/java/com/teamsparker/android/ui/habit/flameroadmap/Level6Fragment.kt b/app/src/main/java/com/teamsparker/android/ui/habit/flameroadmap/Level6Fragment.kt
new file mode 100644
index 00000000..fca59d2e
--- /dev/null
+++ b/app/src/main/java/com/teamsparker/android/ui/habit/flameroadmap/Level6Fragment.kt
@@ -0,0 +1,19 @@
+package com.teamsparker.android.ui.habit.flameroadmap
+
+import android.os.Bundle
+import android.view.View
+import androidx.fragment.app.activityViewModels
+import com.teamsparker.android.R
+import com.teamsparker.android.databinding.FragmentLevel6Binding
+import com.teamsparker.android.ui.base.BaseFragment
+import com.teamsparker.android.ui.habit.viewmodel.HabitViewModel
+
+class Level6Fragment : BaseFragment(R.layout.fragment_level6){
+
+ private val habitViewModel by activityViewModels()
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ binding.habitViewMdoel = habitViewModel
+ }
+}
diff --git a/app/src/main/java/com/teamsparker/android/ui/habit/viewmodel/HabitViewModel.kt b/app/src/main/java/com/teamsparker/android/ui/habit/viewmodel/HabitViewModel.kt
index 4f97ffae..c403cebc 100644
--- a/app/src/main/java/com/teamsparker/android/ui/habit/viewmodel/HabitViewModel.kt
+++ b/app/src/main/java/com/teamsparker/android/ui/habit/viewmodel/HabitViewModel.kt
@@ -9,6 +9,7 @@ import com.teamsparker.android.data.remote.entity.request.SendSparkRequest
import com.teamsparker.android.data.remote.entity.request.SetStatusRequest
import com.teamsparker.android.data.remote.entity.response.HabitRecord
import com.teamsparker.android.data.remote.entity.response.HabitResponse
+import com.teamsparker.android.data.remote.entity.response.HabitRoomTimeLine
import com.teamsparker.android.data.remote.repository.HabitRepository
import com.teamsparker.android.data.remote.service.HabitService
import com.teamsparker.android.data.remote.service.LeaveRoomService
@@ -16,11 +17,12 @@ import com.teamsparker.android.data.remote.service.SendSparkService
import com.teamsparker.android.data.remote.service.SetStatusService
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
+import timber.log.Timber
import javax.inject.Inject
@HiltViewModel
class HabitViewModel @Inject constructor(
- private val habitRepository: HabitRepository,
+ private val habitRepository: HabitRepository
) : ViewModel() {
private val habitService: HabitService = RetrofitBuilder.habitService
private val setStatusService: SetStatusService = RetrofitBuilder.setStatusService
@@ -45,6 +47,9 @@ class HabitViewModel @Inject constructor(
private val _exitSuccess = MutableLiveData()
val exitSuccess: LiveData = _exitSuccess
+ private var _timeLineList = MutableLiveData()
+ val timeLineList: LiveData = _timeLineList
+
private fun initIsLoading(isLoading: Boolean) {
_isLoading.value = isLoading
}
@@ -70,18 +75,22 @@ class HabitViewModel @Inject constructor(
val data = response.data ?: throw NullPointerException("습관방 통신 에러")
_habitInfo.postValue(data)
- _habitRecordList.postValue(mutableListOf().apply {
- add(data.myRecord)
- addAll(data.otherRecords.map {
- HabitRecord(
- nickname = it.nickname,
- profileImg = it.profileImg,
- recordId = it.recordId,
- status = it.status,
- userId = it.userId
+ _habitRecordList.postValue(
+ mutableListOf().apply {
+ add(data.myRecord)
+ addAll(
+ data.otherRecords.map {
+ HabitRecord(
+ nickname = it.nickname,
+ profileImg = it.profileImg,
+ recordId = it.recordId,
+ status = it.status,
+ userId = it.userId
+ )
+ }
)
- })
- })
+ }
+ )
initIsLoading(false)
}.onFailure { }
}
@@ -109,7 +118,6 @@ class HabitViewModel @Inject constructor(
SendSparkRequest(content, recordId)
)
}.onSuccess { _sendSuccess.value = true }
-
}
}
}
@@ -127,5 +135,18 @@ class HabitViewModel @Inject constructor(
fun setUserGuideDialogState(state: Boolean) {
habitRepository.setHabitUserGuideState(state)
}
-}
+ fun getHabitRoomTimeLine() {
+ viewModelScope.launch {
+ habitRepository.getHabitRoomTimeLine(
+ habitInfo.value?.roomId
+ ?: throw IllegalStateException("getHabitRoomTimeLine in viewModel")
+ )
+ .onSuccess {
+ _timeLineList.value = it
+ }.onFailure {
+ Timber.d(it)
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/teamsparker/android/util/BindingAdapters.kt b/app/src/main/java/com/teamsparker/android/util/BindingAdapters.kt
index 3bdc8683..5cd02443 100644
--- a/app/src/main/java/com/teamsparker/android/util/BindingAdapters.kt
+++ b/app/src/main/java/com/teamsparker/android/util/BindingAdapters.kt
@@ -4,6 +4,7 @@ import android.content.res.ColorStateList
import android.net.Uri
import android.text.InputFilter
import android.view.View
+import android.view.ViewGroup
import android.widget.*
import androidx.core.content.ContextCompat
import androidx.databinding.BindingAdapter
@@ -12,7 +13,7 @@ import com.airbnb.lottie.LottieAnimationView
import com.bumptech.glide.Glide
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.teamsparker.android.R
-import java.lang.IllegalArgumentException
+import kotlin.math.roundToInt
object BindingAdapters {
@JvmStatic
@@ -60,11 +61,9 @@ object BindingAdapters {
}
}
-
@JvmStatic
@BindingAdapter("setLeftBackground")
fun setLeftBackground(imageview: ImageView, leftDay: Int?) {
-
if (leftDay != null) {
imageview.setImageResource(
when {
@@ -81,7 +80,6 @@ object BindingAdapters {
}
}
-
@JvmStatic
@BindingAdapter("setLeftTicketColor")
fun setLeftTicketColor(textView: TextView, leftDay: Int?) {
@@ -170,7 +168,6 @@ object BindingAdapters {
@JvmStatic
@BindingAdapter("setLeftTicketComment")
fun setLeftTicketComment(textview: TextView, leftDay: Int?) {
-
val context = textview.context
if (leftDay != null) {
textview.text = when {
@@ -328,7 +325,7 @@ object BindingAdapters {
imageButton: ImageButton,
status: String?,
habitRestCount: Int?,
- habitUserLeftDay: Int?,
+ habitUserLeftDay: Int?
) {
if (status != null) {
if (habitRestCount != -1) {
@@ -351,18 +348,17 @@ object BindingAdapters {
}
)
imageButton.isEnabled = (
- when (status) {
- "DONE", "REST" -> false
- "NONE", "CONSIDER" -> true
- else -> throw IllegalStateException("bindingAdapter setSendSparkImg error")
- }
- )
+ when (status) {
+ "DONE", "REST" -> false
+ "NONE", "CONSIDER" -> true
+ else -> throw IllegalStateException("bindingAdapter setSendSparkImg error")
+ }
+ )
}
}
}
}
-
@JvmStatic
@BindingAdapter(value = ["certificationLeftDay", "certificationStatus"], requireAll = true)
fun setHabitCertificationButton(button: Button, leftDay: Int?, status: String?) {
@@ -379,12 +375,12 @@ object BindingAdapters {
}
)
button.isEnabled = (
- when (status) {
- "DONE", "REST" -> false
- "NONE", "CONSIDER" -> true
- else -> throw IllegalStateException("bindingAdapter setHabitCertificationButton error")
- }
- )
+ when (status) {
+ "DONE", "REST" -> false
+ "NONE", "CONSIDER" -> true
+ else -> throw IllegalStateException("bindingAdapter setHabitCertificationButton error")
+ }
+ )
}
}
}
@@ -397,13 +393,12 @@ object BindingAdapters {
imageview.visibility = View.GONE
} else {
imageview.visibility = (
- when (status) {
- "DONE", "REST" -> View.GONE
- "NONE", "CONSIDER" -> View.VISIBLE
- else -> throw IllegalStateException("bindingAdapter setHabitCertificationVisibility error")
- }
- )
-
+ when (status) {
+ "DONE", "REST" -> View.GONE
+ "NONE", "CONSIDER" -> View.VISIBLE
+ else -> throw IllegalStateException("bindingAdapter setHabitCertificationVisibility error")
+ }
+ )
}
}
}
@@ -411,7 +406,6 @@ object BindingAdapters {
@JvmStatic
@BindingAdapter("setCardOutLineColor")
fun setCardOutLineColor(view: View, leftDay: Int?) {
-
if (leftDay != null) {
view.setBackgroundResource(
when {
@@ -431,7 +425,6 @@ object BindingAdapters {
@JvmStatic
@BindingAdapter("setCardInLineColor")
fun setCardInLineColor(view: View, leftDay: Int?) {
-
if (leftDay != null) {
view.setBackgroundResource(
when {
@@ -451,7 +444,6 @@ object BindingAdapters {
@JvmStatic
@BindingAdapter("setProgressingCardSparkFlake")
fun setProgressingCardSparkFlake(imageview: ImageView, leftDay: Int?) {
-
if (leftDay != null) {
imageview.setBackgroundResource(
when {
@@ -471,7 +463,6 @@ object BindingAdapters {
@JvmStatic
@BindingAdapter("setIncompleteCardSparkFlake")
fun setIncompleteCardSparkFlake(imageview: ImageView, failDay: Int?) {
-
if (failDay != null) {
imageview.setBackgroundResource(
when {
@@ -515,7 +506,6 @@ object BindingAdapters {
.into(this)
}
"CONSIDER" -> {
-
}
}
}
@@ -593,11 +583,11 @@ object BindingAdapters {
@BindingAdapter("setIncompleteCardFailDay")
fun TextView.setIncompleteCardFailDay(failDay: Int) {
if (failDay == 1) {
- this.text = "${failDay} Day"
+ this.text = "$failDay Day"
} else if (failDay == 6) {
this.text = "D-day"
} else {
- this.text = "${failDay} Days"
+ this.text = "$failDay Days"
}
}
@@ -653,4 +643,80 @@ object BindingAdapters {
}
editText.filters = arrayOf(InputFilter.LengthFilter(max))
}
+
+ @JvmStatic
+ @BindingAdapter("TimeLineProfileState", "ProfileVisibleMargin", "ProfileGoneMargin")
+ fun TextView.timeLimeProfileMargin(
+ timeLineProfileState: Boolean,
+ profileVisibleMargin: Int,
+ profileGoneMargin: Int
+ ) {
+ if (timeLineProfileState) {
+ val tempMargin = (profileVisibleMargin * resources.displayMetrics.density).roundToInt()
+ val layoutParams = this.layoutParams as ViewGroup.MarginLayoutParams
+ layoutParams.topMargin = tempMargin
+ this.layoutParams = layoutParams
+ } else {
+ val tempMargin = (profileGoneMargin * resources.displayMetrics.density).roundToInt()
+ val layoutParams = this.layoutParams as ViewGroup.MarginLayoutParams
+ layoutParams.topMargin = tempMargin
+ this.layoutParams = layoutParams
+ }
+ }
+
+ @JvmStatic
+ @BindingAdapter("setFlameRoadMapTitle")
+ fun setFlameRoadMapTitle(textview: TextView, level: Int?) {
+ when (level) {
+ 1 -> textview.setText(R.string.flame_road_map_title_level_1)
+ 2 -> textview.setText(R.string.flame_road_map_title_level_2)
+ 3 -> textview.setText(R.string.flame_road_map_title_level_3)
+ 4 -> textview.setText(R.string.flame_road_map_title_level_4)
+ 5 -> textview.setText(R.string.flame_road_map_title_level_5)
+ 6 -> textview.setText(R.string.flame_road_map_title_level_6)
+ }
+ }
+
+ @JvmStatic
+ @BindingAdapter("setFlameRoadMapContnentDay", "setFlameRoadMapContnentLevel")
+ fun setFlameRoadMapContent(textview: TextView, leftDay: Int, level: Int?) {
+ when (level) {
+ 1 -> textview.setText(R.string.flame_road_map_content_level_1)
+ 2 -> {
+ if (leftDay <= 62) {
+ textview.setText(R.string.flame_road_map_content_level_2)
+ } else {
+ textview.setText(R.string.flame_road_map_content_not_ready_level_2)
+ }
+ }
+ 3 -> {
+ if (leftDay <= 58) {
+ textview.setText(R.string.flame_road_map_content_level_3)
+ } else {
+ textview.setText(R.string.flame_road_map_content_not_ready_level_3)
+ }
+ }
+ 4 -> {
+ if (leftDay <= 32) {
+ textview.setText(R.string.flame_road_map_content_level_4)
+ } else {
+ textview.setText(R.string.flame_road_map_content_not_ready_level_4)
+ }
+ }
+ 5 -> {
+ if (leftDay <= 6) {
+ textview.setText(R.string.flame_road_map_content_level_5)
+ } else {
+ textview.setText(R.string.flame_road_map_content_not_ready_level_5)
+ }
+ }
+ 6 -> {
+ if (leftDay == 0) {
+ textview.setText(R.string.flame_road_map_content_level_6)
+ } else {
+ textview.setText(R.string.flame_road_map_content_not_ready_level_6)
+ }
+ }
+ }
+ }
}
diff --git a/app/src/main/java/com/teamsparker/android/util/FirebaseLogUtil.kt b/app/src/main/java/com/teamsparker/android/util/FirebaseLogUtil.kt
index 0a9b82c5..44e57a92 100644
--- a/app/src/main/java/com/teamsparker/android/util/FirebaseLogUtil.kt
+++ b/app/src/main/java/com/teamsparker/android/util/FirebaseLogUtil.kt
@@ -32,10 +32,11 @@ object FirebaseLogUtil {
const val CLICK_CONSIDER_HABIT_ROOM = "click_CONSIDER_habit_room"
const val CLICK_HEART_FEED = "click_HEART_feed"
const val CLICK_CARD_MY_ROOM = "click_CARD_my_room"
+ const val CLICK_TIMELINE_NEW_HABIT_ROOM = "click_TIMELINE_NEW_habit_room"
+ const val CLICK_TIMELINE_NONE_HABIT_ROOM = "click_TIMELINE_NONE_habit_room"
private const val NOTIFICATION_OPEN = "notification_open_"
-
fun logScreenEvent(screenClass: String, screenName: String) {
Firebase.analytics.logEvent(FirebaseAnalytics.Event.SCREEN_VIEW) {
param(FirebaseAnalytics.Param.SCREEN_CLASS, screenClass)
diff --git a/app/src/main/java/com/teamsparker/android/util/Injector.kt b/app/src/main/java/com/teamsparker/android/util/Injector.kt
new file mode 100644
index 00000000..bb5d1ba0
--- /dev/null
+++ b/app/src/main/java/com/teamsparker/android/util/Injector.kt
@@ -0,0 +1,15 @@
+package com.teamsparker.android.util
+
+import android.content.SharedPreferences
+import dagger.hilt.EntryPoint
+import dagger.hilt.InstallIn
+import dagger.hilt.android.components.ActivityComponent
+
+sealed interface Injector {
+
+ @EntryPoint
+ @InstallIn(ActivityComponent::class)
+ interface SharedPreferencesInjector {
+ fun sharedPreferences(): SharedPreferences
+ }
+}
diff --git a/app/src/main/java/com/teamsparker/android/util/ItemDiffCallback.kt b/app/src/main/java/com/teamsparker/android/util/ItemDiffCallback.kt
new file mode 100644
index 00000000..5f66791e
--- /dev/null
+++ b/app/src/main/java/com/teamsparker/android/util/ItemDiffCallback.kt
@@ -0,0 +1,12 @@
+package com.teamsparker.android.util
+
+import androidx.recyclerview.widget.DiffUtil
+
+class ItemDiffCallback(
+ val onItemsTheSame: (T, T) -> Boolean,
+ val onContentsTheSame: (T, T) -> Boolean
+) : DiffUtil.ItemCallback() {
+ override fun areItemsTheSame(oldItem: T, newItem: T): Boolean = onItemsTheSame(oldItem, newItem)
+ override fun areContentsTheSame(oldItem: T, newItem: T): Boolean =
+ onContentsTheSame(oldItem, newItem)
+}
diff --git a/app/src/main/java/com/teamsparker/android/util/ext/ViewExtension.kt b/app/src/main/java/com/teamsparker/android/util/ext/ViewExtension.kt
new file mode 100644
index 00000000..2a3b2582
--- /dev/null
+++ b/app/src/main/java/com/teamsparker/android/util/ext/ViewExtension.kt
@@ -0,0 +1,17 @@
+package com.teamsparker.android.util.ext
+
+import android.view.View
+
+inline fun View.setOnSingleClickListener(
+ delay: Long = 500L,
+ crossinline block: (View) -> Unit
+) {
+ var previousClickedTime = 0L
+ setOnClickListener { view ->
+ val clickedTime = System.currentTimeMillis()
+ if (clickedTime - previousClickedTime >= delay) {
+ block(view)
+ previousClickedTime = clickedTime
+ }
+ }
+}
diff --git a/app/src/main/res/drawable-hdpi/bg_flame_road_map_gradation_left.png b/app/src/main/res/drawable-hdpi/bg_flame_road_map_gradation_left.png
new file mode 100644
index 00000000..90599f75
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/bg_flame_road_map_gradation_left.png differ
diff --git a/app/src/main/res/drawable-hdpi/bg_flame_road_map_gradation_right.png b/app/src/main/res/drawable-hdpi/bg_flame_road_map_gradation_right.png
new file mode 100644
index 00000000..3ddaa7f2
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/bg_flame_road_map_gradation_right.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_life_red_dot.png b/app/src/main/res/drawable-hdpi/ic_life_red_dot.png
new file mode 100644
index 00000000..a745159e
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_life_red_dot.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_time_line_blank_spark_flake_2.png b/app/src/main/res/drawable-hdpi/ic_time_line_blank_spark_flake_2.png
new file mode 100644
index 00000000..b210a0ae
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_time_line_blank_spark_flake_2.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_time_line_blank_spark_flake_3.png b/app/src/main/res/drawable-hdpi/ic_time_line_blank_spark_flake_3.png
new file mode 100644
index 00000000..1292fffa
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_time_line_blank_spark_flake_3.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_time_line_blank_spark_flake_4.png b/app/src/main/res/drawable-hdpi/ic_time_line_blank_spark_flake_4.png
new file mode 100644
index 00000000..1db18c86
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_time_line_blank_spark_flake_4.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_time_line_blank_spark_flake_5.png b/app/src/main/res/drawable-hdpi/ic_time_line_blank_spark_flake_5.png
new file mode 100644
index 00000000..d947d0a4
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_time_line_blank_spark_flake_5.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_time_line_blank_spark_flake_6.png b/app/src/main/res/drawable-hdpi/ic_time_line_blank_spark_flake_6.png
new file mode 100644
index 00000000..23f0c847
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_time_line_blank_spark_flake_6.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_time_line_spark_flake_1.png b/app/src/main/res/drawable-hdpi/ic_time_line_spark_flake_1.png
new file mode 100644
index 00000000..90dc816b
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_time_line_spark_flake_1.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_time_line_spark_flake_2.png b/app/src/main/res/drawable-hdpi/ic_time_line_spark_flake_2.png
new file mode 100644
index 00000000..a6951118
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_time_line_spark_flake_2.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_time_line_spark_flake_3.png b/app/src/main/res/drawable-hdpi/ic_time_line_spark_flake_3.png
new file mode 100644
index 00000000..f87c5e49
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_time_line_spark_flake_3.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_time_line_spark_flake_4.png b/app/src/main/res/drawable-hdpi/ic_time_line_spark_flake_4.png
new file mode 100644
index 00000000..97392f5e
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_time_line_spark_flake_4.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_time_line_spark_flake_5.png b/app/src/main/res/drawable-hdpi/ic_time_line_spark_flake_5.png
new file mode 100644
index 00000000..d343632f
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_time_line_spark_flake_5.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_time_line_spark_flake_6.png b/app/src/main/res/drawable-hdpi/ic_time_line_spark_flake_6.png
new file mode 100644
index 00000000..f57d48ec
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_time_line_spark_flake_6.png differ
diff --git a/app/src/main/res/drawable-mdpi/bg_flame_road_map_gradation_left.png b/app/src/main/res/drawable-mdpi/bg_flame_road_map_gradation_left.png
new file mode 100644
index 00000000..a80b2314
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/bg_flame_road_map_gradation_left.png differ
diff --git a/app/src/main/res/drawable-mdpi/bg_flame_road_map_gradation_right.png b/app/src/main/res/drawable-mdpi/bg_flame_road_map_gradation_right.png
new file mode 100644
index 00000000..b4634351
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/bg_flame_road_map_gradation_right.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_life_red_dot.png b/app/src/main/res/drawable-mdpi/ic_life_red_dot.png
new file mode 100644
index 00000000..574adce2
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_life_red_dot.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_time_line_blank_spark_flake_2.png b/app/src/main/res/drawable-mdpi/ic_time_line_blank_spark_flake_2.png
new file mode 100644
index 00000000..6e0d532c
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_time_line_blank_spark_flake_2.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_time_line_blank_spark_flake_3.png b/app/src/main/res/drawable-mdpi/ic_time_line_blank_spark_flake_3.png
new file mode 100644
index 00000000..4921ad8b
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_time_line_blank_spark_flake_3.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_time_line_blank_spark_flake_4.png b/app/src/main/res/drawable-mdpi/ic_time_line_blank_spark_flake_4.png
new file mode 100644
index 00000000..41855fe4
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_time_line_blank_spark_flake_4.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_time_line_blank_spark_flake_5.png b/app/src/main/res/drawable-mdpi/ic_time_line_blank_spark_flake_5.png
new file mode 100644
index 00000000..66499607
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_time_line_blank_spark_flake_5.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_time_line_blank_spark_flake_6.png b/app/src/main/res/drawable-mdpi/ic_time_line_blank_spark_flake_6.png
new file mode 100644
index 00000000..37c29201
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_time_line_blank_spark_flake_6.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_time_line_spark_flake_1.png b/app/src/main/res/drawable-mdpi/ic_time_line_spark_flake_1.png
new file mode 100644
index 00000000..8acd9509
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_time_line_spark_flake_1.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_time_line_spark_flake_2.png b/app/src/main/res/drawable-mdpi/ic_time_line_spark_flake_2.png
new file mode 100644
index 00000000..ead67828
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_time_line_spark_flake_2.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_time_line_spark_flake_3.png b/app/src/main/res/drawable-mdpi/ic_time_line_spark_flake_3.png
new file mode 100644
index 00000000..8af32701
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_time_line_spark_flake_3.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_time_line_spark_flake_4.png b/app/src/main/res/drawable-mdpi/ic_time_line_spark_flake_4.png
new file mode 100644
index 00000000..d0667c96
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_time_line_spark_flake_4.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_time_line_spark_flake_5.png b/app/src/main/res/drawable-mdpi/ic_time_line_spark_flake_5.png
new file mode 100644
index 00000000..0258d864
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_time_line_spark_flake_5.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_time_line_spark_flake_6.png b/app/src/main/res/drawable-mdpi/ic_time_line_spark_flake_6.png
new file mode 100644
index 00000000..036f8651
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_time_line_spark_flake_6.png differ
diff --git a/app/src/main/res/drawable-xhdpi/bg_flame_road_map_gradation_left.png b/app/src/main/res/drawable-xhdpi/bg_flame_road_map_gradation_left.png
new file mode 100644
index 00000000..46347f50
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/bg_flame_road_map_gradation_left.png differ
diff --git a/app/src/main/res/drawable-xhdpi/bg_flame_road_map_gradation_right.png b/app/src/main/res/drawable-xhdpi/bg_flame_road_map_gradation_right.png
new file mode 100644
index 00000000..d17524c1
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/bg_flame_road_map_gradation_right.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_life_red_dot.png b/app/src/main/res/drawable-xhdpi/ic_life_red_dot.png
new file mode 100644
index 00000000..97d2f67c
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_life_red_dot.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_time_line_blank_spark_flake_2.png b/app/src/main/res/drawable-xhdpi/ic_time_line_blank_spark_flake_2.png
new file mode 100644
index 00000000..9914515f
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_time_line_blank_spark_flake_2.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_time_line_blank_spark_flake_3.png b/app/src/main/res/drawable-xhdpi/ic_time_line_blank_spark_flake_3.png
new file mode 100644
index 00000000..5053508f
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_time_line_blank_spark_flake_3.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_time_line_blank_spark_flake_4.png b/app/src/main/res/drawable-xhdpi/ic_time_line_blank_spark_flake_4.png
new file mode 100644
index 00000000..2c2dc033
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_time_line_blank_spark_flake_4.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_time_line_blank_spark_flake_5.png b/app/src/main/res/drawable-xhdpi/ic_time_line_blank_spark_flake_5.png
new file mode 100644
index 00000000..9b3b4a48
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_time_line_blank_spark_flake_5.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_time_line_blank_spark_flake_6.png b/app/src/main/res/drawable-xhdpi/ic_time_line_blank_spark_flake_6.png
new file mode 100644
index 00000000..5e56b9f3
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_time_line_blank_spark_flake_6.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_time_line_spark_flake_1.png b/app/src/main/res/drawable-xhdpi/ic_time_line_spark_flake_1.png
new file mode 100644
index 00000000..a2067cb3
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_time_line_spark_flake_1.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_time_line_spark_flake_2.png b/app/src/main/res/drawable-xhdpi/ic_time_line_spark_flake_2.png
new file mode 100644
index 00000000..39bacdf3
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_time_line_spark_flake_2.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_time_line_spark_flake_3.png b/app/src/main/res/drawable-xhdpi/ic_time_line_spark_flake_3.png
new file mode 100644
index 00000000..d5b242ae
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_time_line_spark_flake_3.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_time_line_spark_flake_4.png b/app/src/main/res/drawable-xhdpi/ic_time_line_spark_flake_4.png
new file mode 100644
index 00000000..31fb854f
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_time_line_spark_flake_4.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_time_line_spark_flake_5.png b/app/src/main/res/drawable-xhdpi/ic_time_line_spark_flake_5.png
new file mode 100644
index 00000000..98e25d6a
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_time_line_spark_flake_5.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_time_line_spark_flake_6.png b/app/src/main/res/drawable-xhdpi/ic_time_line_spark_flake_6.png
new file mode 100644
index 00000000..08e287f5
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_time_line_spark_flake_6.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/bg_flame_road_map_gradation_left.png b/app/src/main/res/drawable-xxhdpi/bg_flame_road_map_gradation_left.png
new file mode 100644
index 00000000..2c115316
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/bg_flame_road_map_gradation_left.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/bg_flame_road_map_gradation_right.png b/app/src/main/res/drawable-xxhdpi/bg_flame_road_map_gradation_right.png
new file mode 100644
index 00000000..b24b1cd8
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/bg_flame_road_map_gradation_right.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_life_red_dot.png b/app/src/main/res/drawable-xxhdpi/ic_life_red_dot.png
new file mode 100644
index 00000000..ef9c881f
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_life_red_dot.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_time_line_blank_spark_flake_2.png b/app/src/main/res/drawable-xxhdpi/ic_time_line_blank_spark_flake_2.png
new file mode 100644
index 00000000..be428a2f
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_time_line_blank_spark_flake_2.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_time_line_blank_spark_flake_3.png b/app/src/main/res/drawable-xxhdpi/ic_time_line_blank_spark_flake_3.png
new file mode 100644
index 00000000..67ad8300
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_time_line_blank_spark_flake_3.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_time_line_blank_spark_flake_4.png b/app/src/main/res/drawable-xxhdpi/ic_time_line_blank_spark_flake_4.png
new file mode 100644
index 00000000..e1a1bb1f
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_time_line_blank_spark_flake_4.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_time_line_blank_spark_flake_5.png b/app/src/main/res/drawable-xxhdpi/ic_time_line_blank_spark_flake_5.png
new file mode 100644
index 00000000..c555d8bf
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_time_line_blank_spark_flake_5.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_time_line_blank_spark_flake_6.png b/app/src/main/res/drawable-xxhdpi/ic_time_line_blank_spark_flake_6.png
new file mode 100644
index 00000000..97efc656
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_time_line_blank_spark_flake_6.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_time_line_spark_flake_1.png b/app/src/main/res/drawable-xxhdpi/ic_time_line_spark_flake_1.png
new file mode 100644
index 00000000..97f1dcf1
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_time_line_spark_flake_1.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_time_line_spark_flake_2.png b/app/src/main/res/drawable-xxhdpi/ic_time_line_spark_flake_2.png
new file mode 100644
index 00000000..b29feeba
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_time_line_spark_flake_2.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_time_line_spark_flake_3.png b/app/src/main/res/drawable-xxhdpi/ic_time_line_spark_flake_3.png
new file mode 100644
index 00000000..ad89d11d
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_time_line_spark_flake_3.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_time_line_spark_flake_4.png b/app/src/main/res/drawable-xxhdpi/ic_time_line_spark_flake_4.png
new file mode 100644
index 00000000..8acbbab5
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_time_line_spark_flake_4.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_time_line_spark_flake_5.png b/app/src/main/res/drawable-xxhdpi/ic_time_line_spark_flake_5.png
new file mode 100644
index 00000000..0e32c576
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_time_line_spark_flake_5.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_time_line_spark_flake_6.png b/app/src/main/res/drawable-xxhdpi/ic_time_line_spark_flake_6.png
new file mode 100644
index 00000000..118c3c50
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_time_line_spark_flake_6.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/bg_flame_road_map_gradation_left.png b/app/src/main/res/drawable-xxxhdpi/bg_flame_road_map_gradation_left.png
new file mode 100644
index 00000000..4498f401
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/bg_flame_road_map_gradation_left.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/bg_flame_road_map_gradation_right.png b/app/src/main/res/drawable-xxxhdpi/bg_flame_road_map_gradation_right.png
new file mode 100644
index 00000000..1a48a683
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/bg_flame_road_map_gradation_right.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_life_red_dot.png b/app/src/main/res/drawable-xxxhdpi/ic_life_red_dot.png
new file mode 100644
index 00000000..9421c796
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_life_red_dot.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_time_line_blank_spark_flake_2.png b/app/src/main/res/drawable-xxxhdpi/ic_time_line_blank_spark_flake_2.png
new file mode 100644
index 00000000..fdb6c384
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_time_line_blank_spark_flake_2.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_time_line_blank_spark_flake_3.png b/app/src/main/res/drawable-xxxhdpi/ic_time_line_blank_spark_flake_3.png
new file mode 100644
index 00000000..9acc3379
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_time_line_blank_spark_flake_3.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_time_line_blank_spark_flake_4.png b/app/src/main/res/drawable-xxxhdpi/ic_time_line_blank_spark_flake_4.png
new file mode 100644
index 00000000..4222c1a7
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_time_line_blank_spark_flake_4.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_time_line_blank_spark_flake_5.png b/app/src/main/res/drawable-xxxhdpi/ic_time_line_blank_spark_flake_5.png
new file mode 100644
index 00000000..ccf6983c
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_time_line_blank_spark_flake_5.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_time_line_blank_spark_flake_6.png b/app/src/main/res/drawable-xxxhdpi/ic_time_line_blank_spark_flake_6.png
new file mode 100644
index 00000000..09ec7ff0
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_time_line_blank_spark_flake_6.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_time_line_spark_flake_1.png b/app/src/main/res/drawable-xxxhdpi/ic_time_line_spark_flake_1.png
new file mode 100644
index 00000000..65403638
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_time_line_spark_flake_1.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_time_line_spark_flake_2.png b/app/src/main/res/drawable-xxxhdpi/ic_time_line_spark_flake_2.png
new file mode 100644
index 00000000..659b6966
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_time_line_spark_flake_2.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_time_line_spark_flake_3.png b/app/src/main/res/drawable-xxxhdpi/ic_time_line_spark_flake_3.png
new file mode 100644
index 00000000..7510d243
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_time_line_spark_flake_3.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_time_line_spark_flake_4.png b/app/src/main/res/drawable-xxxhdpi/ic_time_line_spark_flake_4.png
new file mode 100644
index 00000000..fa5018e5
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_time_line_spark_flake_4.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_time_line_spark_flake_5.png b/app/src/main/res/drawable-xxxhdpi/ic_time_line_spark_flake_5.png
new file mode 100644
index 00000000..50cc4292
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_time_line_spark_flake_5.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_time_line_spark_flake_6.png b/app/src/main/res/drawable-xxxhdpi/ic_time_line_spark_flake_6.png
new file mode 100644
index 00000000..2409759b
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_time_line_spark_flake_6.png differ
diff --git a/app/src/main/res/layout/activity_habit.xml b/app/src/main/res/layout/activity_habit.xml
index 79e78baf..433218d2 100644
--- a/app/src/main/res/layout/activity_habit.xml
+++ b/app/src/main/res/layout/activity_habit.xml
@@ -142,6 +142,17 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/btn_habit_more" />
+
+
+
+
+
+
+ app:layout_constraintTop_toBottomOf="@id/view_habit_more_divider3" />
+ app:layout_constraintTop_toBottomOf="@id/view_habit_more_divider4" />
diff --git a/app/src/main/res/layout/bottom_sheet_habit_time_line.xml b/app/src/main/res/layout/bottom_sheet_habit_time_line.xml
new file mode 100644
index 00000000..aaba6817
--- /dev/null
+++ b/app/src/main/res/layout/bottom_sheet_habit_time_line.xml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_flame_road_map_dialog.xml b/app/src/main/res/layout/fragment_flame_road_map_dialog.xml
new file mode 100644
index 00000000..d06d74cb
--- /dev/null
+++ b/app/src/main/res/layout/fragment_flame_road_map_dialog.xml
@@ -0,0 +1,101 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_level1.xml b/app/src/main/res/layout/fragment_level1.xml
new file mode 100644
index 00000000..f2c7dca7
--- /dev/null
+++ b/app/src/main/res/layout/fragment_level1.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_level2.xml b/app/src/main/res/layout/fragment_level2.xml
new file mode 100644
index 00000000..c7772030
--- /dev/null
+++ b/app/src/main/res/layout/fragment_level2.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_level3.xml b/app/src/main/res/layout/fragment_level3.xml
new file mode 100644
index 00000000..56e413c4
--- /dev/null
+++ b/app/src/main/res/layout/fragment_level3.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_level4.xml b/app/src/main/res/layout/fragment_level4.xml
new file mode 100644
index 00000000..1523e6d6
--- /dev/null
+++ b/app/src/main/res/layout/fragment_level4.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_level5.xml b/app/src/main/res/layout/fragment_level5.xml
new file mode 100644
index 00000000..e6a81ce5
--- /dev/null
+++ b/app/src/main/res/layout/fragment_level5.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_level6.xml b/app/src/main/res/layout/fragment_level6.xml
new file mode 100644
index 00000000..8d4fbb46
--- /dev/null
+++ b/app/src/main/res/layout/fragment_level6.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/item_habit_time_line.xml b/app/src/main/res/layout/item_habit_time_line.xml
new file mode 100644
index 00000000..c08455b4
--- /dev/null
+++ b/app/src/main/res/layout/item_habit_time_line.xml
@@ -0,0 +1,96 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values/dimen.xml b/app/src/main/res/values/dimen.xml
index 2fdf817b..9446b721 100644
--- a/app/src/main/res/values/dimen.xml
+++ b/app/src/main/res/values/dimen.xml
@@ -2,4 +2,6 @@
12dp
36dp
-
\ No newline at end of file
+ 80dp
+ 120dp
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index d3122f45..6f72ea92 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -210,6 +210,7 @@
아직 습관이 완료되지 않았어요.\n그래도 나가려면 ‘방 이름’을 입력해주세요.
습관방 이용 팁
방 나가기
+ 불꽃 결정 레벨
오늘도 꺼지지 않는 스파크,
@@ -394,5 +395,27 @@
Oops...
확인했어요
+
+ 생명 타임라인
+
+
+ Level 1
+ Level 2
+ Level 3
+ Level 4
+ Level 5
+ Level 6
+ 새로운 습관 시작!\n첫 번째 불꽃 결정을 얻었어요.
+ 3일 동안 열심히 달렸네요!\n두 번째 불꽃 결정을 얻었어요.
+ 7일 동안 열심히 달렸네요!\n세 번째 불꽃 결정을 얻었어요.
+ 33일 동안 열심히 달렸네요!\n네 번째 불꽃 결정을 얻었어요.
+ 마지막 7일 남았네요!\n다섯 번째 불꽃 결정을 얻었어요.
+ 드디어 마지막 날이네요!\n여섯 번째 불꽃 결정을 얻었어요.
+ 3일을 달성하면 얻을 수 있어요!
+ 7일을 달성하면 얻을 수 있어요!
+ 33일을 달성하면 얻을 수 있어요!
+ 마지막 7일 남았을 때 얻을 수 있어요!
+ 마지막 날에 얻을 수 있어요!
+
diff --git a/build.gradle b/build.gradle
index 5fbe6001..28e3ba48 100644
--- a/build.gradle
+++ b/build.gradle
@@ -7,7 +7,7 @@ buildscript {
}
dependencies {
classpath "com.android.tools.build:gradle:7.0.2"
- classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10'
+ classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.21'
// Hilt
classpath 'com.google.dagger:hilt-android-gradle-plugin:2.38.1'