diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 4fdda6a..487f2d2 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -2,6 +2,8 @@ plugins { id("com.android.application") id("org.jetbrains.kotlin.android") kotlin("plugin.serialization") version "2.0.10" + id("com.google.devtools.ksp") + id("com.google.dagger.hilt.android") } android { @@ -54,6 +56,9 @@ android { dependencies { + implementation("com.google.dagger:hilt-android:2.51.1") + ksp("com.google.dagger:hilt-android-compiler:2.51.1") + implementation("androidx.core:core-ktx:1.13.1") implementation("androidx.appcompat:appcompat:1.7.0") implementation("com.google.android.material:material:1.12.0") @@ -64,12 +69,12 @@ dependencies { implementation("androidx.navigation:navigation-ui-ktx:2.7.7") implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.4") implementation("androidx.activity:activity-compose:1.9.1") - implementation(platform("androidx.compose:compose-bom:2023.08.00")) + implementation(platform("androidx.compose:compose-bom:2024.11.00")) implementation("androidx.compose.ui:ui-graphics") testImplementation("junit:junit:4.13.2") androidTestImplementation("androidx.test.ext:junit:1.2.1") androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1") - androidTestImplementation(platform("androidx.compose:compose-bom:2023.08.00")) + androidTestImplementation(platform("androidx.compose:compose-bom:2024.11.00")) val composeBom = platform("androidx.compose:compose-bom:2024.06.00") implementation(composeBom) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 2ab164d..bbd44e1 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -5,6 +5,7 @@ + + + - + + diff --git a/app/src/main/java/com/example/com_us/MainActivity.kt b/app/src/main/java/com/example/com_us/MainActivity.kt index 92d94ef..ac4f7eb 100644 --- a/app/src/main/java/com/example/com_us/MainActivity.kt +++ b/app/src/main/java/com/example/com_us/MainActivity.kt @@ -9,7 +9,9 @@ import androidx.navigation.ui.AppBarConfiguration import androidx.navigation.ui.setupActionBarWithNavController import androidx.navigation.ui.setupWithNavController import com.example.com_us.databinding.ActivityMainBinding +import dagger.hilt.android.AndroidEntryPoint +@AndroidEntryPoint class MainActivity : AppCompatActivity() { private lateinit var binding: ActivityMainBinding diff --git a/app/src/main/java/com/example/com_us/base/AppInterceptor.kt b/app/src/main/java/com/example/com_us/base/AppInterceptor.kt new file mode 100644 index 0000000..05b496f --- /dev/null +++ b/app/src/main/java/com/example/com_us/base/AppInterceptor.kt @@ -0,0 +1,28 @@ +package com.example.com_us.base + +import android.util.Log +import okhttp3.Interceptor +import okhttp3.Response +import java.io.IOException + + class AppInterceptor(private val accessToken: String) : Interceptor { + private val contentType = "application/json" + @Throws(IOException::class) + override fun intercept(chain: Interceptor.Chain) : Response = with(chain) { + val newRequest = request().newBuilder() + .addHeader("Content-Type", contentType) + .addHeader("Authorization", accessToken) + .build() + + val response = proceed(newRequest) + when (response.code) { + 401, 404 -> { + Log.d("API FAILURE", response.toString()) + } + 500 -> { + Log.d("API FAILURE 500", response.toString()) + } + } + response + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/com_us/base/Application.kt b/app/src/main/java/com/example/com_us/base/Application.kt new file mode 100644 index 0000000..209fd62 --- /dev/null +++ b/app/src/main/java/com/example/com_us/base/Application.kt @@ -0,0 +1,8 @@ +package com.example.com_us.base + +import android.app.Application +import dagger.hilt.android.HiltAndroidApp + +@HiltAndroidApp +class Application : Application() { +} \ No newline at end of file diff --git a/app/src/main/java/com/example/com_us/base/data/BaseResponse.kt b/app/src/main/java/com/example/com_us/base/data/BaseResponse.kt new file mode 100644 index 0000000..a2c6fac --- /dev/null +++ b/app/src/main/java/com/example/com_us/base/data/BaseResponse.kt @@ -0,0 +1,26 @@ +package com.example.com_us.base.data + +import kotlinx.serialization.Serializable + +@Serializable +data class BaseResponse( + val status: Int, + val message: String, + val data: T? = null, +) + +@Serializable +data class BaseResponseNoData( + val status: Int, + val message: String, +) + +fun BaseResponse.toResult(): Result { + return when{ + status == 200 && data != null -> Result.success(data) + status == 200 && data == null -> Result.failure(NetworkError.NullDataError()) + status == 400 or 401 -> Result.failure(NetworkError.ApiError(status, message)) + status == 500 -> Result.failure(NetworkError.ApiError(status, message)) + else -> Result.failure(NetworkError.ApiError(status, message)) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/com_us/base/data/NetworkError.kt b/app/src/main/java/com/example/com_us/base/data/NetworkError.kt new file mode 100644 index 0000000..9d3a18b --- /dev/null +++ b/app/src/main/java/com/example/com_us/base/data/NetworkError.kt @@ -0,0 +1,17 @@ +package com.example.com_us.base.data + +sealed class NetworkError : Exception(){ + data class ApiError( + val statusCode : Int, + override val message : String + ) : NetworkError() + + data class NetworkException( + override val cause : Throwable, + ) : NetworkError() + + data class NullDataError( + override val message : String = "Data is null" + ) : NetworkError() + +} diff --git a/app/src/main/java/com/example/com_us/base/di/ServiceModule.kt b/app/src/main/java/com/example/com_us/base/di/ServiceModule.kt new file mode 100644 index 0000000..ad5b670 --- /dev/null +++ b/app/src/main/java/com/example/com_us/base/di/ServiceModule.kt @@ -0,0 +1,22 @@ +package com.example.com_us.base.di + +import android.content.Context +import com.example.com_us.R +import com.example.com_us.data.di.BaseUrl +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton +import dagger.Provides +import dagger.hilt.android.qualifiers.ApplicationContext + +@Module +@InstallIn(SingletonComponent::class) +object ServiceModule { + + @Provides + @BaseUrl + fun provideBaseUrl( + @ApplicationContext context: Context + ) : String = context.getString(R.string.baseUrl) +} \ No newline at end of file diff --git a/app/src/main/java/com/example/com_us/data/datasource/HomeDataSource.kt b/app/src/main/java/com/example/com_us/data/datasource/HomeDataSource.kt deleted file mode 100644 index 75cada0..0000000 --- a/app/src/main/java/com/example/com_us/data/datasource/HomeDataSource.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.example.com_us.data.datasource - -import com.example.com_us.data.response.BaseResponse -import com.example.com_us.data.response.home.ResponseHomeDataDto - -interface HomeDataSource { - suspend fun getHomeData() : BaseResponse -} \ No newline at end of file diff --git a/app/src/main/java/com/example/com_us/data/datasource/HomeRemoteDataSource.kt b/app/src/main/java/com/example/com_us/data/datasource/HomeRemoteDataSource.kt deleted file mode 100644 index c43f617..0000000 --- a/app/src/main/java/com/example/com_us/data/datasource/HomeRemoteDataSource.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.example.com_us.data.datasource - -import com.example.com_us.data.response.BaseResponse -import com.example.com_us.data.response.home.ResponseHomeDataDto -import com.example.com_us.data.service.HomeService - -class HomeRemoteDataSource(private val homeService : HomeService) : HomeDataSource { - override suspend fun getHomeData(): BaseResponse { - return homeService.getHomeData() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/example/com_us/data/datasource/ProfileDataSource.kt b/app/src/main/java/com/example/com_us/data/datasource/ProfileDataSource.kt deleted file mode 100644 index 536c81a..0000000 --- a/app/src/main/java/com/example/com_us/data/datasource/ProfileDataSource.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.example.com_us.data.datasource - -import com.example.com_us.data.response.BaseResponse -import com.example.com_us.data.response.question.ResponseProfileDto - -interface ProfileDataSource { - suspend fun getProfileData() : BaseResponse -} \ No newline at end of file diff --git a/app/src/main/java/com/example/com_us/data/datasource/ProfileRemoteDataSource.kt b/app/src/main/java/com/example/com_us/data/datasource/ProfileRemoteDataSource.kt deleted file mode 100644 index f993e79..0000000 --- a/app/src/main/java/com/example/com_us/data/datasource/ProfileRemoteDataSource.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.example.com_us.data.datasource - -import com.example.com_us.data.response.BaseResponse -import com.example.com_us.data.response.question.ResponseProfileDto -import com.example.com_us.data.service.ProfileService - -class ProfileRemoteDataSource(private val profileService: ProfileService) : ProfileDataSource { - override suspend fun getProfileData(): BaseResponse { - return profileService.getProfileData() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/example/com_us/data/datasource/QuestionDataSource.kt b/app/src/main/java/com/example/com_us/data/datasource/QuestionDataSource.kt deleted file mode 100644 index 91096a3..0000000 --- a/app/src/main/java/com/example/com_us/data/datasource/QuestionDataSource.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.example.com_us.data.datasource - -import com.example.com_us.data.request.question.RequestAnswerDto -import com.example.com_us.data.response.BaseResponse -import com.example.com_us.data.response.BaseResponseNoData -import com.example.com_us.data.response.question.ResponseAnswerDetailDto -import com.example.com_us.data.response.question.ResponseAnswerDetailWithDateDto -import com.example.com_us.data.response.question.ResponsePreviousAnswerDto -import com.example.com_us.data.response.question.ResponseQuestionDetailDto -import com.example.com_us.data.response.question.ResponseQuestionDto - -interface QuestionDataSource { - suspend fun getQuestionListByCate(category: String) : BaseResponse> - suspend fun getQuestionDetail(questionId: Long) : BaseResponse - suspend fun getAnswerDetail(answer: String) : BaseResponse> - suspend fun postAnswer(body: RequestAnswerDto) : BaseResponse - suspend fun getPreviousAnswer(questionId: Long) : BaseResponse -} \ No newline at end of file diff --git a/app/src/main/java/com/example/com_us/data/datasource/QuestionRemoteDataSource.kt b/app/src/main/java/com/example/com_us/data/datasource/QuestionRemoteDataSource.kt deleted file mode 100644 index ba0f669..0000000 --- a/app/src/main/java/com/example/com_us/data/datasource/QuestionRemoteDataSource.kt +++ /dev/null @@ -1,29 +0,0 @@ -package com.example.com_us.data.datasource - -import com.example.com_us.data.request.question.RequestAnswerDto -import com.example.com_us.data.response.BaseResponse -import com.example.com_us.data.response.BaseResponseNoData -import com.example.com_us.data.response.question.ResponseAnswerDetailDto -import com.example.com_us.data.response.question.ResponseAnswerDetailWithDateDto -import com.example.com_us.data.response.question.ResponsePreviousAnswerDto -import com.example.com_us.data.response.question.ResponseQuestionDetailDto -import com.example.com_us.data.response.question.ResponseQuestionDto -import com.example.com_us.data.service.QuestionService - -class QuestionRemoteDataSource(private val questionService : QuestionService) : QuestionDataSource { - override suspend fun getQuestionListByCate(category: String): BaseResponse> { - return questionService.getQuestionListByCate(category) - } - override suspend fun getQuestionDetail(questionId: Long): BaseResponse { - return questionService.getQuestionDetail(questionId) - } - override suspend fun getAnswerDetail(answer: String): BaseResponse> { - return questionService.getAnswerDetail(answer) - } - override suspend fun postAnswer(body: RequestAnswerDto): BaseResponse { - return questionService.postAnswer(body) - } - override suspend fun getPreviousAnswer(questionId: Long): BaseResponse { - return questionService.getPreviousAnswer(questionId) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/example/com_us/data/default_repository/DefaultHomeRepository.kt b/app/src/main/java/com/example/com_us/data/default_repository/DefaultHomeRepository.kt new file mode 100644 index 0000000..3b9d165 --- /dev/null +++ b/app/src/main/java/com/example/com_us/data/default_repository/DefaultHomeRepository.kt @@ -0,0 +1,22 @@ +package com.example.com_us.data.default_repository + +import com.example.com_us.base.data.NetworkError +import com.example.com_us.data.model.home.ResponseHomeDataDto +import com.example.com_us.data.repository.HomeRepository +import com.example.com_us.data.default_source.DefaultHomeDataSource +import com.example.com_us.base.data.toResult +import javax.inject.Inject + + + +class DefaultHomeRepository @Inject constructor( + private val defaultHomeDataSource: DefaultHomeDataSource +) : HomeRepository { + override suspend fun getHomeData(): Result { + return try { + defaultHomeDataSource.getHomeData().toResult() + } catch (e: Exception) { + Result.failure(NetworkError.NetworkException(e)) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/com_us/data/default_repository/DefaultProfileRepository.kt b/app/src/main/java/com/example/com_us/data/default_repository/DefaultProfileRepository.kt new file mode 100644 index 0000000..0c2ac71 --- /dev/null +++ b/app/src/main/java/com/example/com_us/data/default_repository/DefaultProfileRepository.kt @@ -0,0 +1,20 @@ +package com.example.com_us.data.default_repository + +import com.example.com_us.base.data.NetworkError +import com.example.com_us.data.model.question.response.question.ResponseProfileDto +import com.example.com_us.data.repository.ProfileRepository +import com.example.com_us.data.default_source.DefaultProfileDataSource +import com.example.com_us.base.data.toResult +import javax.inject.Inject + +class DefaultProfileRepository @Inject constructor( + private val profileRemoteDataSource: DefaultProfileDataSource +) : ProfileRepository { + override suspend fun getProfileData(): Result { + return try { + profileRemoteDataSource.getProfileData().toResult() + }catch (e: Exception){ + Result.failure(NetworkError.NetworkException(e)) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/com_us/data/default_repository/DefaultQuestionRepository.kt b/app/src/main/java/com/example/com_us/data/default_repository/DefaultQuestionRepository.kt new file mode 100644 index 0000000..7b5a0fd --- /dev/null +++ b/app/src/main/java/com/example/com_us/data/default_repository/DefaultQuestionRepository.kt @@ -0,0 +1,53 @@ +package com.example.com_us.data.default_repository + +import com.example.com_us.base.data.NetworkError +import com.example.com_us.data.model.question.request.RequestAnswerRequest +import com.example.com_us.data.model.question.response.question.ResponseAnswerDetailDto +import com.example.com_us.data.model.question.response.question.ResponseAnswerDetailWithDateDto +import com.example.com_us.data.model.question.response.question.ResponsePreviousAnswerDto +import com.example.com_us.data.model.question.response.question.ResponseQuestionDetailDto +import com.example.com_us.data.model.question.response.question.ResponseQuestionDto +import com.example.com_us.data.repository.QuestionRepository +import com.example.com_us.data.default_source.DefaultQuestionDataSource +import com.example.com_us.base.data.toResult +import javax.inject.Inject + +class DefaultQuestionRepository @Inject constructor( + private val defaultQuestionDataSource: DefaultQuestionDataSource +) : QuestionRepository { + override suspend fun getQuestionListByCate(category: String): Result> { + return try{ + defaultQuestionDataSource.getQuestionListByCate(category).toResult() + } catch (e: Exception) { + Result.failure(NetworkError.NetworkException(e)) + } + } + override suspend fun getQuestionDetail(questionId: Long): Result { + return try{ + defaultQuestionDataSource.getQuestionDetail(questionId).toResult() + } catch (e: Exception) { + Result.failure(NetworkError.NetworkException(e)) + } + } + override suspend fun getAnswerDetail(answer: String): Result> { + return try{ + defaultQuestionDataSource.getAnswerDetail(answer).toResult() + } catch (e: Exception) { + Result.failure(NetworkError.NetworkException(e)) + } + } + override suspend fun postAnswer(body: RequestAnswerRequest): Result { + return try{ + defaultQuestionDataSource.postAnswer(body).toResult() + } catch (e: Exception) { + Result.failure(NetworkError.NetworkException(e)) + } + } + override suspend fun getPreviousAnswer(questionId: Long ): Result { + return try{ + defaultQuestionDataSource.getPreviousAnswer(questionId).toResult() + } catch (e: Exception) { + Result.failure(NetworkError.NetworkException(e)) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/com_us/data/default_source/DefaultHomeDataSource.kt b/app/src/main/java/com/example/com_us/data/default_source/DefaultHomeDataSource.kt new file mode 100644 index 0000000..ec2154b --- /dev/null +++ b/app/src/main/java/com/example/com_us/data/default_source/DefaultHomeDataSource.kt @@ -0,0 +1,13 @@ +package com.example.com_us.data.default_source + +import com.example.com_us.base.data.BaseResponse +import com.example.com_us.data.model.home.ResponseHomeDataDto +import com.example.com_us.data.service.HomeService +import com.example.com_us.data.source.HomeDataSource +import javax.inject.Inject + +class DefaultHomeDataSource @Inject constructor(private val homeService : HomeService) : HomeDataSource { + override suspend fun getHomeData(): BaseResponse { + return homeService.getHomeData() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/com_us/data/default_source/DefaultProfileDataSource.kt b/app/src/main/java/com/example/com_us/data/default_source/DefaultProfileDataSource.kt new file mode 100644 index 0000000..0c45041 --- /dev/null +++ b/app/src/main/java/com/example/com_us/data/default_source/DefaultProfileDataSource.kt @@ -0,0 +1,13 @@ +package com.example.com_us.data.default_source + +import com.example.com_us.base.data.BaseResponse +import com.example.com_us.data.model.question.response.question.ResponseProfileDto +import com.example.com_us.data.service.ProfileService +import com.example.com_us.data.source.ProfileDataSource +import javax.inject.Inject + +class DefaultProfileDataSource @Inject constructor(private val profileService: ProfileService) : ProfileDataSource { + override suspend fun getProfileData(): BaseResponse { + return profileService.getProfileData() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/com_us/data/default_source/DefaultQuestionDataSource.kt b/app/src/main/java/com/example/com_us/data/default_source/DefaultQuestionDataSource.kt new file mode 100644 index 0000000..676b120 --- /dev/null +++ b/app/src/main/java/com/example/com_us/data/default_source/DefaultQuestionDataSource.kt @@ -0,0 +1,31 @@ +package com.example.com_us.data.default_source + +import com.example.com_us.data.model.question.request.RequestAnswerRequest +import com.example.com_us.base.data.BaseResponse +import com.example.com_us.data.model.question.response.question.ResponseAnswerDetailDto +import com.example.com_us.data.model.question.response.question.ResponseAnswerDetailWithDateDto +import com.example.com_us.data.model.question.response.question.ResponsePreviousAnswerDto +import com.example.com_us.data.model.question.response.question.ResponseQuestionDetailDto +import com.example.com_us.data.model.question.response.question.ResponseQuestionDto +import com.example.com_us.data.service.QuestionService +import com.example.com_us.data.source.QuestionDataSource +import javax.inject.Inject + +class DefaultQuestionDataSource @Inject constructor(private val questionService : QuestionService) : + QuestionDataSource { + override suspend fun getQuestionListByCate(category: String): BaseResponse> { + return questionService.getQuestionListByCate(category) + } + override suspend fun getQuestionDetail(questionId: Long): BaseResponse { + return questionService.getQuestionDetail(questionId) + } + override suspend fun getAnswerDetail(answer: String): BaseResponse> { + return questionService.getAnswerDetail(answer) + } + override suspend fun postAnswer(body: RequestAnswerRequest): BaseResponse { + return questionService.postAnswer(body) + } + override suspend fun getPreviousAnswer(questionId: Long): BaseResponse { + return questionService.getPreviousAnswer(questionId) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/com_us/data/di/DatasourceModule.kt b/app/src/main/java/com/example/com_us/data/di/DatasourceModule.kt new file mode 100644 index 0000000..bf43aa8 --- /dev/null +++ b/app/src/main/java/com/example/com_us/data/di/DatasourceModule.kt @@ -0,0 +1,28 @@ +package com.example.com_us.data.di + +import com.example.com_us.data.default_source.DefaultHomeDataSource +import com.example.com_us.data.default_source.DefaultProfileDataSource +import com.example.com_us.data.default_source.DefaultQuestionDataSource +import com.example.com_us.data.source.HomeDataSource +import com.example.com_us.data.source.ProfileDataSource +import com.example.com_us.data.source.QuestionDataSource +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent + +@InstallIn(SingletonComponent::class) +@Module +interface DatasourceModule { + + @Binds + fun provideHomeDataSource(defaultDataSource : DefaultHomeDataSource) : HomeDataSource + + @Binds + fun provideProfileDataSource(defaultDataSource : DefaultProfileDataSource) : ProfileDataSource + + @Binds + fun provideQuestionDataSource(defaultDataSource : DefaultQuestionDataSource) : QuestionDataSource + + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/com_us/data/ApiClient.kt b/app/src/main/java/com/example/com_us/data/di/NetworkModule.kt similarity index 57% rename from app/src/main/java/com/example/com_us/data/ApiClient.kt rename to app/src/main/java/com/example/com_us/data/di/NetworkModule.kt index 9d70897..f2c287e 100644 --- a/app/src/main/java/com/example/com_us/data/ApiClient.kt +++ b/app/src/main/java/com/example/com_us/data/di/NetworkModule.kt @@ -1,47 +1,49 @@ -package com.example.com_us.data +package com.example.com_us.data.di import android.content.Context import android.util.Log -import okhttp3.Interceptor +import com.example.com_us.base.AppInterceptor +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent import okhttp3.OkHttpClient -import okhttp3.Response import okhttp3.logging.HttpLoggingInterceptor import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory -import java.io.IOException import java.security.KeyStore import java.security.cert.Certificate import java.security.cert.CertificateException import java.security.cert.CertificateFactory import java.security.cert.X509Certificate import java.util.concurrent.TimeUnit +import javax.inject.Singleton import javax.net.ssl.SSLContext import javax.net.ssl.TrustManagerFactory import javax.net.ssl.X509TrustManager -object ApiClient { - private const val baseUrl = "http://15.165.140.141:8080" - private const val contentType = "application/json" - private const val sampleToken = "Bearer " + "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiI1IiwiaWF0IjoxNzIzNjY5NTQxLCJleHAiOjE3MjYyNjE1NDF9.x2KM7idtqLN4EQwjuw3zqPWhp01KpmCxHLweYu1Gqvo" +// 모듈을 통해 객체 제공방법을 hilt에게 알려줄 수 있다. +// 생성자 삽입할 수 없는 결합 +@Module +@InstallIn(SingletonComponent::class) +object NetworkModule { - fun getApiClient(context: Context): Retrofit { - val accessToken = "Bearer " + ""; - return Retrofit.Builder() - .baseUrl(baseUrl) - .client(sslOkHttpClient(context, AppInterceptor(context, sampleToken))) - .addConverterFactory(GsonConverterFactory.create()) - .build() - } - - private fun okHttpClient(interceptor: AppInterceptor): OkHttpClient { - return OkHttpClient.Builder() - .addInterceptor(interceptor) - .addInterceptor(HttpLoggingInterceptor().apply { - level = HttpLoggingInterceptor.Level.BODY - }).build() + @Provides + @Singleton + fun provideInterceptor( + ) : AppInterceptor { + return AppInterceptor( + accessToken = "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiI0IiwiaWF0IjoxNzMxOTI3NDI0LCJleHAiOjE3MzQ1MTk0MjR9.jU6s6drHXR09SBvFMbd5w_rbkxG3wYHYHwTzy403ifY" + ) } - private fun sslOkHttpClient(context: Context, interceptor: Interceptor): OkHttpClient { + @Provides + @Singleton + fun provideOkHttpClient( + @ApplicationContext context: Context, + interceptor: AppInterceptor + ) : OkHttpClient { val okHttpClient = OkHttpClient.Builder() try { val cf = CertificateFactory.getInstance("X.509") @@ -56,8 +58,7 @@ object ApiClient { } finally { caInput.close() } - - if (ca != null) { + if(ca != null) { val keyStoreType = KeyStore.getDefaultType() val keyStore = KeyStore.getInstance(keyStoreType) keyStore.load(null, null) @@ -78,35 +79,26 @@ object ApiClient { } catch (e: Exception) { e.printStackTrace() } - return okHttpClient.addInterceptor(interceptor) .addInterceptor(HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BODY }) .connectTimeout(200, TimeUnit.SECONDS) - .readTimeout(200,TimeUnit.SECONDS) - .writeTimeout(200,TimeUnit.SECONDS) + .readTimeout(200, TimeUnit.SECONDS) + .writeTimeout(200, TimeUnit.SECONDS) .build() } - class AppInterceptor(private val context: Context, private val accessToken: String) : Interceptor { - @Throws(IOException::class) - override fun intercept(chain: Interceptor.Chain) : Response = with(chain) { - val newRequest = request().newBuilder() - .addHeader("Content-Type", contentType) - .addHeader("Authorization", accessToken) - .build() - - val response = proceed(newRequest) - when (response.code) { - 401, 404 -> { - Log.d("API FAILURE", response.toString()) - } - 500 -> { - Log.d("API FAILURE 500", response.toString()) - } - } - response - } + @Provides + @Singleton + fun provideRetrofit( + @BaseUrl baseUrl : String, + okHttpClient: OkHttpClient) : Retrofit { + return Retrofit.Builder() + .baseUrl(baseUrl) + .client(okHttpClient) + .addConverterFactory(GsonConverterFactory.create()) + .build() } + } \ No newline at end of file diff --git a/app/src/main/java/com/example/com_us/data/di/RepositoryModule.kt b/app/src/main/java/com/example/com_us/data/di/RepositoryModule.kt new file mode 100644 index 0000000..5d48549 --- /dev/null +++ b/app/src/main/java/com/example/com_us/data/di/RepositoryModule.kt @@ -0,0 +1,29 @@ +package com.example.com_us.data.di + +import com.example.com_us.data.default_repository.DefaultHomeRepository +import com.example.com_us.data.default_repository.DefaultProfileRepository +import com.example.com_us.data.default_repository.DefaultQuestionRepository +import com.example.com_us.data.repository.HomeRepository +import com.example.com_us.data.repository.ProfileRepository +import com.example.com_us.data.repository.QuestionRepository +import dagger.Binds +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@InstallIn(SingletonComponent::class) +@Module +interface RepositoryModule { + @Binds + @Singleton + fun provideHomeRepository(defaultRepository : DefaultHomeRepository) :HomeRepository + + @Binds + fun provideProfileRepository(defaultRepository : DefaultProfileRepository) : ProfileRepository + + @Binds + fun provideQuestionRepository(defaultRepository : DefaultQuestionRepository) : QuestionRepository + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/com_us/data/di/ServiceModule.kt b/app/src/main/java/com/example/com_us/data/di/ServiceModule.kt new file mode 100644 index 0000000..fd44e6f --- /dev/null +++ b/app/src/main/java/com/example/com_us/data/di/ServiceModule.kt @@ -0,0 +1,32 @@ +package com.example.com_us.data.di + +import com.example.com_us.data.service.HomeService +import com.example.com_us.data.service.ProfileService +import com.example.com_us.data.service.QuestionService +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import retrofit2.Retrofit +import javax.inject.Singleton + + +@InstallIn(SingletonComponent::class) +@Module +object ServiceModule { + @Provides + @Singleton + fun provideHomeService(retrofit: Retrofit): HomeService = + retrofit.create(HomeService::class.java) + + @Provides + @Singleton + fun provideProfileService(retrofit: Retrofit): ProfileService = + retrofit.create(ProfileService::class.java) + + @Provides + @Singleton + fun provideQuestionService(retrofit: Retrofit): QuestionService = + retrofit.create(QuestionService::class.java) + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/com_us/data/di/quarlifier.kt b/app/src/main/java/com/example/com_us/data/di/quarlifier.kt new file mode 100644 index 0000000..b2ef289 --- /dev/null +++ b/app/src/main/java/com/example/com_us/data/di/quarlifier.kt @@ -0,0 +1,7 @@ +package com.example.com_us.data.di + +import javax.inject.Qualifier + + @Qualifier + @Retention(AnnotationRetention.BINARY) + annotation class BaseUrl diff --git a/app/src/main/java/com/example/com_us/data/response/home/ResponseHomeDataDto.kt b/app/src/main/java/com/example/com_us/data/model/home/ResponseHomeDataDto.kt similarity index 97% rename from app/src/main/java/com/example/com_us/data/response/home/ResponseHomeDataDto.kt rename to app/src/main/java/com/example/com_us/data/model/home/ResponseHomeDataDto.kt index 3e815da..d129951 100644 --- a/app/src/main/java/com/example/com_us/data/response/home/ResponseHomeDataDto.kt +++ b/app/src/main/java/com/example/com_us/data/model/home/ResponseHomeDataDto.kt @@ -1,4 +1,4 @@ -package com.example.com_us.data.response.home +package com.example.com_us.data.model.home import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable diff --git a/app/src/main/java/com/example/com_us/data/request/question/RequestAnswerDto.kt b/app/src/main/java/com/example/com_us/data/model/question/request/RequestAnswerRequest.kt similarity index 71% rename from app/src/main/java/com/example/com_us/data/request/question/RequestAnswerDto.kt rename to app/src/main/java/com/example/com_us/data/model/question/request/RequestAnswerRequest.kt index 3fa7af7..499bf68 100644 --- a/app/src/main/java/com/example/com_us/data/request/question/RequestAnswerDto.kt +++ b/app/src/main/java/com/example/com_us/data/model/question/request/RequestAnswerRequest.kt @@ -1,10 +1,10 @@ -package com.example.com_us.data.request.question +package com.example.com_us.data.model.question.request import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -data class RequestAnswerDto( +data class RequestAnswerRequest( @SerialName("questionId") val questionId: Long, @SerialName("answerContent") diff --git a/app/src/main/java/com/example/com_us/data/response/question/ResponseAnswerDetailDto.kt b/app/src/main/java/com/example/com_us/data/model/question/response/question/ResponseAnswerDetailDto.kt similarity index 56% rename from app/src/main/java/com/example/com_us/data/response/question/ResponseAnswerDetailDto.kt rename to app/src/main/java/com/example/com_us/data/model/question/response/question/ResponseAnswerDetailDto.kt index e8140ef..f3f1bd3 100644 --- a/app/src/main/java/com/example/com_us/data/response/question/ResponseAnswerDetailDto.kt +++ b/app/src/main/java/com/example/com_us/data/model/question/response/question/ResponseAnswerDetailDto.kt @@ -1,15 +1,15 @@ -package com.example.com_us.data.response.question +package com.example.com_us.data.model.question.response.question import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable data class ResponseAnswerDetailDto( @SerialName("id") - val id: Long, + val id: Long=0L, @SerialName("signLanguageName") - val signLanguageName: String, + val signLanguageName: String = "", @SerialName("signLanguageVideoUrl") - val signLanguageVideoUrl: String, + val signLanguageVideoUrl: String = "", @SerialName("signLanguageDescription") - val signLanguageDescription: String, + val signLanguageDescription: String = "", ) diff --git a/app/src/main/java/com/example/com_us/data/response/question/ResponseAnswerDetailWithDateDto.kt b/app/src/main/java/com/example/com_us/data/model/question/response/question/ResponseAnswerDetailWithDateDto.kt similarity index 81% rename from app/src/main/java/com/example/com_us/data/response/question/ResponseAnswerDetailWithDateDto.kt rename to app/src/main/java/com/example/com_us/data/model/question/response/question/ResponseAnswerDetailWithDateDto.kt index 1bc5f09..6e1b909 100644 --- a/app/src/main/java/com/example/com_us/data/response/question/ResponseAnswerDetailWithDateDto.kt +++ b/app/src/main/java/com/example/com_us/data/model/question/response/question/ResponseAnswerDetailWithDateDto.kt @@ -1,4 +1,4 @@ -package com.example.com_us.data.response.question +package com.example.com_us.data.model.question.response.question import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable diff --git a/app/src/main/java/com/example/com_us/data/response/question/ResponsePreviousAnswerDto.kt b/app/src/main/java/com/example/com_us/data/model/question/response/question/ResponsePreviousAnswerDto.kt similarity index 92% rename from app/src/main/java/com/example/com_us/data/response/question/ResponsePreviousAnswerDto.kt rename to app/src/main/java/com/example/com_us/data/model/question/response/question/ResponsePreviousAnswerDto.kt index 6bb4465..5a2bbfc 100644 --- a/app/src/main/java/com/example/com_us/data/response/question/ResponsePreviousAnswerDto.kt +++ b/app/src/main/java/com/example/com_us/data/model/question/response/question/ResponsePreviousAnswerDto.kt @@ -1,4 +1,4 @@ -package com.example.com_us.data.response.question +package com.example.com_us.data.model.question.response.question import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable diff --git a/app/src/main/java/com/example/com_us/data/response/question/ResponseProfileDto.kt b/app/src/main/java/com/example/com_us/data/model/question/response/question/ResponseProfileDto.kt similarity index 95% rename from app/src/main/java/com/example/com_us/data/response/question/ResponseProfileDto.kt rename to app/src/main/java/com/example/com_us/data/model/question/response/question/ResponseProfileDto.kt index aa19812..d30dde9 100644 --- a/app/src/main/java/com/example/com_us/data/response/question/ResponseProfileDto.kt +++ b/app/src/main/java/com/example/com_us/data/model/question/response/question/ResponseProfileDto.kt @@ -1,4 +1,4 @@ -package com.example.com_us.data.response.question +package com.example.com_us.data.model.question.response.question import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable diff --git a/app/src/main/java/com/example/com_us/data/response/question/ResponseQuestionDetailDto.kt b/app/src/main/java/com/example/com_us/data/model/question/response/question/ResponseQuestionDetailDto.kt similarity index 88% rename from app/src/main/java/com/example/com_us/data/response/question/ResponseQuestionDetailDto.kt rename to app/src/main/java/com/example/com_us/data/model/question/response/question/ResponseQuestionDetailDto.kt index 339f1f6..4d515f6 100644 --- a/app/src/main/java/com/example/com_us/data/response/question/ResponseQuestionDetailDto.kt +++ b/app/src/main/java/com/example/com_us/data/model/question/response/question/ResponseQuestionDetailDto.kt @@ -1,4 +1,4 @@ -package com.example.com_us.data.response.question +package com.example.com_us.data.model.question.response.question import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable diff --git a/app/src/main/java/com/example/com_us/data/response/question/ResponseQuestionListDto.kt b/app/src/main/java/com/example/com_us/data/model/question/response/question/ResponseQuestionListDto.kt similarity index 86% rename from app/src/main/java/com/example/com_us/data/response/question/ResponseQuestionListDto.kt rename to app/src/main/java/com/example/com_us/data/model/question/response/question/ResponseQuestionListDto.kt index b158eea..ef3a750 100644 --- a/app/src/main/java/com/example/com_us/data/response/question/ResponseQuestionListDto.kt +++ b/app/src/main/java/com/example/com_us/data/model/question/response/question/ResponseQuestionListDto.kt @@ -1,4 +1,4 @@ -package com.example.com_us.data.response.question +package com.example.com_us.data.model.question.response.question import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable diff --git a/app/src/main/java/com/example/com_us/data/repository/HomeRepository.kt b/app/src/main/java/com/example/com_us/data/repository/HomeRepository.kt index c1b3eda..a616187 100644 --- a/app/src/main/java/com/example/com_us/data/repository/HomeRepository.kt +++ b/app/src/main/java/com/example/com_us/data/repository/HomeRepository.kt @@ -1,12 +1,7 @@ package com.example.com_us.data.repository -import com.example.com_us.data.datasource.HomeRemoteDataSource -import com.example.com_us.data.response.home.ResponseHomeDataDto +import com.example.com_us.data.model.home.ResponseHomeDataDto -class HomeRepository( - private val homeRemoteDataSource: HomeRemoteDataSource -) { - suspend fun getHomeData(): Result { - return runCatching { homeRemoteDataSource.getHomeData().data!! } - } +interface HomeRepository{ + suspend fun getHomeData(): Result } \ No newline at end of file diff --git a/app/src/main/java/com/example/com_us/data/repository/ProfileRepository.kt b/app/src/main/java/com/example/com_us/data/repository/ProfileRepository.kt index 7f1ed87..56f4407 100644 --- a/app/src/main/java/com/example/com_us/data/repository/ProfileRepository.kt +++ b/app/src/main/java/com/example/com_us/data/repository/ProfileRepository.kt @@ -1,12 +1,7 @@ package com.example.com_us.data.repository -import com.example.com_us.data.datasource.ProfileRemoteDataSource -import com.example.com_us.data.response.question.ResponseProfileDto +import com.example.com_us.data.model.question.response.question.ResponseProfileDto -class ProfileRepository( - private val profileRemoteDataSource: ProfileRemoteDataSource -) { - suspend fun getProfileData(): Result { - return runCatching { profileRemoteDataSource.getProfileData().data!! } - } +interface ProfileRepository { + suspend fun getProfileData(): Result } \ No newline at end of file diff --git a/app/src/main/java/com/example/com_us/data/repository/QuestionRepository.kt b/app/src/main/java/com/example/com_us/data/repository/QuestionRepository.kt index 2a6f067..0aa1343 100644 --- a/app/src/main/java/com/example/com_us/data/repository/QuestionRepository.kt +++ b/app/src/main/java/com/example/com_us/data/repository/QuestionRepository.kt @@ -1,29 +1,16 @@ package com.example.com_us.data.repository -import com.example.com_us.data.datasource.QuestionRemoteDataSource -import com.example.com_us.data.request.question.RequestAnswerDto -import com.example.com_us.data.response.question.ResponseAnswerDetailDto -import com.example.com_us.data.response.question.ResponseAnswerDetailWithDateDto -import com.example.com_us.data.response.question.ResponsePreviousAnswerDto -import com.example.com_us.data.response.question.ResponseQuestionDetailDto -import com.example.com_us.data.response.question.ResponseQuestionDto +import com.example.com_us.data.model.question.request.RequestAnswerRequest +import com.example.com_us.data.model.question.response.question.ResponseAnswerDetailDto +import com.example.com_us.data.model.question.response.question.ResponseAnswerDetailWithDateDto +import com.example.com_us.data.model.question.response.question.ResponsePreviousAnswerDto +import com.example.com_us.data.model.question.response.question.ResponseQuestionDetailDto +import com.example.com_us.data.model.question.response.question.ResponseQuestionDto -class QuestionRepository( - private val questionRemoteDataSource: QuestionRemoteDataSource -) { - suspend fun getQuestionListByCate(category: String): Result> { - return runCatching { questionRemoteDataSource.getQuestionListByCate(category).data!! } - } - suspend fun getQuestionDetail(questionId: Long): Result { - return runCatching { questionRemoteDataSource.getQuestionDetail(questionId).data!! } - } - suspend fun getAnswerDetail(answer: String): Result> { - return runCatching { questionRemoteDataSource.getAnswerDetail(answer).data!! } - } - suspend fun postAnswer(body: RequestAnswerDto): Result { - return runCatching { questionRemoteDataSource.postAnswer(body).data!! } - } - suspend fun getPreviousAnswer(questionId: Long ): Result { - return runCatching { questionRemoteDataSource.getPreviousAnswer(questionId).data!! } - } +interface QuestionRepository { + suspend fun getQuestionListByCate(category: String): Result> + suspend fun getQuestionDetail(questionId: Long): Result + suspend fun getAnswerDetail(answer: String): Result> + suspend fun postAnswer(body: RequestAnswerRequest): Result + suspend fun getPreviousAnswer(questionId: Long ): Result } \ No newline at end of file diff --git a/app/src/main/java/com/example/com_us/data/response/BaseResponse.kt b/app/src/main/java/com/example/com_us/data/response/BaseResponse.kt deleted file mode 100644 index fd26f1d..0000000 --- a/app/src/main/java/com/example/com_us/data/response/BaseResponse.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.example.com_us.data.response - -import kotlinx.serialization.Serializable - -@Serializable -data class BaseResponse( - val status: Int, - val message: String, - val data: T? = null, -) - -@Serializable -data class BaseResponseNoData( - val status: Int, - val message: String, -) diff --git a/app/src/main/java/com/example/com_us/data/service/HomeService.kt b/app/src/main/java/com/example/com_us/data/service/HomeService.kt index 62a0228..d8f4d78 100644 --- a/app/src/main/java/com/example/com_us/data/service/HomeService.kt +++ b/app/src/main/java/com/example/com_us/data/service/HomeService.kt @@ -1,7 +1,7 @@ package com.example.com_us.data.service -import com.example.com_us.data.response.BaseResponse -import com.example.com_us.data.response.home.ResponseHomeDataDto +import com.example.com_us.base.data.BaseResponse +import com.example.com_us.data.model.home.ResponseHomeDataDto import retrofit2.http.GET interface HomeService { diff --git a/app/src/main/java/com/example/com_us/data/service/ProfileService.kt b/app/src/main/java/com/example/com_us/data/service/ProfileService.kt index 327f2e0..9f47180 100644 --- a/app/src/main/java/com/example/com_us/data/service/ProfileService.kt +++ b/app/src/main/java/com/example/com_us/data/service/ProfileService.kt @@ -1,8 +1,7 @@ package com.example.com_us.data.service -import com.example.com_us.data.response.BaseResponse -import com.example.com_us.data.response.home.ResponseHomeDataDto -import com.example.com_us.data.response.question.ResponseProfileDto +import com.example.com_us.base.data.BaseResponse +import com.example.com_us.data.model.question.response.question.ResponseProfileDto import retrofit2.http.GET interface ProfileService { diff --git a/app/src/main/java/com/example/com_us/data/service/QuestionService.kt b/app/src/main/java/com/example/com_us/data/service/QuestionService.kt index 5d9b67d..1670133 100644 --- a/app/src/main/java/com/example/com_us/data/service/QuestionService.kt +++ b/app/src/main/java/com/example/com_us/data/service/QuestionService.kt @@ -1,12 +1,12 @@ package com.example.com_us.data.service -import com.example.com_us.data.request.question.RequestAnswerDto -import com.example.com_us.data.response.BaseResponse -import com.example.com_us.data.response.question.ResponseAnswerDetailDto -import com.example.com_us.data.response.question.ResponseAnswerDetailWithDateDto -import com.example.com_us.data.response.question.ResponsePreviousAnswerDto -import com.example.com_us.data.response.question.ResponseQuestionDetailDto -import com.example.com_us.data.response.question.ResponseQuestionDto +import com.example.com_us.data.model.question.request.RequestAnswerRequest +import com.example.com_us.base.data.BaseResponse +import com.example.com_us.data.model.question.response.question.ResponseAnswerDetailDto +import com.example.com_us.data.model.question.response.question.ResponseAnswerDetailWithDateDto +import com.example.com_us.data.model.question.response.question.ResponsePreviousAnswerDto +import com.example.com_us.data.model.question.response.question.ResponseQuestionDetailDto +import com.example.com_us.data.model.question.response.question.ResponseQuestionDto import retrofit2.http.Body import retrofit2.http.GET import retrofit2.http.POST @@ -31,7 +31,7 @@ interface QuestionService { @POST("/api/answer") suspend fun postAnswer( - @Body body: RequestAnswerDto, + @Body body: RequestAnswerRequest, ): BaseResponse @GET("/api/answer/{questionId}") diff --git a/app/src/main/java/com/example/com_us/data/source/HomeDataSource.kt b/app/src/main/java/com/example/com_us/data/source/HomeDataSource.kt new file mode 100644 index 0000000..30727e5 --- /dev/null +++ b/app/src/main/java/com/example/com_us/data/source/HomeDataSource.kt @@ -0,0 +1,8 @@ +package com.example.com_us.data.source + +import com.example.com_us.base.data.BaseResponse +import com.example.com_us.data.model.home.ResponseHomeDataDto + +interface HomeDataSource { + suspend fun getHomeData() : BaseResponse +} \ No newline at end of file diff --git a/app/src/main/java/com/example/com_us/data/source/ProfileDataSource.kt b/app/src/main/java/com/example/com_us/data/source/ProfileDataSource.kt new file mode 100644 index 0000000..387c696 --- /dev/null +++ b/app/src/main/java/com/example/com_us/data/source/ProfileDataSource.kt @@ -0,0 +1,8 @@ +package com.example.com_us.data.source + +import com.example.com_us.base.data.BaseResponse +import com.example.com_us.data.model.question.response.question.ResponseProfileDto + +interface ProfileDataSource { + suspend fun getProfileData() : BaseResponse +} \ No newline at end of file diff --git a/app/src/main/java/com/example/com_us/data/source/QuestionDataSource.kt b/app/src/main/java/com/example/com_us/data/source/QuestionDataSource.kt new file mode 100644 index 0000000..d9fb8cc --- /dev/null +++ b/app/src/main/java/com/example/com_us/data/source/QuestionDataSource.kt @@ -0,0 +1,17 @@ +package com.example.com_us.data.source + +import com.example.com_us.data.model.question.request.RequestAnswerRequest +import com.example.com_us.base.data.BaseResponse +import com.example.com_us.data.model.question.response.question.ResponseAnswerDetailDto +import com.example.com_us.data.model.question.response.question.ResponseAnswerDetailWithDateDto +import com.example.com_us.data.model.question.response.question.ResponsePreviousAnswerDto +import com.example.com_us.data.model.question.response.question.ResponseQuestionDetailDto +import com.example.com_us.data.model.question.response.question.ResponseQuestionDto + +interface QuestionDataSource { + suspend fun getQuestionListByCate(category: String) : BaseResponse> + suspend fun getQuestionDetail(questionId: Long) : BaseResponse + suspend fun getAnswerDetail(answer: String) : BaseResponse> + suspend fun postAnswer(body: RequestAnswerRequest) : BaseResponse + suspend fun getPreviousAnswer(questionId: Long) : BaseResponse +} \ No newline at end of file diff --git a/app/src/main/java/com/example/com_us/ui/base/UiState.kt b/app/src/main/java/com/example/com_us/ui/base/UiState.kt new file mode 100644 index 0000000..4d6e9fc --- /dev/null +++ b/app/src/main/java/com/example/com_us/ui/base/UiState.kt @@ -0,0 +1,9 @@ +package com.example.com_us.ui.base + + +// 성공 / 실패에 따른 UI 처리 +sealed class UiState { + data object Initial : UiState() + data class Success(val data : T) : UiState() + data class Error(val message : String) : UiState() +} diff --git a/app/src/main/java/com/example/com_us/ui/compose/AnswerOptionList.kt b/app/src/main/java/com/example/com_us/ui/compose/AnswerOptionList.kt index 3b421fd..f93f18f 100644 --- a/app/src/main/java/com/example/com_us/ui/compose/AnswerOptionList.kt +++ b/app/src/main/java/com/example/com_us/ui/compose/AnswerOptionList.kt @@ -7,10 +7,10 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue -import com.example.com_us.ui.question.QuestionViewModel +import com.example.com_us.ui.question.select.SelectAnswerViewModel @Composable -fun AnswerOptionList(answerList: List, questionViewModel: QuestionViewModel) { +fun AnswerOptionList(answerList: List, questionViewModel: SelectAnswerViewModel) { var columCount = 2 var selectedOption by remember { mutableIntStateOf(-1) } // 선택된 아이템의 ID를 저장하는 상태 diff --git a/app/src/main/java/com/example/com_us/ui/compose/QuestionListItem.kt b/app/src/main/java/com/example/com_us/ui/compose/QuestionListItem.kt index e5dfd72..cf67bd2 100644 --- a/app/src/main/java/com/example/com_us/ui/compose/QuestionListItem.kt +++ b/app/src/main/java/com/example/com_us/ui/compose/QuestionListItem.kt @@ -16,7 +16,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp -import com.example.com_us.data.response.question.ResponseQuestionDto +import com.example.com_us.data.model.question.response.question.ResponseQuestionDto import com.example.com_us.util.ColorMatch import com.example.com_us.util.ColorType import com.example.com_us.ui.compose.theme.Gray200 @@ -27,7 +27,7 @@ import com.example.com_us.ui.compose.theme.Typography @Composable fun QuestionListItem(data: ResponseQuestionDto, onClick: () -> Unit) { - var color = ColorMatch.fromKor(data.answerType)?.colorType ?: ColorType.GRAY + val color = ColorMatch.fromKor(data.answerType)?.colorType ?: ColorType.GRAY Surface( diff --git a/app/src/main/java/com/example/com_us/ui/home/HomeFragment.kt b/app/src/main/java/com/example/com_us/ui/home/HomeFragment.kt index f46f835..1bacfda 100644 --- a/app/src/main/java/com/example/com_us/ui/home/HomeFragment.kt +++ b/app/src/main/java/com/example/com_us/ui/home/HomeFragment.kt @@ -6,21 +6,29 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.view.ViewTreeObserver +import android.widget.Toast import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels +import androidx.lifecycle.Lifecycle import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import com.bumptech.glide.Glide import com.bumptech.glide.load.resource.bitmap.RoundedCorners import com.bumptech.glide.request.RequestOptions import com.example.com_us.R -import com.example.com_us.data.response.home.Block -import com.example.com_us.data.response.home.Category +import com.example.com_us.data.model.home.Block +import com.example.com_us.data.model.home.Category import com.example.com_us.databinding.FragmentHomeBinding +import com.example.com_us.ui.base.UiState +import com.example.com_us.ui.question.theme.ThemeQuestionListActivity import com.example.com_us.util.ColorMatch -import com.example.com_us.util.ServerResponseHandler import com.example.com_us.util.ThemeType +import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.launch -class HomeFragment : Fragment(), View.OnClickListener, ServerResponseHandler { +@AndroidEntryPoint +class HomeFragment : Fragment(), View.OnClickListener { private lateinit var blockList: List> @@ -28,7 +36,7 @@ class HomeFragment : Fragment(), View.OnClickListener, ServerResponseHandler { private var isReload: MutableLiveData = MutableLiveData(false) private val binding get() = _binding!! - private val homeViewModel: HomeViewModel by viewModels { HomeViewModelFactory(requireContext()) } + private val homeViewModel: HomeViewModel by viewModels() private val scrollChangedListener = ViewTreeObserver.OnScrollChangedListener { _binding?.let { @@ -52,9 +60,6 @@ class HomeFragment : Fragment(), View.OnClickListener, ServerResponseHandler { listOf(binding.viewHomeBlock30, binding.viewHomeBlock31, binding.viewHomeBlock32, binding.viewHomeBlock33), ) - homeViewModel.serverResponseHandler = this - homeViewModel.loadHomeData() - setSwipeRefresh() setThemeClickListener() setHomeData() @@ -88,19 +93,41 @@ class HomeFragment : Fragment(), View.OnClickListener, ServerResponseHandler { private fun setHomeData() { val emojiText = String(Character.toChars(resources.getInteger(R.integer.waving_hand_sign))) - homeViewModel.homeData.observe(viewLifecycleOwner) { - val chatMinute = it.user.todayChatTime.substring(3, 5).toInt() - binding.textviewHomeGreeting.text = String.format(resources.getString(R.string.home_title_greeting_user_hi), it.user.name, emojiText) - binding.textviewHomeMinute.text = String.format(resources.getString(R.string.home_sub_today_conversation_minute), chatMinute) - Glide.with(this) - .load(it.user.imageUrl) - .apply(RequestOptions().transform(RoundedCorners(300))) - .into(binding.imageviewHomeUsericon) - - setThemeProgress(it.category) - setBlock(it.block) - isReload.value = true + viewLifecycleOwner.lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + homeViewModel.homeUiState.collect { + when (it) { + is UiState.Success -> { + binding.constraintHome.visibility = View.VISIBLE + val chatMinute = it.data.user.todayChatTime.substring(3, 5).toInt() + binding.textviewHomeGreeting.text = String.format( + resources.getString(R.string.home_title_greeting_user_hi), + it.data.user.name, + emojiText + ) + binding.textviewHomeMinute.text = String.format( + resources.getString(R.string.home_sub_today_conversation_minute), + chatMinute + ) + Glide.with(this@HomeFragment) + .load(it.data.user.imageUrl) + .apply(RequestOptions().transform(RoundedCorners(300))) + .into(binding.imageviewHomeUsericon) + + setThemeProgress(it.data.category) + setBlock(it.data.block) + isReload.value = true + + } + is UiState.Error -> { + Toast.makeText(context,"잠시 후에 다시 시도해주세요!",Toast.LENGTH_SHORT).show() + } + else -> {} + } + } + } } + } override fun onClick(view: View) { @@ -155,6 +182,7 @@ class HomeFragment : Fragment(), View.OnClickListener, ServerResponseHandler { color = ColorMatch.findColorFromKor(data.category) if(color != null){ blockList[data.blockRow][data.blockColumn].setBackgroundResource(color) + } } } @@ -164,7 +192,7 @@ class HomeFragment : Fragment(), View.OnClickListener, ServerResponseHandler { binding.textviewHomeNoblock.visibility = visibility } private fun moveToQuestionList(theme: String, themeKor: String) { - val intent = Intent(context, HomeThemeQuestionListActivity::class.java) + val intent = Intent(context, ThemeQuestionListActivity::class.java) intent.putExtra("theme", theme) intent.putExtra("themeKor", themeKor) startActivity(intent) @@ -186,10 +214,4 @@ class HomeFragment : Fragment(), View.OnClickListener, ServerResponseHandler { _binding = null } - override fun onServerSuccess() { - binding.constraintHome.visibility = View.VISIBLE - } - - override fun onServerFailure() { - } } diff --git a/app/src/main/java/com/example/com_us/ui/home/HomeViewModel.kt b/app/src/main/java/com/example/com_us/ui/home/HomeViewModel.kt index 76978ac..d6d7d91 100644 --- a/app/src/main/java/com/example/com_us/ui/home/HomeViewModel.kt +++ b/app/src/main/java/com/example/com_us/ui/home/HomeViewModel.kt @@ -1,32 +1,47 @@ package com.example.com_us.ui.home -import android.util.Log -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.example.com_us.base.data.NetworkError import com.example.com_us.data.repository.HomeRepository -import com.example.com_us.data.response.home.ResponseHomeDataDto +import com.example.com_us.data.model.home.ResponseHomeDataDto +import com.example.com_us.ui.base.UiState import com.example.com_us.util.ServerResponseHandler +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch +import okhttp3.Response +import javax.inject.Inject -class HomeViewModel(private val homeRepository: HomeRepository) : ViewModel() { - private val _homeData = MutableLiveData() - val homeData: LiveData = _homeData +@HiltViewModel +class HomeViewModel @Inject constructor(private val homeRepository: HomeRepository) : ViewModel() { + private val _homeUiState= MutableStateFlow>(UiState.Initial) + val homeUiState = _homeUiState.asStateFlow() var serverResponseHandler: ServerResponseHandler? = null + init { + loadHomeData() + } + fun loadHomeData(){ viewModelScope.launch { homeRepository.getHomeData() .onSuccess { - _homeData.value = it - serverResponseHandler?.onServerSuccess() + _homeUiState.value = UiState.Success(it) } .onFailure { - Log.d("GET: [HOME DATA] DATA FAILURE", it.toString()) - serverResponseHandler?.onServerFailure() + val errorMessage = when(it) { + is NetworkError.NetworkException -> { it.message } + is NetworkError.NullDataError -> { "데이터가 준비중이에요!" } + else -> "알 수 없는 에러가 발생했습니다. 다시 시도해주세요!" + } + if (errorMessage!= null ) _homeUiState.value = UiState.Error(errorMessage) } } } + + + } \ No newline at end of file diff --git a/app/src/main/java/com/example/com_us/ui/home/HomeViewModelFactory.kt b/app/src/main/java/com/example/com_us/ui/home/HomeViewModelFactory.kt deleted file mode 100644 index 9440efc..0000000 --- a/app/src/main/java/com/example/com_us/ui/home/HomeViewModelFactory.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.example.com_us.ui.home - -import android.content.Context -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider -import com.example.com_us.data.ApiClient -import com.example.com_us.data.datasource.HomeRemoteDataSource -import com.example.com_us.data.datasource.QuestionRemoteDataSource -import com.example.com_us.data.repository.HomeRepository -import com.example.com_us.data.repository.QuestionRepository -import retrofit2.create -class HomeViewModelFactory(private val context: Context): ViewModelProvider.Factory { - - override fun create(modelClass: Class): T { - return when { - modelClass.isAssignableFrom(HomeViewModel::class.java) -> { - val repository = HomeRepository(HomeRemoteDataSource(ApiClient.getApiClient(context).create())) - HomeViewModel(repository) as T - } - else -> { - throw IllegalArgumentException("Failed to create ViewModel : ${modelClass.name}") - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/example/com_us/ui/login/LoginActivity.kt b/app/src/main/java/com/example/com_us/ui/login/LoginActivity.kt new file mode 100644 index 0000000..1cf1f4b --- /dev/null +++ b/app/src/main/java/com/example/com_us/ui/login/LoginActivity.kt @@ -0,0 +1,19 @@ +package com.example.com_us.ui.login + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import com.example.com_us.databinding.ActivityLoginBinding +import com.example.com_us.databinding.ActivityQuestionCheckAnswerBinding +import dagger.hilt.android.AndroidEntryPoint + +@AndroidEntryPoint +class LoginActivity : AppCompatActivity() { + + private lateinit var binding: ActivityLoginBinding + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityLoginBinding.inflate(layoutInflater) + setContentView(binding.root) + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/com_us/ui/profile/ProfileFragment.kt b/app/src/main/java/com/example/com_us/ui/profile/ProfileFragment.kt index 8b0931e..556b0b2 100644 --- a/app/src/main/java/com/example/com_us/ui/profile/ProfileFragment.kt +++ b/app/src/main/java/com/example/com_us/ui/profile/ProfileFragment.kt @@ -3,28 +3,35 @@ package com.example.com_us.ui.profile import android.graphics.drawable.GradientDrawable import android.os.Build import android.os.Bundle +import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.LinearLayout.LayoutParams +import android.widget.Toast import androidx.annotation.RequiresApi import androidx.core.content.ContextCompat import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels +import androidx.lifecycle.lifecycleScope import com.bumptech.glide.Glide import com.bumptech.glide.load.resource.bitmap.RoundedCorners import com.bumptech.glide.request.RequestOptions import com.example.com_us.R import com.example.com_us.databinding.FragmentProfileBinding +import com.example.com_us.ui.base.UiState import com.example.com_us.util.ServerResponseHandler +import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.launch import java.time.LocalTime import java.time.format.DateTimeFormatter -class ProfileFragment : Fragment(), ServerResponseHandler { +@AndroidEntryPoint +class ProfileFragment : Fragment() ,ServerResponseHandler{ private var _binding: FragmentProfileBinding? = null - private val profileViewModel: ProfileViewModel by viewModels { ProfileViewModelFactory(requireContext()) } + private val profileViewModel: ProfileViewModel by viewModels() private val binding get() = _binding!! @RequiresApi(Build.VERSION_CODES.O) @@ -36,31 +43,53 @@ class ProfileFragment : Fragment(), ServerResponseHandler { _binding = FragmentProfileBinding.inflate(inflater, container, false) profileViewModel.loadProfileData() - profileViewModel.serverResponseHandler = this - - setProfile() return binding.root } + @RequiresApi(Build.VERSION_CODES.O) + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + setProfile() + super.onViewCreated(view, savedInstanceState) + } + @RequiresApi(Build.VERSION_CODES.O) private fun setProfile() { - profileViewModel.profileData.observe(viewLifecycleOwner) { - val themeGraphRatioList: List = listOf( - it.answerStatistic.dailyQuestionRatio.toFloat(), - it.answerStatistic.schoolQuestionRatio.toFloat(), - it.answerStatistic.friendQuestionRatio.toFloat(), - it.answerStatistic.familyQuestionRatio.toFloat(), - it.answerStatistic.hobbyQuestionRatio.toFloat(), - ) - setProfile(it.userInfo.name, it.userInfo.imageUrl) - setStatic(it.userInfo.totalChatTime, it.userInfo.totalChatCount) - changeTypeGraph(it.answerStatistic.multipleChoiceRatio.toFloat(), it.answerStatistic.sentenceRatio.toFloat()) - changeThemeGraph(themeGraphRatioList) + lifecycleScope.launch { + profileViewModel.profileUiState.collect { + when (it) { + is UiState.Success -> { + binding.constraintProfile.visibility = View.VISIBLE + val themeGraphRatioList: List = listOf( + it.data.answerStatistic.dailyQuestionRatio.toFloat(), + it.data.answerStatistic.schoolQuestionRatio.toFloat(), + it.data.answerStatistic.friendQuestionRatio.toFloat(), + it.data.answerStatistic.familyQuestionRatio.toFloat(), + it.data.answerStatistic.hobbyQuestionRatio.toFloat(), + ) + + setProfile(it.data.userInfo.name, it.data.userInfo.imageUrl) + setStatic(it.data.userInfo.totalChatTime, it.data.userInfo.totalChatCount) + changeTypeGraph( + it.data.answerStatistic.multipleChoiceRatio.toFloat(), + it.data.answerStatistic.sentenceRatio.toFloat() + ) + changeThemeGraph(themeGraphRatioList) + } + + is UiState.Error -> { + Toast.makeText(context,"잠시 후에 다시 시도해주세요!",Toast.LENGTH_SHORT).show() + } + + else -> {} + } + + } } } private fun setProfile(username: String, profileImgUrl: String) { + Log.d("profile","setProfile") binding.textviewProfileNickname.text = String.format(resources.getString(R.string.profile_username), username) Glide.with(this) .load(profileImgUrl) @@ -70,12 +99,14 @@ class ProfileFragment : Fragment(), ServerResponseHandler { @RequiresApi(Build.VERSION_CODES.O) private fun setStatic(totalChatTime: String, totalChatCount: Int) { + Log.d("profile","setStatic") val totalChatTime = stringToLocalTime(totalChatTime) binding.textviewTimeTaken.text = String.format(resources.getString(R.string.profile_time_taken_minute), totalChatTime.toSecondOfDay() / 60) binding.textviewConvCount.text = String.format(resources.getString(R.string.profile_conv_count_count), totalChatCount) } private fun changeTypeGraph(choiceRatio: Float, interactionRatio: Float) { + Log.d("profile","changeTypeGraph") changeGraphShape(R.drawable.shape_type_graph_fill_stroke_rect34_blue, choiceRatio, GraphPosition.LEFT) changeGraphShape(R.drawable.shape_type_graph_fill_stroke_rect34_pink, interactionRatio, GraphPosition.RIGHT) @@ -87,6 +118,7 @@ class ProfileFragment : Fragment(), ServerResponseHandler { } private fun changeThemeGraph(themeGraphRatioList: List) { + Log.d("profile","changeThemeGraph") val themeGraphBg: List = listOf( R.drawable.shape_theme_graph_fill_rect_orange700, R.drawable.shape_theme_graph_fill_rect_blue700, @@ -121,6 +153,7 @@ class ProfileFragment : Fragment(), ServerResponseHandler { changeGraphLayoutWeight(binding.viewProfileThemeGraphFamily, themeGraphRatioList[3]) changeGraphLayoutWeight(binding.viewProfileThemeGraphInterest, themeGraphRatioList[4]) + print(binding) binding.includeProfileGraphThemeDaily.textviewGraphPercent.text = String.format(resources.getString(R.string.percent), themeGraphRatioList[0].toInt()) binding.includeProfileGraphThemeSchool.textviewGraphPercent.text = String.format(resources.getString(R.string.percent), themeGraphRatioList[1].toInt()) binding.includeProfileGraphThemeFriend.textviewGraphPercent.text = String.format(resources.getString(R.string.percent), themeGraphRatioList[2].toInt()) @@ -129,6 +162,7 @@ class ProfileFragment : Fragment(), ServerResponseHandler { } private fun changeGraphLayoutWeight(view: View, ratio: Float) { + Log.d("profile","changeGraphLayoutWeight") val marginInDp = resources.getDimensionPixelSize(R.dimen.profile_graph_margin) view.layoutParams = LayoutParams( LayoutParams.WRAP_CONTENT, @@ -140,6 +174,7 @@ class ProfileFragment : Fragment(), ServerResponseHandler { } private fun changeGraphShape(graphDrawableId: Int, ratio: Float, position: GraphPosition) { + Log.d("profile","changeGraphShape") val drawable = context?.let { ContextCompat.getDrawable(it, graphDrawableId) } as? GradientDrawable if(ratio >= 100.0) { drawable?.cornerRadius = 40f @@ -158,6 +193,7 @@ class ProfileFragment : Fragment(), ServerResponseHandler { @RequiresApi(Build.VERSION_CODES.O) fun stringToLocalTime(timeString: String): LocalTime { + Log.d("profile","stringToLocalTime") val formatter = DateTimeFormatter.ofPattern("HH:mm:ss") return LocalTime.parse(timeString, formatter) @@ -172,10 +208,11 @@ class ProfileFragment : Fragment(), ServerResponseHandler { RIGHT, MIDDLE, LEFT } + @RequiresApi(Build.VERSION_CODES.O) override fun onServerSuccess() { - binding.constraintProfile.visibility = View.VISIBLE } override fun onServerFailure() { + TODO("Not yet implemented") } } \ No newline at end of file diff --git a/app/src/main/java/com/example/com_us/ui/profile/ProfileViewModel.kt b/app/src/main/java/com/example/com_us/ui/profile/ProfileViewModel.kt index c231e7e..9aec7d5 100644 --- a/app/src/main/java/com/example/com_us/ui/profile/ProfileViewModel.kt +++ b/app/src/main/java/com/example/com_us/ui/profile/ProfileViewModel.kt @@ -1,32 +1,41 @@ package com.example.com_us.ui.profile -import android.util.Log -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.example.com_us.base.data.NetworkError import com.example.com_us.data.repository.ProfileRepository -import com.example.com_us.data.response.question.ResponseProfileDto -import com.example.com_us.util.ServerResponseHandler +import com.example.com_us.data.model.question.response.question.ResponseProfileDto +import com.example.com_us.ui.base.UiState +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch +import javax.inject.Inject -class ProfileViewModel(private val profileRepository: ProfileRepository) : ViewModel() { +@HiltViewModel +class ProfileViewModel @Inject constructor(private val profileRepository: ProfileRepository) : ViewModel() { - var serverResponseHandler: ServerResponseHandler? = null + private val _profileUiState = MutableStateFlow>(UiState.Initial) + val profileUiState = _profileUiState.asStateFlow() - private val _profileData = MutableLiveData() - val profileData: LiveData = _profileData - fun loadProfileData(){ + + + fun loadProfileData() { viewModelScope.launch { profileRepository.getProfileData() .onSuccess { - _profileData.value = it - serverResponseHandler?.onServerSuccess() + _profileUiState.value = UiState.Success(it) } .onFailure { - Log.d("GET: [PROFILE DATA]", it.toString()) - serverResponseHandler?.onServerFailure() + val errorMessage = when (it) { + is NetworkError.NetworkException -> { it.message } + is NetworkError.NullDataError -> { "데이터가 준비중이에요!" } + else -> "알 수 없는 에러가 발생했습니다. 다시 시도해주세요!" + } + if (errorMessage != null) { + _profileUiState.value = UiState.Error(errorMessage) + } } } } diff --git a/app/src/main/java/com/example/com_us/ui/profile/ProfileViewModelFactory.kt b/app/src/main/java/com/example/com_us/ui/profile/ProfileViewModelFactory.kt deleted file mode 100644 index 155ce63..0000000 --- a/app/src/main/java/com/example/com_us/ui/profile/ProfileViewModelFactory.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.example.com_us.ui.profile - -import android.content.Context -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider -import com.example.com_us.data.ApiClient -import com.example.com_us.data.datasource.ProfileRemoteDataSource -import com.example.com_us.data.repository.ProfileRepository -import retrofit2.create -class ProfileViewModelFactory(private val context: Context): ViewModelProvider.Factory { - - override fun create(modelClass: Class): T { - return when { - modelClass.isAssignableFrom(ProfileViewModel::class.java) -> { - val repository = ProfileRepository(ProfileRemoteDataSource(ApiClient.getApiClient(context).create())) - ProfileViewModel(repository) as T - } - else -> { - throw IllegalArgumentException("Failed to create ViewModel : ${modelClass.name}") - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/example/com_us/ui/question/QuestionFollowAlongDialog.kt b/app/src/main/java/com/example/com_us/ui/question/QuestionFollowAlongDialog.kt deleted file mode 100644 index 0235fc4..0000000 --- a/app/src/main/java/com/example/com_us/ui/question/QuestionFollowAlongDialog.kt +++ /dev/null @@ -1,143 +0,0 @@ -package com.example.com_us.ui.question - -import android.content.Intent -import android.graphics.Color -import android.graphics.drawable.ColorDrawable -import android.net.Uri -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.Toast -import androidx.fragment.app.DialogFragment -import androidx.fragment.app.viewModels -import androidx.lifecycle.MutableLiveData -import com.example.com_us.R -import com.example.com_us.data.response.question.ResponseAnswerDetailDto -import com.example.com_us.databinding.DialogQuestionFollowAlongBinding -import com.example.com_us.util.QuestionManager -import com.example.com_us.util.ServerResponseHandler - -private const val ARG_PARAM_QUESTION = "paramQuestion" -private const val ARG_PARAM_ANSWER = "paramAnswer" -private const val ARG_PARAM_CATEGORY = "paramCategory" -private const val ARG_PARAM_SIGNDATA = "paramSignData" - -class QuestionFollowAlongDialog : DialogFragment(), ServerResponseHandler { - // TODO: Rename and change types of parameters - private lateinit var paramQuestion: String - private lateinit var paramAnswer: String - private lateinit var paramCategory: String - - private lateinit var signData: List - - private var videoPlayCount: MutableLiveData = MutableLiveData(0) - - private var _binding: DialogQuestionFollowAlongBinding? = null - private val binding get() = _binding!! - - private val questionViewModel: QuestionViewModel by viewModels { QuestionViewModelFactory(requireContext()) } - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - questionViewModel.serverResponseHandler = this - arguments?.let { - paramQuestion = it.getString(ARG_PARAM_QUESTION)!! - paramAnswer = it.getString(ARG_PARAM_ANSWER)!! - paramCategory = it.getString(ARG_PARAM_CATEGORY)!! - //paramSignData = it.getSerializable(ARG_PARAM_SIGNDATA) as List - } - } - - override fun onCreateView( - inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - _binding = DialogQuestionFollowAlongBinding.inflate(inflater, container, false) - val view = binding.root - dialog?.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) - signData = QuestionManager.signLanguageInfo - - setAnswerDetail() - getAnswerDate() - - binding.buttonAnswerComplete.setOnClickListener { - val questionId = QuestionManager.questionId - if(questionId > 0 && paramAnswer.isNotEmpty()) questionViewModel.postAnswer(questionId, paramAnswer) - } - - // Inflate the layout for this fragment - return view - } - - override fun onStart() { - super.onStart() - - dialog?.window?.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) - } - - companion object { - @JvmStatic - fun newInstance(paramQuestion: String, paramAnswer: String, paramCategory: String) = - QuestionFollowAlongDialog().apply { - arguments = Bundle().apply { - putString(ARG_PARAM_QUESTION, paramQuestion) - putString(ARG_PARAM_ANSWER, paramAnswer) - putString(ARG_PARAM_CATEGORY, paramCategory) - //putSerializable(ARG_PARAM_SIGNDATA, ArrayList(paramSignData)) - } - } - } - - override fun onDestroyView() { - super.onDestroyView() - _binding = null - } - - private fun setAnswerDetail() { - binding.textviewFollowdialogQuestion.text = paramQuestion - var repeatCount = 0 - - videoPlayCount.observe(this) { - if(it >= 0) setSignDetail(it) - } - - if(signData.size > 1) { - binding.videoviewFollowdialogSign.setOnCompletionListener { - if(videoPlayCount.value!! >= signData.size-1){ - if(++repeatCount > 3) { - videoPlayCount.value = -1 - repeatCount = 0 - } else { - videoPlayCount.value = 0 - } - } else - videoPlayCount.value = videoPlayCount.value?.plus(1) - - } - } - } - - private fun getAnswerDate() { - questionViewModel.resultData.observe(this) { - if(it != null) QuestionManager.answerDate = it.answerDate - } - } - - private fun setSignDetail(signIdx: Int) { - binding.textviewFollowdialogAnswer.text = signData[signIdx].signLanguageName - binding.videoviewFollowdialogSign.setVideoURI(Uri.parse(signData[signIdx].signLanguageVideoUrl)) - binding.videoviewFollowdialogSign.start() - binding.textviewFollodialogDescrp.text = signData[signIdx].signLanguageDescription - } - - override fun onServerSuccess() { - val intent = Intent(activity, QuestionCollectBlockActivity::class.java) - intent.putExtra("category", paramCategory) - startActivity(intent) - activity?.finish() - } - - override fun onServerFailure() { - Toast.makeText(context, getString(R.string.question_follow_along_server_failure), Toast.LENGTH_SHORT).show() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/example/com_us/ui/question/QuestionViewModel.kt b/app/src/main/java/com/example/com_us/ui/question/QuestionViewModel.kt deleted file mode 100644 index 73e2b72..0000000 --- a/app/src/main/java/com/example/com_us/ui/question/QuestionViewModel.kt +++ /dev/null @@ -1,122 +0,0 @@ -package com.example.com_us.ui.question - -import android.util.Log -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import com.example.com_us.R -import com.example.com_us.data.repository.QuestionRepository -import com.example.com_us.data.request.question.RequestAnswerDto -import com.example.com_us.data.response.question.ResponseAnswerDetailDto -import com.example.com_us.data.response.question.ResponseAnswerDetailWithDateDto -import com.example.com_us.data.response.question.ResponsePreviousAnswerDto -import com.example.com_us.data.response.question.ResponseQuestionDetailDto -import com.example.com_us.data.response.question.ResponseQuestionDto -import com.example.com_us.util.ServerResponseHandler -import kotlinx.coroutines.launch - -class QuestionViewModel(private val questionRepository: QuestionRepository) : ViewModel() { - - var serverResponseHandler: ServerResponseHandler? = null - - private val _selectedThemeId = MutableLiveData().apply { - value = R.id.include_theme_all - } - val selectedThemeId: LiveData = _selectedThemeId - - private val _selectedAnswerOptionId = MutableLiveData(-1) - val selectedAnswerOptionId: LiveData = _selectedAnswerOptionId - - private val _questionListByCate = MutableLiveData>() - val questionListByCate: LiveData> = _questionListByCate - - private val _questionDetail = MutableLiveData() - val questionDetail: LiveData = _questionDetail - - private val _answerDetail = MutableLiveData>() - val answerDetail: LiveData> = _answerDetail - - private val _answerPrevious = MutableLiveData() - val answerPrevious: LiveData = _answerPrevious - - private val _resultData = MutableLiveData() - val resultData: LiveData = _resultData - - fun updateSelectedThemeId(newId: Int) { - _selectedThemeId.value = newId - } - fun updateSelectedAnswerOptionId(newOptionId: Int) { - _selectedAnswerOptionId.value = newOptionId - } - fun loadQuestionListByCate(category: String){ - viewModelScope.launch { - questionRepository.getQuestionListByCate(category) - .onSuccess { - serverResponseHandler?.onServerSuccess() - _questionListByCate.value = it - } - .onFailure { - serverResponseHandler?.onServerFailure() - Log.d("GET: [QUESTION LIST BY CATE]", it.toString()) - } - } - } - - fun loadQuestionDetail(questionId: Long){ - viewModelScope.launch { - questionRepository.getQuestionDetail(questionId) - .onSuccess { - _questionDetail.value = it - serverResponseHandler?.onServerSuccess() - } - .onFailure { - serverResponseHandler?.onServerFailure() - Log.d("GET: [QUESTION DETAIL]", it.toString()) - } - } - } - - fun loadAnswerDetail(answer: String){ - viewModelScope.launch { - questionRepository.getAnswerDetail(answer) - .onSuccess { - _answerDetail.value = it - serverResponseHandler?.onServerSuccess() - } - .onFailure { - serverResponseHandler?.onServerFailure() - Log.d("GET: [ANSWER DETAIL]", it.toString()) - } - } - } - - fun postAnswer(questionId: Long, answerContent: String){ - var body = RequestAnswerDto(questionId, answerContent) - viewModelScope.launch { - questionRepository.postAnswer(body) - .onSuccess { - _resultData.value = it - serverResponseHandler?.onServerSuccess() - } - .onFailure { - serverResponseHandler?.onServerFailure() - Log.d("POST: [ANSWER]", it.toString()) - } - } - } - - fun loadPreviousAnswer(questionId: Long) { - viewModelScope.launch { - questionRepository.getPreviousAnswer(questionId) - .onSuccess { - _answerPrevious.value = it - serverResponseHandler?.onServerSuccess() - } - .onFailure { - serverResponseHandler?.onServerFailure() - Log.d("GET: [ANSWER DETAIL]", it.toString()) - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/example/com_us/ui/question/QuestionViewModelFactory.kt b/app/src/main/java/com/example/com_us/ui/question/QuestionViewModelFactory.kt deleted file mode 100644 index 93c31cb..0000000 --- a/app/src/main/java/com/example/com_us/ui/question/QuestionViewModelFactory.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.example.com_us.ui.question - -import android.content.Context -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider -import com.example.com_us.data.ApiClient -import com.example.com_us.data.datasource.QuestionRemoteDataSource -import com.example.com_us.data.repository.QuestionRepository -import retrofit2.create -class QuestionViewModelFactory(private val context: Context): ViewModelProvider.Factory { - - override fun create(modelClass: Class): T { - return when { - modelClass.isAssignableFrom(QuestionViewModel::class.java) -> { - val repository = QuestionRepository(QuestionRemoteDataSource(ApiClient.getApiClient(context).create())) - QuestionViewModel(repository) as T - } - else -> { - throw IllegalArgumentException("Failed to create ViewModel : ${modelClass.name}") - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/example/com_us/ui/question/QuestionCollectBlockActivity.kt b/app/src/main/java/com/example/com_us/ui/question/block/CollectBlockActivity.kt similarity index 73% rename from app/src/main/java/com/example/com_us/ui/question/QuestionCollectBlockActivity.kt rename to app/src/main/java/com/example/com_us/ui/question/block/CollectBlockActivity.kt index 013bced..fe7a211 100644 --- a/app/src/main/java/com/example/com_us/ui/question/QuestionCollectBlockActivity.kt +++ b/app/src/main/java/com/example/com_us/ui/question/block/CollectBlockActivity.kt @@ -1,15 +1,19 @@ -package com.example.com_us.ui.question +package com.example.com_us.ui.question.block import android.content.Intent import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import android.os.Handler import androidx.activity.OnBackPressedCallback -import com.example.com_us.R +import com.example.com_us.MainActivity import com.example.com_us.databinding.ActivityQuestionCollectBlockBinding +import com.example.com_us.ui.question.result.ResultAfterSignActivity import com.example.com_us.util.ThemeType +import dagger.hilt.android.AndroidEntryPoint -class QuestionCollectBlockActivity : AppCompatActivity() { +// 대화 블럭 획득 화면 +@AndroidEntryPoint +class CollectBlockActivity : AppCompatActivity() { private lateinit var binding: ActivityQuestionCollectBlockBinding private lateinit var category: String @@ -17,20 +21,20 @@ class QuestionCollectBlockActivity : AppCompatActivity() { super.onCreate(savedInstanceState) binding = ActivityQuestionCollectBlockBinding.inflate(layoutInflater) setContentView(binding.root) - category = intent.getStringExtra("category").toString() - if(!category.isNullOrEmpty()){ + if(category.isNotEmpty()){ setTheme() } controlBackButton() Handler().postDelayed({ - val intent = Intent(this, QuestionResultActivity::class.java) + val intent = Intent(this, ResultAfterSignActivity::class.java) + intent.putExtra("category",category) startActivity(intent) finish() - }, 1500) // 3초 + }, 3000) // 3초 } private fun setTheme() { diff --git a/app/src/main/java/com/example/com_us/ui/question/QuestionFragment.kt b/app/src/main/java/com/example/com_us/ui/question/list/AllQuestionListFragment.kt similarity index 52% rename from app/src/main/java/com/example/com_us/ui/question/QuestionFragment.kt rename to app/src/main/java/com/example/com_us/ui/question/list/AllQuestionListFragment.kt index 1edd595..2a7634a 100644 --- a/app/src/main/java/com/example/com_us/ui/question/QuestionFragment.kt +++ b/app/src/main/java/com/example/com_us/ui/question/list/AllQuestionListFragment.kt @@ -1,4 +1,4 @@ -package com.example.com_us.ui.question +package com.example.com_us.ui.question.list import android.content.Intent import android.os.Bundle @@ -6,6 +6,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.TextView +import android.widget.Toast import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.ui.Modifier @@ -13,24 +14,26 @@ import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.core.content.ContextCompat import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import com.example.com_us.R import com.example.com_us.databinding.FragmentQuestionBinding +import com.example.com_us.ui.base.UiState import com.example.com_us.ui.compose.QuestionListItem -import com.example.com_us.util.ServerResponseHandler +import com.example.com_us.ui.question.select.SelectAnswerActivity import com.example.com_us.util.ThemeType +import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.launch -class QuestionFragment : Fragment(), View.OnClickListener, ServerResponseHandler { +// 바텀 네비게이션바에서 햄버거 버튼 클릭 시 이동하는 질문 리스트 화면 +@AndroidEntryPoint +class AllQuestionListFragment : Fragment(), View.OnClickListener { private var _binding: FragmentQuestionBinding? = null - - // This property is only valid between onCreateView and - // onDestroyView. private val binding get() = _binding!! - - private val questionViewModel: QuestionViewModel by viewModels { QuestionViewModelFactory(requireContext()) } - + private val viewModel: AllQuestionListViewModel by viewModels() private var lastSelectedView: TextView? = null - private lateinit var selectedView:TextView private lateinit var themeKor:String @@ -39,50 +42,71 @@ class QuestionFragment : Fragment(), View.OnClickListener, ServerResponseHandler container: ViewGroup?, savedInstanceState: Bundle? ): View { - _binding = FragmentQuestionBinding.inflate(inflater, container, false) val root: View = binding.root + return root + } - questionViewModel.serverResponseHandler = this - questionViewModel.loadQuestionListByCate("") + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + val themeAllView = binding.includeThemeAll + viewModel.updateSelectedThemeId(themeAllView.textviewTheme.id) setThemeList() setComposeList() setThemeClickListener() - return root - } - - override fun onResume() { - super.onResume() - val themeAllView = binding.includeThemeAll - questionViewModel.updateSelectedThemeId(themeAllView.textviewTheme.id) } + // 선택한 카테고리에 맞는 질문 리스트 얻기 private fun setThemeList() { - questionViewModel.selectedThemeId.observe(viewLifecycleOwner) { + viewModel.selectedThemeId.observe(viewLifecycleOwner) { selectedView = binding.root.findViewById(it) themeKor = selectedView.text.toString() var category = ThemeType.fromKor(themeKor).toString() if(category == ThemeType.ALL.toString()) category = "" - questionViewModel.loadQuestionListByCate(category) + viewModel.loadQuestionListByCate(category) } } + // 각 질문을 담을 아이템 private fun setComposeList() { - questionViewModel.questionListByCate.observe(viewLifecycleOwner) { - binding.textviewQuestionCount.text = String.format(resources.getString(R.string.question_title_question_count), it.size) - binding.composeviewQuestion.apply { - setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) - setContent { - LazyColumn(modifier = Modifier.fillMaxSize()) { - items(it.size) { idx -> - QuestionListItem(data = it[idx], onClick = { moveToQuestionDetail(it[idx].id) }) + // 데이터 리스트 + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED){ + viewModel.apiResult.collect { + when(it) { + is UiState.Success -> { + if (it.data.isNotEmpty()) { + setThemeSelected() + binding.constraintQuestion.visibility = View.VISIBLE + binding.textviewQuestionCount.text = String.format( + resources.getString(R.string.question_title_question_count), + it.data.size + ) + binding.composeviewQuestion.apply { + setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) + setContent { + LazyColumn(modifier = Modifier.fillMaxSize()) { + items(it.data.size) { idx -> + QuestionListItem( + data = it.data[idx], + onClick = { movieToSelectAnswer(it.data[idx].id) }) + } + } + } + } + } + } + is UiState.Error -> { + binding.constraintQuestion.visibility = View.GONE + Toast.makeText(context, it.toString(), Toast.LENGTH_SHORT).show() } + else -> {} } } } } + } private fun setThemeClickListener(){ @@ -111,14 +135,14 @@ class QuestionFragment : Fragment(), View.OnClickListener, ServerResponseHandler lastSelectedView = selectedView } - private fun moveToQuestionDetail(questionId: Long) { - val intent = Intent(activity, QuestionDetailActivity::class.java) + private fun movieToSelectAnswer(questionId: Long) { + val intent = Intent(activity, SelectAnswerActivity::class.java) intent.putExtra("questionId", questionId) startActivity(intent) } override fun onClick(view: View) { - questionViewModel.updateSelectedThemeId(view.id) + viewModel.updateSelectedThemeId(view.id) } override fun onDestroyView() { @@ -126,11 +150,4 @@ class QuestionFragment : Fragment(), View.OnClickListener, ServerResponseHandler _binding = null } - override fun onServerSuccess() { - setThemeSelected() - binding.constraintQuestion.visibility = View.VISIBLE - } - - override fun onServerFailure() { - } } \ No newline at end of file diff --git a/app/src/main/java/com/example/com_us/ui/question/list/AllQuestionListViewModel.kt b/app/src/main/java/com/example/com_us/ui/question/list/AllQuestionListViewModel.kt new file mode 100644 index 0000000..5588761 --- /dev/null +++ b/app/src/main/java/com/example/com_us/ui/question/list/AllQuestionListViewModel.kt @@ -0,0 +1,71 @@ +package com.example.com_us.ui.question.list + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.example.com_us.R +import com.example.com_us.base.data.NetworkError +import com.example.com_us.data.model.question.response.question.ResponseQuestionDto +import com.example.com_us.data.repository.QuestionRepository +import com.example.com_us.ui.base.UiState +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import javax.inject.Inject + + +@HiltViewModel +class AllQuestionListViewModel @Inject constructor( + private val questionRepository: QuestionRepository +) : ViewModel() { + + init { + loadQuestionListByCate("") + } + + // 질문 리스트 + private val _questionListByCate = MutableLiveData>() + val questionListByCate: LiveData> = _questionListByCate + + + // ui 상태 변수 + private val _uiState = MutableStateFlow>>(UiState.Initial) + val apiResult = _uiState.asStateFlow() + + + // 선택한 테마의 id + private val _selectedThemeId = MutableLiveData().apply { + value = R.id.include_theme_all + } + val selectedThemeId: LiveData = _selectedThemeId + + + + fun updateSelectedThemeId(newId: Int) { + _selectedThemeId.value = newId + } + + + // 클릭한 카테고리의 질문 리스트를 가져오는 함수 + fun loadQuestionListByCate(category: String){ + viewModelScope.launch { + questionRepository.getQuestionListByCate(category) + .onSuccess { + _uiState.value = UiState.Success(it) + } + .onFailure { + val errorMessage = when(it){ + is NetworkError.NetworkException -> {it.message} + is NetworkError.NullDataError -> {"아직 데이터를 준비중이에요!"} + else -> "알 수 없는 에러가 발생했습니다. 다시 시도해주세요!" + } + if (errorMessage != null) { + _uiState.value = UiState.Error(errorMessage) + } } + } + } + + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/com_us/ui/question/QuestionPreviousAnswerActivity.kt b/app/src/main/java/com/example/com_us/ui/question/previous/PreviousAnswerActivity.kt similarity index 70% rename from app/src/main/java/com/example/com_us/ui/question/QuestionPreviousAnswerActivity.kt rename to app/src/main/java/com/example/com_us/ui/question/previous/PreviousAnswerActivity.kt index 1f6e84f..d73636f 100644 --- a/app/src/main/java/com/example/com_us/ui/question/QuestionPreviousAnswerActivity.kt +++ b/app/src/main/java/com/example/com_us/ui/question/previous/PreviousAnswerActivity.kt @@ -1,25 +1,34 @@ -package com.example.com_us.ui.question +package com.example.com_us.ui.question.previous import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import android.view.MenuItem +import android.widget.Toast import androidx.activity.viewModels import androidx.compose.foundation.layout.Row import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.ui.platform.ViewCompositionStrategy +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import com.example.com_us.R -import com.example.com_us.data.response.question.Answer +import com.example.com_us.data.model.question.response.question.Answer import com.example.com_us.databinding.ActivityQuestionPreviousAnswerBinding +import com.example.com_us.ui.base.UiState import com.example.com_us.ui.compose.AnswerHistoryItem import com.example.com_us.ui.compose.AnswerTypeTag import com.example.com_us.util.ColorMatch +import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.launch -class QuestionPreviousAnswerActivity : AppCompatActivity() { +// 이전 답변을 보여주는 화면 +@AndroidEntryPoint +class PreviousAnswerActivity : AppCompatActivity() { private lateinit var binding: ActivityQuestionPreviousAnswerBinding - private val questionViewModel: QuestionViewModel by viewModels { QuestionViewModelFactory(applicationContext) } + private val viewModel: PreviousAnswerViewModel by viewModels() private var questionId: Long = -1 override fun onCreate(savedInstanceState: Bundle?) { @@ -33,13 +42,25 @@ class QuestionPreviousAnswerActivity : AppCompatActivity() { questionId = intent.getLongExtra("questionId", -1) println(questionId) - if(questionId > -1) questionViewModel.loadPreviousAnswer(questionId) + if(questionId > -1) viewModel.loadPreviousAnswer(questionId) + + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED){ + viewModel.uiState.collect{ + when(it){ + is UiState.Success -> { + setQuestion(it.data.question.questionCount, it.data.question.questionContent) + setQuestionTypeCompose(it.data.question.category, it.data.question.answerType) + setComposeList(it.data.answerList) + } + else -> + Toast.makeText(this@PreviousAnswerActivity, it.toString(), Toast.LENGTH_SHORT).show() + + } + } - questionViewModel.answerPrevious.observe(this) { - setQuestion(it.question.questionCount, it.question.questionContent) - setQuestionTypeCompose(it.question.category, it.question.answerType) - setComposeList(it.answerList) - } + } + } } private fun setActionBar() { diff --git a/app/src/main/java/com/example/com_us/ui/question/previous/PreviousAnswerViewModel.kt b/app/src/main/java/com/example/com_us/ui/question/previous/PreviousAnswerViewModel.kt new file mode 100644 index 0000000..8543d21 --- /dev/null +++ b/app/src/main/java/com/example/com_us/ui/question/previous/PreviousAnswerViewModel.kt @@ -0,0 +1,49 @@ +package com.example.com_us.ui.question.previous + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.example.com_us.base.data.NetworkError +import com.example.com_us.data.model.question.response.question.ResponsePreviousAnswerDto +import com.example.com_us.data.repository.QuestionRepository +import com.example.com_us.ui.base.UiState +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import javax.inject.Inject + + + +@HiltViewModel +class PreviousAnswerViewModel @Inject constructor( + private val questionRepository: QuestionRepository +) : ViewModel(){ + + // ui 상태 변수 + private val _uiState = MutableStateFlow>(UiState.Initial) + val uiState= _uiState.asStateFlow() + + private val _answerPrevious = MutableLiveData() + val answerPrevious: LiveData = _answerPrevious + + fun loadPreviousAnswer(questionId: Long) { + viewModelScope.launch { + questionRepository.getPreviousAnswer(questionId) + .onSuccess { + _uiState.value = UiState.Success(it) + } + .onFailure { + val errorMessage = when(it){ + is NetworkError.NetworkException -> {it.message} + is NetworkError.NullDataError -> "데이터가 없어요" + else -> "알 수 없는 에러가 발생했어요. 다시 시도해주세요!" + } + if (errorMessage != null) { + _uiState.value = UiState.Error(errorMessage) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/com_us/ui/question/QuestionResultActivity.kt b/app/src/main/java/com/example/com_us/ui/question/result/ResultAfterSignActivity.kt similarity index 83% rename from app/src/main/java/com/example/com_us/ui/question/QuestionResultActivity.kt rename to app/src/main/java/com/example/com_us/ui/question/result/ResultAfterSignActivity.kt index 4827018..1ecfb7c 100644 --- a/app/src/main/java/com/example/com_us/ui/question/QuestionResultActivity.kt +++ b/app/src/main/java/com/example/com_us/ui/question/result/ResultAfterSignActivity.kt @@ -1,4 +1,4 @@ -package com.example.com_us.ui.question +package com.example.com_us.ui.question.result import android.content.Intent import android.net.Uri @@ -8,13 +8,18 @@ import android.view.MenuItem import androidx.lifecycle.MutableLiveData import com.example.com_us.MainActivity import com.example.com_us.R -import com.example.com_us.data.response.question.ResponseAnswerDetailDto +import com.example.com_us.data.model.question.response.question.ResponseAnswerDetailDto import com.example.com_us.databinding.ActivityQuestionResultBinding +import com.example.com_us.ui.question.block.CollectBlockActivity import com.example.com_us.util.QuestionManager +import dagger.hilt.android.AndroidEntryPoint -class QuestionResultActivity : AppCompatActivity() { +// 수형 따라해보기 완료 후 대화 블럭 얻기 전 답변 완료 화면 +@AndroidEntryPoint +class ResultAfterSignActivity : AppCompatActivity() { private lateinit var binding: ActivityQuestionResultBinding + private lateinit var category : String private var videoPlayCount: MutableLiveData = MutableLiveData(0) @@ -32,6 +37,7 @@ class QuestionResultActivity : AppCompatActivity() { if(!QuestionManager.answerDate.isNullOrEmpty()) { binding.textviewResultDate.text = QuestionManager.answerDate } + category = intent.getStringExtra("category").toString() setActionBar() setCompleteButton() @@ -88,7 +94,8 @@ class QuestionResultActivity : AppCompatActivity() { private fun setCompleteButton() { binding.buttonResultComplete.setOnClickListener{ QuestionManager.reset() - val intent = Intent(this, MainActivity::class.java) + // 메인 화면으로 이동 + val intent = Intent(this,MainActivity::class.java) startActivity(intent) finish() } diff --git a/app/src/main/java/com/example/com_us/ui/question/QuestionCheckAnswerActivity.kt b/app/src/main/java/com/example/com_us/ui/question/result/ResultBeforeSignActivity.kt similarity index 50% rename from app/src/main/java/com/example/com_us/ui/question/QuestionCheckAnswerActivity.kt rename to app/src/main/java/com/example/com_us/ui/question/result/ResultBeforeSignActivity.kt index c38cab1..1219258 100644 --- a/app/src/main/java/com/example/com_us/ui/question/QuestionCheckAnswerActivity.kt +++ b/app/src/main/java/com/example/com_us/ui/question/result/ResultBeforeSignActivity.kt @@ -1,23 +1,31 @@ -package com.example.com_us.ui.question +package com.example.com_us.ui.question.result import android.net.Uri import android.os.Bundle import android.view.MenuItem +import android.view.View import android.widget.Toast import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.Lifecycle import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import com.example.com_us.R -import com.example.com_us.data.response.question.ResponseAnswerDetailDto +import com.example.com_us.data.model.question.response.question.ResponseAnswerDetailDto import com.example.com_us.databinding.ActivityQuestionCheckAnswerBinding +import com.example.com_us.ui.base.UiState +import com.example.com_us.ui.question.sign.SignAnswerDialog import com.example.com_us.util.QuestionManager -import com.example.com_us.util.ServerResponseHandler +import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.launch - -class QuestionCheckAnswerActivity : AppCompatActivity(), ServerResponseHandler { +// 답변 선택 시 처음으로 이동하는 화면 (질문, 답변 , 수형 영상, 따라해보기 버튼) +@AndroidEntryPoint +class ResultBeforeSignActivity : AppCompatActivity(){ private lateinit var binding: ActivityQuestionCheckAnswerBinding - private val questionViewModel: QuestionViewModel by viewModels { QuestionViewModelFactory(this) } + private val viewModel: ResultViewModel by viewModels() private lateinit var answer: String private lateinit var question: String @@ -29,31 +37,43 @@ class QuestionCheckAnswerActivity : AppCompatActivity(), ServerResponseHandler { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - questionViewModel.serverResponseHandler = this answer = intent.getStringExtra("answer").toString() question = intent.getStringExtra("question").toString() category = intent.getStringExtra("category").toString() - if(!answer.isNullOrEmpty()){ + if (answer.isNotEmpty()) { QuestionManager.question = question - questionViewModel.loadAnswerDetail(answer) + viewModel.loadAnswerDetail(answer) } binding = ActivityQuestionCheckAnswerBinding.inflate(layoutInflater) setContentView(binding.root) setActionBar() - questionViewModel.answerDetail.observe(this) { - if(!it.isNullOrEmpty()){ - QuestionManager.signLanguageInfo = it - signData = it - setAnswerDetail() - binding.buttonAnswerFollowalong.setOnClickListener { - moveToFollowAlongDialog() + + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + viewModel.answerDetail.collect { + when (it) { + is UiState.Success -> { + QuestionManager.signLanguageInfo = it.data + signData = it.data + setAnswerDetail() + binding.buttonAnswerFollowalong.setOnClickListener { + moveToFollowAlongDialog() + } + } + + is UiState.Error -> { + Toast.makeText(this@ResultBeforeSignActivity, it.toString(), Toast.LENGTH_SHORT).show() + } + else -> {} + } } } } + } private fun setActionBar() { @@ -105,15 +125,43 @@ class QuestionCheckAnswerActivity : AppCompatActivity(), ServerResponseHandler { } private fun moveToFollowAlongDialog() { videoPlayCount.value = -1 - val dialog = QuestionFollowAlongDialog.newInstance(question, answer, category) - dialog.show(supportFragmentManager, "FollowAlongDialog") - } - override fun onServerSuccess() { - } + viewModel.answerDetail.value + viewModel.answerDetail.value.let { + when(it) { + is UiState.Success -> { + val dialog = + SignAnswerDialog.newInstance(question, answer, category, + it.data + ) + dialog.isCancelable = false + dialog.show(supportFragmentManager, "FollowAlongDialog") + + } + else -> { + Toast.makeText(this , "잠시 후에 다시 시도해주세요",Toast.LENGTH_SHORT).show() + } + } + } + + print(viewModel.answerDetail.value) + + // 다이얼로그가 뜨면 아래 내용 질문만 희미하게 보이게 하기 + binding.textView8.visibility = View.GONE + binding.textviewAnswerAnswer.visibility = View.GONE + binding.textView13.visibility = View.GONE + binding.textviewAnswerDescrp.visibility = View.GONE + binding.buttonAnswerFollowalong.visibility = View.GONE + binding.videoviewAnswerSign.visibility = View.GONE - override fun onServerFailure() { - Toast.makeText(this, getString(R.string.server_data_error), Toast.LENGTH_SHORT).show() - finish() } + + +// override fun onServerSuccess() { +// } +// +// override fun onServerFailure() { +// Toast.makeText(this, getString(R.string.server_data_error), Toast.LENGTH_SHORT).show() +// finish() +// } } \ No newline at end of file diff --git a/app/src/main/java/com/example/com_us/ui/question/result/ResultViewModel.kt b/app/src/main/java/com/example/com_us/ui/question/result/ResultViewModel.kt new file mode 100644 index 0000000..4d09d90 --- /dev/null +++ b/app/src/main/java/com/example/com_us/ui/question/result/ResultViewModel.kt @@ -0,0 +1,37 @@ +package com.example.com_us.ui.question.result + +import android.util.Log +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.example.com_us.data.model.question.response.question.ResponseAnswerDetailDto +import com.example.com_us.data.repository.QuestionRepository +import com.example.com_us.ui.base.UiState +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class ResultViewModel @Inject constructor( + private val questionRepository : QuestionRepository +) : ViewModel() { + + private val _answerDetail = MutableStateFlow>>(UiState.Initial) + val answerDetail =_answerDetail.asStateFlow() + + + fun loadAnswerDetail(answer: String){ + viewModelScope.launch { + questionRepository.getAnswerDetail(answer) + .onSuccess { + _answerDetail.value = UiState.Success(it) + } + .onFailure { + Log.d("GET: [ANSWER DETAIL]", it.toString()) + } + } + } + + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/com_us/ui/question/QuestionDetailActivity.kt b/app/src/main/java/com/example/com_us/ui/question/select/SelectAnswerActivity.kt similarity index 60% rename from app/src/main/java/com/example/com_us/ui/question/QuestionDetailActivity.kt rename to app/src/main/java/com/example/com_us/ui/question/select/SelectAnswerActivity.kt index 1488f1d..c7d2f89 100644 --- a/app/src/main/java/com/example/com_us/ui/question/QuestionDetailActivity.kt +++ b/app/src/main/java/com/example/com_us/ui/question/select/SelectAnswerActivity.kt @@ -1,4 +1,4 @@ -package com.example.com_us.ui.question +package com.example.com_us.ui.question.select import android.content.Intent import androidx.appcompat.app.AppCompatActivity @@ -9,15 +9,22 @@ import androidx.activity.viewModels import androidx.compose.foundation.layout.Row import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.core.content.ContextCompat +import androidx.lifecycle.lifecycleScope import com.example.com_us.R import com.example.com_us.databinding.ActivityQuestionDetailBinding +import com.example.com_us.ui.base.UiState import com.example.com_us.util.ColorMatch import com.example.com_us.ui.compose.AnswerOptionList import com.example.com_us.ui.compose.AnswerTypeTag +import com.example.com_us.ui.question.result.ResultBeforeSignActivity +import com.example.com_us.ui.question.previous.PreviousAnswerActivity import com.example.com_us.util.QuestionManager -import com.example.com_us.util.ServerResponseHandler +import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.launch -class QuestionDetailActivity : AppCompatActivity(), ServerResponseHandler { +// 질문에 대한 답변을 선택하는 화면 +@AndroidEntryPoint +class SelectAnswerActivity : AppCompatActivity() { private lateinit var binding: ActivityQuestionDetailBinding private lateinit var answerList: List @@ -26,26 +33,25 @@ class QuestionDetailActivity : AppCompatActivity(), ServerResponseHandler { private var questionId: Long = -1 private var answerOptionId: Int = -1 - private val questionViewModel: QuestionViewModel by viewModels { QuestionViewModelFactory(this) } + private val viewModel: SelectAnswerViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityQuestionDetailBinding.inflate(layoutInflater) setContentView(binding.root) - questionViewModel.serverResponseHandler = this questionId = intent.getLongExtra("questionId", 0L) if(questionId > 0) { QuestionManager.questionId = questionId - questionViewModel.loadQuestionDetail(questionId) + viewModel.loadQuestionDetail(questionId) setPreviousAnswerButton() } setQuestionDetail() - questionViewModel.selectedAnswerOptionId.observe(this) { + viewModel.selectedAnswerOptionId.observe(this) { if (it > -1) { setCompleteButton() answerOptionId = it @@ -54,13 +60,29 @@ class QuestionDetailActivity : AppCompatActivity(), ServerResponseHandler { } private fun setQuestionDetail() { - questionViewModel.questionDetail.observe(this) { - binding.textviewDetailQuestion.text = it.question.questionContent - question = it.question.questionContent - category = it.question.category - setQuestionTypeCompose(it.question.answerType) - setQuestionAnswerOptionCompose(it.answerList) - } + lifecycleScope.launch { + viewModel.uiState.collect { + if (it is UiState.Success) { + binding.constraintQuestionDetail.visibility = View.VISIBLE + binding.textviewDetailQuestion.text = it.data.question.questionContent + question = it.data.question.questionContent + category = it.data.question.category + setQuestionTypeCompose(it.data.question.answerType) + setQuestionAnswerOptionCompose(it.data.answerList) + } + + if (it is UiState.Error) { + Toast.makeText( + this@SelectAnswerActivity, + it.message, + Toast.LENGTH_SHORT + ).show() + finish() + } + } + + } + } private fun setQuestionTypeCompose(answerType: String) { @@ -80,7 +102,7 @@ class QuestionDetailActivity : AppCompatActivity(), ServerResponseHandler { binding.composeDetailAnsweroption.apply { setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) setContent { - AnswerOptionList(answerList, questionViewModel) + AnswerOptionList(answerList, viewModel) } } } @@ -103,14 +125,14 @@ class QuestionDetailActivity : AppCompatActivity(), ServerResponseHandler { private fun setPreviousAnswerButton() { binding.buttonDetailAnswerbefore.setOnClickListener { - val intent = Intent(this, QuestionPreviousAnswerActivity::class.java) + val intent = Intent(this, PreviousAnswerActivity::class.java) intent.putExtra("questionId", questionId) startActivity(intent) } } private fun moveToQuestionAnswer(answerOptionId: Int) { - val intent = Intent(this, QuestionCheckAnswerActivity::class.java) + val intent = Intent(this, ResultBeforeSignActivity::class.java) intent.putExtra("question", question) intent.putExtra("category", category) intent.putExtra("answer", answerList[answerOptionId]) @@ -118,12 +140,12 @@ class QuestionDetailActivity : AppCompatActivity(), ServerResponseHandler { finish() } - override fun onServerSuccess() { - binding.constraintQuestionDetail.visibility = View.VISIBLE - } - - override fun onServerFailure() { - Toast.makeText(this, getString(R.string.question_detail_msg_when_server_failure), Toast.LENGTH_SHORT).show() - finish() - } +// override fun onServerSuccess() { +// binding.constraintQuestionDetail.visibility = View.VISIBLE +// } +// +// override fun onServerFailure() { +// Toast.makeText(this, getString(R.string.question_detail_msg_when_server_failure), Toast.LENGTH_SHORT).show() +// finish() +// } } \ No newline at end of file diff --git a/app/src/main/java/com/example/com_us/ui/question/select/SelectAnswerViewModel.kt b/app/src/main/java/com/example/com_us/ui/question/select/SelectAnswerViewModel.kt new file mode 100644 index 0000000..ac8ab07 --- /dev/null +++ b/app/src/main/java/com/example/com_us/ui/question/select/SelectAnswerViewModel.kt @@ -0,0 +1,56 @@ +package com.example.com_us.ui.question.select + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.example.com_us.base.data.NetworkError +import com.example.com_us.data.model.question.response.question.ResponseQuestionDetailDto +import com.example.com_us.data.repository.QuestionRepository +import com.example.com_us.ui.base.UiState +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class SelectAnswerViewModel @Inject constructor( + private val questionRepository: QuestionRepository +): ViewModel() { + + // ui 상태 변수 + private val _uiState = MutableStateFlow>(UiState.Initial) + val uiState: StateFlow> = _uiState + + private val _questionDetail = MutableLiveData() + val questionDetail: LiveData = _questionDetail + + private val _selectedAnswerOptionId = MutableLiveData(-1) + val selectedAnswerOptionId: LiveData = _selectedAnswerOptionId + + fun updateSelectedAnswerOptionId(newOptionId: Int) { + _selectedAnswerOptionId.value = newOptionId + } + + // 질문 클릭 시 상세 내용 가져오는 함수 + fun loadQuestionDetail(questionId: Long){ + viewModelScope.launch { + questionRepository.getQuestionDetail(questionId) + .onSuccess { + _uiState.value = UiState.Success(it) + } + .onFailure { + println(it) + val errorMessage = when(it) { + is NetworkError.ApiError -> { it.message } + is NetworkError.NullDataError -> { "데이터가 없어요" } + is NetworkError.NetworkException -> { "잠시 후에 다시 시도해주세요" } + else -> "알 수 없는 에러가 발생했어요. 다시 시도해주세요!" + } + _uiState.value = UiState.Error(errorMessage) + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/com_us/ui/question/sign/SignAnswerDialog.kt b/app/src/main/java/com/example/com_us/ui/question/sign/SignAnswerDialog.kt new file mode 100644 index 0000000..3a31e19 --- /dev/null +++ b/app/src/main/java/com/example/com_us/ui/question/sign/SignAnswerDialog.kt @@ -0,0 +1,120 @@ +package com.example.com_us.ui.question.sign + +import android.content.Context +import android.content.Intent +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.net.Uri +import android.os.Bundle +import android.util.Printer +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Toast +import androidx.fragment.app.DialogFragment +import androidx.fragment.app.viewModels +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.viewModelScope +import com.example.com_us.data.model.question.response.question.ResponseAnswerDetailDto +import com.example.com_us.databinding.DialogQuestionFollowAlongBinding +import com.example.com_us.ui.question.block.CollectBlockActivity +import com.example.com_us.ui.question.result.ResultAfterSignActivity +import com.example.com_us.util.QuestionManager +import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlin.math.sign + +// 수형 따라해보기 화면 +@AndroidEntryPoint +class SignAnswerDialog( + private val question : String, + private val answer : String, + private val category : String, + private val signDescription: List, +) : DialogFragment() { + private lateinit var signData: List + private var index = 0 + + private var _binding: DialogQuestionFollowAlongBinding? = null + private val binding get() = _binding!! + + private val viewModel: SignAnswerViewModel by viewModels() + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + _binding = DialogQuestionFollowAlongBinding.inflate(inflater, container, false) + val view = binding.root + dialog?.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) + signData = signDescription + return view + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + setSignDetail(0) + + binding.btnCompleteWithoutBlock.setOnClickListener { + val intent = Intent(context,ResultAfterSignActivity::class.java) + startActivity(intent) + } + binding.buttonNextStep.setOnClickListener { + if (viewModel.signIndex.value == signData.lastIndex) { + val intent = Intent(context,CollectBlockActivity::class.java) + intent.putExtra("category",category) + startActivity(intent) + } + else { + index +=1 + viewModel.setSignIndex(index) + setSignDetail(index) + } + + } + binding.linearProgressIndicator.progress = 50 + binding.textviewFollowdialogQuestion.text = question + } + + override fun onStart() { + super.onStart() + dialog?.window?.setLayout( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) + } + + companion object { + fun newInstance(paramQuestion: String, paramAnswer: String, paramCategory: String,signDescription: List) = + SignAnswerDialog(paramQuestion,paramAnswer,paramCategory, signDescription).apply { + arguments = Bundle() + } + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } + + + + // todo: 역할이 뭐지? + private fun getAnswerDate() { + viewModel.resultData.observe(this) { + if(it != null) QuestionManager.answerDate = it.answerDate + } + } + + private fun setSignDetail(signIdx: Int) { + if (signIdx > signData.lastIndex) return + if (viewModel.signIndex.value == signData.lastIndex) { + binding.buttonNextStep.text = "완료하기" + binding.btnCompleteWithoutBlock.visibility = View.GONE + } + binding.textviewFollowdialogAnswer.text = signData[signIdx].signLanguageName + binding.videoviewFollowdialogSign.setVideoURI(Uri.parse(signData[signIdx].signLanguageVideoUrl)) + binding.videoviewFollowdialogSign.start() + binding.textviewFollodialogDescrp.text = signData[signIdx].signLanguageDescription + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/com_us/ui/question/sign/SignAnswerViewModel.kt b/app/src/main/java/com/example/com_us/ui/question/sign/SignAnswerViewModel.kt new file mode 100644 index 0000000..5babe3a --- /dev/null +++ b/app/src/main/java/com/example/com_us/ui/question/sign/SignAnswerViewModel.kt @@ -0,0 +1,57 @@ +package com.example.com_us.ui.question.sign + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.example.com_us.base.data.NetworkError +import com.example.com_us.data.repository.QuestionRepository +import com.example.com_us.data.model.question.request.RequestAnswerRequest +import com.example.com_us.data.model.question.response.question.ResponseAnswerDetailWithDateDto +import com.example.com_us.ui.base.UiState +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import javax.inject.Inject + + +@HiltViewModel +class SignAnswerViewModel @Inject constructor( + private val questionRepository: QuestionRepository, +) : ViewModel() { + + + private val _signIndex = MutableStateFlow(0) + val signIndex = _signIndex.asStateFlow() + + private val _uiState = MutableStateFlow>(UiState.Initial) + val uiState= _uiState.asStateFlow() + private val _resultData = MutableLiveData() + val resultData: LiveData = _resultData + + fun setSignIndex(index : Int ) { + _signIndex.value = index + } +// todo : 이 함수의 역할은 뭐야? + fun postAnswer(questionId: Long, answerContent: String){ + val body = RequestAnswerRequest(questionId, answerContent) + viewModelScope.launch { + questionRepository.postAnswer(body) + .onSuccess { + _uiState.value = UiState.Success(it) + } + .onFailure { + val errorMessage = when(it) { + is NetworkError.NetworkException -> { "네트워크 에러가 발생했어요! 잠시 후에 다시 시도해주세에요"} + is NetworkError.ApiError ->{it.message} + is NetworkError.NullDataError -> {"데이터를 준비하고 있어요!"} + else -> { "잠시 후에 다시 시도해주세요"} + } + _uiState.value = UiState.Error(errorMessage) + } + } + } + + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/com_us/ui/home/HomeThemeQuestionListActivity.kt b/app/src/main/java/com/example/com_us/ui/question/theme/ThemeQuestionListActivity.kt similarity index 51% rename from app/src/main/java/com/example/com_us/ui/home/HomeThemeQuestionListActivity.kt rename to app/src/main/java/com/example/com_us/ui/question/theme/ThemeQuestionListActivity.kt index 320d466..6cf58d5 100644 --- a/app/src/main/java/com/example/com_us/ui/home/HomeThemeQuestionListActivity.kt +++ b/app/src/main/java/com/example/com_us/ui/question/theme/ThemeQuestionListActivity.kt @@ -1,66 +1,83 @@ -package com.example.com_us.ui.home +package com.example.com_us.ui.question.theme import android.content.Intent import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import android.view.MenuItem +import android.widget.Toast import androidx.activity.viewModels import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.ui.platform.ViewCompositionStrategy +import androidx.lifecycle.lifecycleScope import com.example.com_us.R import com.example.com_us.databinding.ActivityThemeQuestionListBinding -import com.example.com_us.ui.question.QuestionDetailActivity +import com.example.com_us.ui.base.UiState +import com.example.com_us.ui.question.select.SelectAnswerActivity import com.example.com_us.ui.compose.QuestionListItem -import com.example.com_us.ui.question.QuestionViewModel -import com.example.com_us.ui.question.QuestionViewModelFactory +import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.launch -class HomeThemeQuestionListActivity : AppCompatActivity() { +@AndroidEntryPoint +class ThemeQuestionListActivity : AppCompatActivity() { private lateinit var binding: ActivityThemeQuestionListBinding - private val questionViewModel: QuestionViewModel by viewModels { QuestionViewModelFactory(applicationContext) } + private val viewModel: ThemeQuestionListViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityThemeQuestionListBinding.inflate(layoutInflater) + val theme = intent.getStringExtra("theme").toString() + viewModel.loadQuestionListByCate(theme) + setContentView(binding.root) + } - val theme = intent.getStringExtra("theme").toString() + override fun onStart() { + super.onStart() val themeKor = intent.getStringExtra("themeKor") binding.textviewTitle.text = String.format(resources.getString(R.string.theme_question_list_title), themeKor) - - questionViewModel.loadQuestionListByCate(theme) - setActionBar() setComposeList() } private fun setComposeList() { - questionViewModel.questionListByCate.observe(this) { - binding.composeviewTheme.apply { - setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) - setContent { - LazyColumn { - items(it.size) { idx -> - QuestionListItem(data = it[idx], onClick = { moveToQuestionDetail(it[idx].id) }) + lifecycleScope.launch { + viewModel.uiState.collect { + when (it) { + is UiState.Success -> { + binding.composeviewTheme.apply { + setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) + setContent { + LazyColumn { + println(it.data) + items(it.data.size) { idx -> + QuestionListItem( + data = it.data[idx], + onClick = { moveToQuestionDetail(it.data[idx].id) }) + } + } + } } } + is UiState.Error -> { + Toast.makeText(this@ThemeQuestionListActivity,it.toString(),Toast.LENGTH_SHORT).show() + } + else -> {} } } } } - private fun setActionBar() { setSupportActionBar(binding.includeToolbar.toolbar) - val actionBar = supportActionBar actionBar?.setDisplayHomeAsUpEnabled(true) actionBar?.setHomeAsUpIndicator(R.drawable.ic_arrow_left) } private fun moveToQuestionDetail(questionId: Long) { - val intent = Intent(this, QuestionDetailActivity::class.java) + val intent = Intent(this, SelectAnswerActivity::class.java) intent.putExtra("questionId", questionId) startActivity(intent) } diff --git a/app/src/main/java/com/example/com_us/ui/question/theme/ThemeQuestionListViewModel.kt b/app/src/main/java/com/example/com_us/ui/question/theme/ThemeQuestionListViewModel.kt new file mode 100644 index 0000000..1e72890 --- /dev/null +++ b/app/src/main/java/com/example/com_us/ui/question/theme/ThemeQuestionListViewModel.kt @@ -0,0 +1,60 @@ +package com.example.com_us.ui.question.theme + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.example.com_us.R +import com.example.com_us.base.data.NetworkError +import com.example.com_us.data.model.question.response.question.ResponseQuestionDto +import com.example.com_us.data.repository.QuestionRepository +import com.example.com_us.ui.base.UiState +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import javax.inject.Inject + + + +@HiltViewModel +class ThemeQuestionListViewModel @Inject constructor( + private val questionRepository: QuestionRepository +) : ViewModel() { + // 질문 리스트 + private val _questionListByCate = MutableLiveData>() + val questionListByCate: LiveData> = _questionListByCate + + + // ui 상태 변수 + private val _uiState = MutableStateFlow>>(UiState.Initial) + val uiState = _uiState.asStateFlow() + + // 선택한 테마의 id + private val _selectedThemeId = MutableLiveData().apply { + value = R.id.include_theme_all + } + val selectedThemeId: LiveData = _selectedThemeId + + + + // 클릭한 카테고리의 질문 리스트를 가져오는 함수 + fun loadQuestionListByCate(category: String){ + viewModelScope.launch { + questionRepository.getQuestionListByCate(category) + .onSuccess { + _uiState.value = UiState.Success(it) + } + .onFailure { + val errorMessage = when(it){ + is NetworkError.NetworkException -> {it.message} + is NetworkError.NullDataError -> {"아직 데이터를 준비중이에요!"} + else -> "알 수 없는 에러가 발생했습니다. 다시 시도해주세요!" + } + if (errorMessage != null) { + _uiState.value = UiState.Error(errorMessage) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/com_us/util/ColorMatch.kt b/app/src/main/java/com/example/com_us/util/ColorMatch.kt index aca09f3..150f50a 100644 --- a/app/src/main/java/com/example/com_us/util/ColorMatch.kt +++ b/app/src/main/java/com/example/com_us/util/ColorMatch.kt @@ -1,5 +1,6 @@ package com.example.com_us.util +import android.content.Context import androidx.compose.ui.graphics.Color import com.example.com_us.R import com.example.com_us.ui.compose.theme.Blue700 @@ -9,7 +10,8 @@ import com.example.com_us.ui.compose.theme.Pink700 import com.example.com_us.ui.compose.theme.Purple700 import com.example.com_us.ui.compose.theme.Salmon700 -enum class ColorMatch(val kor: String, val colorType: ColorType, val color: Color, val colorRes: Int) { +enum class ColorMatch( + val kor: String, val colorType: ColorType, val color: Color, val colorRes: Int) { DAILY("일상", ColorType.ORANGE, Orange700, R.color.orange_700), SCHOOL("학교", ColorType.BLUE, Blue700, R.color.blue_700), FRIEND("친구", ColorType.GREEN, Green700, R.color.green_700), diff --git a/app/src/main/java/com/example/com_us/util/QuestionManager.kt b/app/src/main/java/com/example/com_us/util/QuestionManager.kt index 069a081..f59b649 100644 --- a/app/src/main/java/com/example/com_us/util/QuestionManager.kt +++ b/app/src/main/java/com/example/com_us/util/QuestionManager.kt @@ -1,6 +1,6 @@ package com.example.com_us.util -import com.example.com_us.data.response.question.ResponseAnswerDetailDto +import com.example.com_us.data.model.question.response.question.ResponseAnswerDetailDto object QuestionManager { var questionId: Long = -1 diff --git a/app/src/main/res/drawable/btn_complete_selected.xml b/app/src/main/res/drawable/btn_complete_selected.xml new file mode 100644 index 0000000..ee6dee1 --- /dev/null +++ b/app/src/main/res/drawable/btn_complete_selected.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/btn_complete_unselected.xml b/app/src/main/res/drawable/btn_complete_unselected.xml new file mode 100644 index 0000000..135dff9 --- /dev/null +++ b/app/src/main/res/drawable/btn_complete_unselected.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/btn_google_login.png b/app/src/main/res/drawable/btn_google_login.png new file mode 100644 index 0000000..4f15111 Binary files /dev/null and b/app/src/main/res/drawable/btn_google_login.png differ diff --git a/app/src/main/res/drawable/ic_block_family_purple.xml b/app/src/main/res/drawable/ic_block_family_purple.xml new file mode 100644 index 0000000..f415548 --- /dev/null +++ b/app/src/main/res/drawable/ic_block_family_purple.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_block_friend_green.xml b/app/src/main/res/drawable/ic_block_friend_green.xml new file mode 100644 index 0000000..443c270 --- /dev/null +++ b/app/src/main/res/drawable/ic_block_friend_green.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_block_interest_pink.xml b/app/src/main/res/drawable/ic_block_interest_pink.xml new file mode 100644 index 0000000..6858838 --- /dev/null +++ b/app/src/main/res/drawable/ic_block_interest_pink.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_block_school_blue.xml b/app/src/main/res/drawable/ic_block_school_blue.xml new file mode 100644 index 0000000..0646f99 --- /dev/null +++ b/app/src/main/res/drawable/ic_block_school_blue.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_block_school_orange.xml b/app/src/main/res/drawable/ic_block_school_orange.xml new file mode 100644 index 0000000..e197fa8 --- /dev/null +++ b/app/src/main/res/drawable/ic_block_school_orange.xml @@ -0,0 +1,28 @@ + + + + + + + diff --git a/app/src/main/res/drawable/ic_daily.xml b/app/src/main/res/drawable/ic_daily.xml new file mode 100644 index 0000000..33ae60e --- /dev/null +++ b/app/src/main/res/drawable/ic_daily.xml @@ -0,0 +1,17 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_entire.xml b/app/src/main/res/drawable/ic_entire.xml new file mode 100644 index 0000000..a0287a7 --- /dev/null +++ b/app/src/main/res/drawable/ic_entire.xml @@ -0,0 +1,17 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_hamburger.xml b/app/src/main/res/drawable/ic_hamburger.xml new file mode 100644 index 0000000..72b7e27 --- /dev/null +++ b/app/src/main/res/drawable/ic_hamburger.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_home_blue.xml b/app/src/main/res/drawable/ic_home_blue.xml new file mode 100644 index 0000000..0ca7da8 --- /dev/null +++ b/app/src/main/res/drawable/ic_home_blue.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_logo.xml b/app/src/main/res/drawable/ic_logo.xml new file mode 100644 index 0000000..862d47d --- /dev/null +++ b/app/src/main/res/drawable/ic_logo.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_speech_bubble.xml b/app/src/main/res/drawable/ic_speech_bubble.xml new file mode 100644 index 0000000..fbf31b4 --- /dev/null +++ b/app/src/main/res/drawable/ic_speech_bubble.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_tag_conversation.xml b/app/src/main/res/drawable/ic_tag_conversation.xml new file mode 100644 index 0000000..b39a328 --- /dev/null +++ b/app/src/main/res/drawable/ic_tag_conversation.xml @@ -0,0 +1,17 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_tag_select.xml b/app/src/main/res/drawable/ic_tag_select.xml new file mode 100644 index 0000000..d5cd735 --- /dev/null +++ b/app/src/main/res/drawable/ic_tag_select.xml @@ -0,0 +1,17 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_timer.xml b/app/src/main/res/drawable/ic_timer.xml new file mode 100644 index 0000000..489540c --- /dev/null +++ b/app/src/main/res/drawable/ic_timer.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_user_blue.xml b/app/src/main/res/drawable/ic_user_blue.xml new file mode 100644 index 0000000..3276d94 --- /dev/null +++ b/app/src/main/res/drawable/ic_user_blue.xml @@ -0,0 +1,45 @@ + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_user_green.xml b/app/src/main/res/drawable/ic_user_green.xml new file mode 100644 index 0000000..bacb8a8 --- /dev/null +++ b/app/src/main/res/drawable/ic_user_green.xml @@ -0,0 +1,45 @@ + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_user_orange.xml b/app/src/main/res/drawable/ic_user_orange.xml new file mode 100644 index 0000000..da13bbd --- /dev/null +++ b/app/src/main/res/drawable/ic_user_orange.xml @@ -0,0 +1,33 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/ic_user_pink.xml b/app/src/main/res/drawable/ic_user_pink.xml new file mode 100644 index 0000000..a2fc8fc --- /dev/null +++ b/app/src/main/res/drawable/ic_user_pink.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_user_purple.xml b/app/src/main/res/drawable/ic_user_purple.xml new file mode 100644 index 0000000..4785ecc --- /dev/null +++ b/app/src/main/res/drawable/ic_user_purple.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable/layout_table.xml b/app/src/main/res/drawable/layout_table.xml new file mode 100644 index 0000000..bbbcfb0 --- /dev/null +++ b/app/src/main/res/drawable/layout_table.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_stroke_rect_bottomleft10_gray200.xml b/app/src/main/res/drawable/shape_stroke_rect_bottomleft10_gray200.xml index 8ff971d..33ce8e7 100644 --- a/app/src/main/res/drawable/shape_stroke_rect_bottomleft10_gray200.xml +++ b/app/src/main/res/drawable/shape_stroke_rect_bottomleft10_gray200.xml @@ -3,4 +3,4 @@ android:shape="rectangle"> - \ No newline at end of file + diff --git a/app/src/main/res/drawable/shape_stroke_rect_gray200.xml b/app/src/main/res/drawable/shape_stroke_rect_gray200.xml index 73ddd04..45b8859 100644 --- a/app/src/main/res/drawable/shape_stroke_rect_gray200.xml +++ b/app/src/main/res/drawable/shape_stroke_rect_gray200.xml @@ -2,4 +2,5 @@ + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml new file mode 100644 index 0000000..281f59d --- /dev/null +++ b/app/src/main/res/layout/activity_login.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 2e6249e..60f59f2 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -4,7 +4,7 @@ android:id="@+id/container" android:layout_width="match_parent" android:layout_height="match_parent"> -/ + @@ -24,11 +24,11 @@ app:menu="@menu/bottom_nav_menu" /> + tools:context=".ui.question.result.ResultBeforeSignActivity"> + tools:context=".ui.question.block.CollectBlockActivity"> + tools:context=".ui.question.select.SelectAnswerActivity"> + tools:context=".ui.question.previous.PreviousAnswerActivity"> + tools:context=".ui.question.result.ResultAfterSignActivity"> + tools:context=".ui.question.theme.ThemeQuestionListActivity"> + tools:context=".ui.question.sign.SignAnswerDialog"> + + + + app:layout_constraintTop_toBottomOf="@+id/linearProgressIndicator" />