diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 40be57d..52e6177 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,3 +1,5 @@ +import java.util.Properties + plugins { alias(libs.plugins.android.application) alias(libs.plugins.kotlin.android) @@ -7,6 +9,10 @@ plugins { alias(libs.plugins.kotlin.serialization) } +val properties = Properties().apply { + load(rootProject.file("local.properties").inputStream()) +} + android { namespace = "org.sopt.and" compileSdk = 34 @@ -19,6 +25,12 @@ android { versionName = "1.0" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + + buildConfigField( + "String", + "BASE_URL", + properties.getProperty("base.url") + ) } buildTypes { @@ -39,6 +51,7 @@ android { } buildFeatures { compose = true + buildConfig = true } } @@ -83,4 +96,12 @@ dependencies { //coil implementation(libs.coil) + + //retrofit + implementation(libs.okhttp) + implementation(libs.okhttp.logging) + implementation(platform(libs.okhttp.bom)) + implementation(libs.retrofit) + implementation(libs.retrofit) + implementation(libs.retrofit.kotlinx.serialization.converter) } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 6de7a59..f41f49f 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -14,6 +14,7 @@ android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.ANDANDROID" + android:usesCleartextTraffic="true" tools:targetApi="31"> Unit = {}, diff --git a/app/src/main/java/org/sopt/and/core/preference/PreferenceUtil.kt b/app/src/main/java/org/sopt/and/core/preference/PreferenceUtil.kt index be272fc..c410ae0 100644 --- a/app/src/main/java/org/sopt/and/core/preference/PreferenceUtil.kt +++ b/app/src/main/java/org/sopt/and/core/preference/PreferenceUtil.kt @@ -11,25 +11,19 @@ class PreferenceUtil( PREF_NAME, Context.MODE_PRIVATE ) - var id: String - get() = preference.getString(ID, DEFAULT_STRING).toString() - set(value) = preference.edit().putString(ID, value).apply() + var token: String + get() = preference.getString(TOKEN, DEFAULT_STRING).toString() + set(value) = preference.edit().putString(TOKEN, value).apply() - var password: String - get() = preference.getString(PASSWORD, DEFAULT_STRING).toString() - set(value) = preference.edit().putString(PASSWORD, value).apply() - - fun clearIdPassword() { - id = "" - password = "" + fun clearToken() { + token = "" } companion object { private const val PREF_NAME = "wavve_prefs" - private const val ID = "ID" - private const val PASSWORD = "PASSWORD" + private const val TOKEN = "TOKEN" private const val DEFAULT_STRING = "" - + val LocalPreference = staticCompositionLocalOf { error("PreferenceUtil is not initialized") } diff --git a/app/src/main/java/org/sopt/and/data/datasource/AuthDataSource.kt b/app/src/main/java/org/sopt/and/data/datasource/AuthDataSource.kt new file mode 100644 index 0000000..5e98873 --- /dev/null +++ b/app/src/main/java/org/sopt/and/data/datasource/AuthDataSource.kt @@ -0,0 +1,18 @@ +package org.sopt.and.data.datasource + +import org.sopt.and.data.dto.BaseResponse +import org.sopt.and.data.dto.request.SignInRequestDto +import org.sopt.and.data.dto.request.SignUpRequestDto +import org.sopt.and.data.dto.response.SignInResponseDto +import org.sopt.and.data.dto.response.SignUpResponseDto +import retrofit2.Call + +interface AuthDataSource { + fun postSignUp( + request: SignUpRequestDto + ): Call> + + fun postSignIn( + request: SignInRequestDto + ): Call> +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/data/datasource/UserDataSource.kt b/app/src/main/java/org/sopt/and/data/datasource/UserDataSource.kt new file mode 100644 index 0000000..328f54d --- /dev/null +++ b/app/src/main/java/org/sopt/and/data/datasource/UserDataSource.kt @@ -0,0 +1,9 @@ +package org.sopt.and.data.datasource + +import org.sopt.and.data.dto.BaseResponse +import org.sopt.and.data.dto.response.MyHobbyResponseDto +import retrofit2.Call + +interface UserDataSource { + fun getMyHobby(token: String): Call> +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/data/datasourceImpl/AuthDataSourceImpl.kt b/app/src/main/java/org/sopt/and/data/datasourceImpl/AuthDataSourceImpl.kt new file mode 100644 index 0000000..e2a349c --- /dev/null +++ b/app/src/main/java/org/sopt/and/data/datasourceImpl/AuthDataSourceImpl.kt @@ -0,0 +1,19 @@ +package org.sopt.and.data.datasourceImpl + +import org.sopt.and.data.datasource.AuthDataSource +import org.sopt.and.data.dto.BaseResponse +import org.sopt.and.data.dto.request.SignInRequestDto +import org.sopt.and.data.dto.request.SignUpRequestDto +import org.sopt.and.data.dto.response.SignInResponseDto +import org.sopt.and.data.dto.response.SignUpResponseDto +import org.sopt.and.data.remote.AuthService +import retrofit2.Call +import javax.inject.Inject + +class AuthDataSourceImpl @Inject constructor( + private val userService: AuthService +): AuthDataSource { + + override fun postSignUp(request: SignUpRequestDto): Call> = userService.signUp(request) + override fun postSignIn(request: SignInRequestDto): Call> = userService.signIn(request) +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/data/datasourceImpl/UserDataSourceImpl.kt b/app/src/main/java/org/sopt/and/data/datasourceImpl/UserDataSourceImpl.kt new file mode 100644 index 0000000..25dfbd7 --- /dev/null +++ b/app/src/main/java/org/sopt/and/data/datasourceImpl/UserDataSourceImpl.kt @@ -0,0 +1,15 @@ +package org.sopt.and.data.datasourceImpl + +import org.sopt.and.data.datasource.UserDataSource +import org.sopt.and.data.dto.BaseResponse +import org.sopt.and.data.dto.response.MyHobbyResponseDto +import org.sopt.and.data.remote.UserService +import retrofit2.Call +import javax.inject.Inject + +class UserDataSourceImpl @Inject constructor( + private val userService: UserService +) : UserDataSource { + override fun getMyHobby(token: String): Call> = + userService.getMyHobby(token) +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/core/data/di/DBModule.kt b/app/src/main/java/org/sopt/and/data/di/DBModule.kt similarity index 81% rename from app/src/main/java/org/sopt/and/core/data/di/DBModule.kt rename to app/src/main/java/org/sopt/and/data/di/DBModule.kt index 0275268..9003fdc 100644 --- a/app/src/main/java/org/sopt/and/core/data/di/DBModule.kt +++ b/app/src/main/java/org/sopt/and/data/di/DBModule.kt @@ -1,4 +1,4 @@ -package org.sopt.and.core.data.di +package org.sopt.and.data.di import android.content.Context import dagger.Module @@ -6,7 +6,7 @@ import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent -import org.sopt.and.core.data.local.database.StarredProgramDatabase +import org.sopt.and.data.local.database.StarredProgramDatabase import javax.inject.Singleton @InstallIn(SingletonComponent::class) diff --git a/app/src/main/java/org/sopt/and/data/di/DataSourceModule.kt b/app/src/main/java/org/sopt/and/data/di/DataSourceModule.kt new file mode 100644 index 0000000..da8dc96 --- /dev/null +++ b/app/src/main/java/org/sopt/and/data/di/DataSourceModule.kt @@ -0,0 +1,29 @@ +package org.sopt.and.data.di + +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import org.sopt.and.data.datasource.AuthDataSource +import org.sopt.and.data.datasource.UserDataSource +import org.sopt.and.data.datasourceImpl.AuthDataSourceImpl +import org.sopt.and.data.datasourceImpl.UserDataSourceImpl +import javax.inject.Singleton + +@InstallIn(SingletonComponent::class) +@Module +abstract class DataSourceModule { + + @Binds + @Singleton + abstract fun bindsAuthDataSource( + authDataSourceImpl: AuthDataSourceImpl + ): AuthDataSource + + @Binds + @Singleton + abstract fun bindsUserDataSource( + userDataSourceImpl: UserDataSourceImpl + ): UserDataSource + +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/data/di/RepositoryModule.kt b/app/src/main/java/org/sopt/and/data/di/RepositoryModule.kt new file mode 100644 index 0000000..48ec48b --- /dev/null +++ b/app/src/main/java/org/sopt/and/data/di/RepositoryModule.kt @@ -0,0 +1,62 @@ +package org.sopt.and.data.di + +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import org.sopt.and.data.repositoryimpl.DummyPopularProgramRepositoryImpl +import org.sopt.and.data.repositoryimpl.DummyRecommendationRepositoryImpl +import org.sopt.and.data.repositoryimpl.MyHobbyRepositoryImpl +import org.sopt.and.data.repositoryimpl.SignInRepositoryImpl +import org.sopt.and.data.repositoryimpl.SignUpRepositoryImpl +import org.sopt.and.data.repositoryimpl.StarredProgramRepositoryImpl +import org.sopt.and.domain.repository.MyHobbyRepository +import org.sopt.and.domain.repository.PopularProgramRepository +import org.sopt.and.domain.repository.RecommendationRepository +import org.sopt.and.domain.repository.SignInRepository +import org.sopt.and.domain.repository.SignUpRepository +import org.sopt.and.domain.repository.StarredProgramRepository +import javax.inject.Singleton + +@InstallIn(SingletonComponent::class) +@Module +abstract class RepositoryModule() { + + @Binds + @Singleton + abstract fun bindsDummyRecommendationRepository( + recommendationRepositoryImpl: DummyRecommendationRepositoryImpl + ): RecommendationRepository + + @Binds + @Singleton + abstract fun bindsDummyPopularProgramRepository( + popularProgramRepositoryImpl: DummyPopularProgramRepositoryImpl + ): PopularProgramRepository + + @Binds + @Singleton + abstract fun bindsStarredProgramRepository( + starredProgramRepositoryImpl: StarredProgramRepositoryImpl + ): StarredProgramRepository + + @Binds + @Singleton + abstract fun bindsSignUpRepository( + signUpRepositoryImpl: SignUpRepositoryImpl + ): SignUpRepository + + + @Binds + @Singleton + abstract fun bindsSignInRepository( + signInRepositoryImpl: SignInRepositoryImpl + ): SignInRepository + + @Binds + @Singleton + abstract fun bindsMyHobbyRepository( + myHobbyRepositoryImpl: MyHobbyRepositoryImpl + ): MyHobbyRepository + +} diff --git a/app/src/main/java/org/sopt/and/data/di/RetrofitModule.kt b/app/src/main/java/org/sopt/and/data/di/RetrofitModule.kt new file mode 100644 index 0000000..a55ad49 --- /dev/null +++ b/app/src/main/java/org/sopt/and/data/di/RetrofitModule.kt @@ -0,0 +1,49 @@ +package org.sopt.and.data.di + +import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import kotlinx.serialization.json.Json +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.OkHttpClient +import okhttp3.logging.HttpLoggingInterceptor +import org.sopt.and.BuildConfig.BASE_URL +import retrofit2.Converter +import retrofit2.Retrofit +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object RetrofitModule { + + @Provides + @Singleton + fun providesLoggingInterceptor() = HttpLoggingInterceptor().apply { + level = HttpLoggingInterceptor.Level.BODY + } + + @Provides + @Singleton + fun providesOkHttpClient( + loggingInterceptor: HttpLoggingInterceptor + ): OkHttpClient = OkHttpClient.Builder() + .addInterceptor(loggingInterceptor) + .build() + + @Provides + @Singleton + fun providesConverterFactory(): Converter.Factory = Json.asConverterFactory("application/json".toMediaType()) + + @Provides + @Singleton + fun providesRetrofit( + client: OkHttpClient, + converterFactory: Converter.Factory + ): Retrofit = Retrofit.Builder() + .baseUrl(BASE_URL) + .client(client) + .addConverterFactory(converterFactory) + .build() +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/data/di/ServiceModule.kt b/app/src/main/java/org/sopt/and/data/di/ServiceModule.kt new file mode 100644 index 0000000..48470a6 --- /dev/null +++ b/app/src/main/java/org/sopt/and/data/di/ServiceModule.kt @@ -0,0 +1,28 @@ +package org.sopt.and.data.di + +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import org.sopt.and.data.remote.AuthService +import org.sopt.and.data.remote.UserService +import retrofit2.Retrofit +import javax.inject.Singleton + +@InstallIn(SingletonComponent::class) +@Module +object ServiceModule { + + @Provides + @Singleton + fun providesAuthService( + retrofit: Retrofit + ): AuthService = retrofit.create(AuthService::class.java) + + @Provides + @Singleton + fun providesUserService( + retrofit: Retrofit + ): UserService = retrofit.create(UserService::class.java) + +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/data/dto/BaseResponse.kt b/app/src/main/java/org/sopt/and/data/dto/BaseResponse.kt new file mode 100644 index 0000000..627956c --- /dev/null +++ b/app/src/main/java/org/sopt/and/data/dto/BaseResponse.kt @@ -0,0 +1,10 @@ +package org.sopt.and.data.dto + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable + data class BaseResponse( + @SerialName("result") + val result: T +) diff --git a/app/src/main/java/org/sopt/and/data/dto/request/SignInRequestDto.kt b/app/src/main/java/org/sopt/and/data/dto/request/SignInRequestDto.kt new file mode 100644 index 0000000..21bc736 --- /dev/null +++ b/app/src/main/java/org/sopt/and/data/dto/request/SignInRequestDto.kt @@ -0,0 +1,12 @@ +package org.sopt.and.data.dto.request + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class SignInRequestDto( + @SerialName("username") + val userName: String, + @SerialName("password") + val password: String +) diff --git a/app/src/main/java/org/sopt/and/data/dto/request/SignUpRequestDto.kt b/app/src/main/java/org/sopt/and/data/dto/request/SignUpRequestDto.kt new file mode 100644 index 0000000..978181a --- /dev/null +++ b/app/src/main/java/org/sopt/and/data/dto/request/SignUpRequestDto.kt @@ -0,0 +1,14 @@ +package org.sopt.and.data.dto.request + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class SignUpRequestDto( + @SerialName("username") + val userName: String, + @SerialName("password") + val password: String, + @SerialName("hobby") + val hobby: String +) diff --git a/app/src/main/java/org/sopt/and/data/dto/response/MyHobbyResponseDto.kt b/app/src/main/java/org/sopt/and/data/dto/response/MyHobbyResponseDto.kt new file mode 100644 index 0000000..aa21ef0 --- /dev/null +++ b/app/src/main/java/org/sopt/and/data/dto/response/MyHobbyResponseDto.kt @@ -0,0 +1,10 @@ +package org.sopt.and.data.dto.response + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class MyHobbyResponseDto( + @SerialName("hobby") + val hobby: String +) diff --git a/app/src/main/java/org/sopt/and/data/dto/response/SignInResponseDto.kt b/app/src/main/java/org/sopt/and/data/dto/response/SignInResponseDto.kt new file mode 100644 index 0000000..2698c5a --- /dev/null +++ b/app/src/main/java/org/sopt/and/data/dto/response/SignInResponseDto.kt @@ -0,0 +1,10 @@ +package org.sopt.and.data.dto.response + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class SignInResponseDto( + @SerialName("token") + val token: String +) diff --git a/app/src/main/java/org/sopt/and/data/dto/response/SignUpResponseDto.kt b/app/src/main/java/org/sopt/and/data/dto/response/SignUpResponseDto.kt new file mode 100644 index 0000000..4e54352 --- /dev/null +++ b/app/src/main/java/org/sopt/and/data/dto/response/SignUpResponseDto.kt @@ -0,0 +1,10 @@ +package org.sopt.and.data.dto.response + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class SignUpResponseDto ( + @SerialName("no") + val userNumber: Int +) diff --git a/app/src/main/java/org/sopt/and/core/data/local/dao/StarredProgramDao.kt b/app/src/main/java/org/sopt/and/data/local/dao/StarredProgramDao.kt similarity index 83% rename from app/src/main/java/org/sopt/and/core/data/local/dao/StarredProgramDao.kt rename to app/src/main/java/org/sopt/and/data/local/dao/StarredProgramDao.kt index 60c11d3..081dd6c 100644 --- a/app/src/main/java/org/sopt/and/core/data/local/dao/StarredProgramDao.kt +++ b/app/src/main/java/org/sopt/and/data/local/dao/StarredProgramDao.kt @@ -1,11 +1,11 @@ -package org.sopt.and.core.data.local.dao +package org.sopt.and.data.local.dao import androidx.room.Dao import androidx.room.Insert import androidx.room.OnConflictStrategy import androidx.room.Query import kotlinx.coroutines.flow.Flow -import org.sopt.and.core.data.local.entity.StarredProgramEntity +import org.sopt.and.data.local.entity.StarredProgramEntity @Dao interface StarredProgramDao { diff --git a/app/src/main/java/org/sopt/and/core/data/local/database/StarredProgramDatabase.kt b/app/src/main/java/org/sopt/and/data/local/database/StarredProgramDatabase.kt similarity index 82% rename from app/src/main/java/org/sopt/and/core/data/local/database/StarredProgramDatabase.kt rename to app/src/main/java/org/sopt/and/data/local/database/StarredProgramDatabase.kt index ca884a7..bb7ffc7 100644 --- a/app/src/main/java/org/sopt/and/core/data/local/database/StarredProgramDatabase.kt +++ b/app/src/main/java/org/sopt/and/data/local/database/StarredProgramDatabase.kt @@ -1,11 +1,11 @@ -package org.sopt.and.core.data.local.database +package org.sopt.and.data.local.database import android.content.Context import androidx.room.Database import androidx.room.Room import androidx.room.RoomDatabase -import org.sopt.and.core.data.local.dao.StarredProgramDao -import org.sopt.and.core.data.local.entity.StarredProgramEntity +import org.sopt.and.data.local.dao.StarredProgramDao +import org.sopt.and.data.local.entity.StarredProgramEntity @Database(entities = [StarredProgramEntity::class], version = 2, exportSchema = false) abstract class StarredProgramDatabase : RoomDatabase() { diff --git a/app/src/main/java/org/sopt/and/core/data/local/entity/StarredProgramEntity.kt b/app/src/main/java/org/sopt/and/data/local/entity/StarredProgramEntity.kt similarity index 90% rename from app/src/main/java/org/sopt/and/core/data/local/entity/StarredProgramEntity.kt rename to app/src/main/java/org/sopt/and/data/local/entity/StarredProgramEntity.kt index b53ec53..1640bee 100644 --- a/app/src/main/java/org/sopt/and/core/data/local/entity/StarredProgramEntity.kt +++ b/app/src/main/java/org/sopt/and/data/local/entity/StarredProgramEntity.kt @@ -1,4 +1,4 @@ -package org.sopt.and.core.data.local.entity +package org.sopt.and.data.local.entity import androidx.annotation.NonNull import androidx.room.ColumnInfo diff --git a/app/src/main/java/org/sopt/and/core/data/mapper/ProgramToEntityMapper.kt b/app/src/main/java/org/sopt/and/data/mapper/ProgramToEntityMapper.kt similarity index 64% rename from app/src/main/java/org/sopt/and/core/data/mapper/ProgramToEntityMapper.kt rename to app/src/main/java/org/sopt/and/data/mapper/ProgramToEntityMapper.kt index ca651be..a7200f7 100644 --- a/app/src/main/java/org/sopt/and/core/data/mapper/ProgramToEntityMapper.kt +++ b/app/src/main/java/org/sopt/and/data/mapper/ProgramToEntityMapper.kt @@ -1,7 +1,7 @@ -package org.sopt.and.core.data.mapper +package org.sopt.and.data.mapper -import org.sopt.and.core.data.local.entity.StarredProgramEntity import org.sopt.and.core.model.Program +import org.sopt.and.data.local.entity.StarredProgramEntity fun Program.toStarredProgramEntity(): StarredProgramEntity = StarredProgramEntity( programName = this.title, diff --git a/app/src/main/java/org/sopt/and/data/mapper/SignInMapper.kt b/app/src/main/java/org/sopt/and/data/mapper/SignInMapper.kt new file mode 100644 index 0000000..21052d6 --- /dev/null +++ b/app/src/main/java/org/sopt/and/data/mapper/SignInMapper.kt @@ -0,0 +1,9 @@ +package org.sopt.and.data.mapper + +import org.sopt.and.data.dto.request.SignInRequestDto +import org.sopt.and.domain.entity.User + +fun User.toSignInRequest(): SignInRequestDto = SignInRequestDto( + userName = this.userName, + password = this.password +) \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/data/mapper/SignUpMapper.kt b/app/src/main/java/org/sopt/and/data/mapper/SignUpMapper.kt new file mode 100644 index 0000000..61d1e06 --- /dev/null +++ b/app/src/main/java/org/sopt/and/data/mapper/SignUpMapper.kt @@ -0,0 +1,10 @@ +package org.sopt.and.data.mapper + +import org.sopt.and.data.dto.request.SignUpRequestDto +import org.sopt.and.domain.entity.User + +fun User.toSignUpRequest(): SignUpRequestDto = SignUpRequestDto( + userName = this.userName, + password = this.password, + hobby = this.hobby +) \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/data/remote/AuthService.kt b/app/src/main/java/org/sopt/and/data/remote/AuthService.kt new file mode 100644 index 0000000..7ddf5ce --- /dev/null +++ b/app/src/main/java/org/sopt/and/data/remote/AuthService.kt @@ -0,0 +1,22 @@ +package org.sopt.and.data.remote + +import org.sopt.and.data.dto.BaseResponse +import org.sopt.and.data.dto.request.SignInRequestDto +import org.sopt.and.data.dto.request.SignUpRequestDto +import org.sopt.and.data.dto.response.SignInResponseDto +import org.sopt.and.data.dto.response.SignUpResponseDto +import retrofit2.Call +import retrofit2.http.Body +import retrofit2.http.POST + +interface AuthService { + @POST("/user") + fun signUp( + @Body request: SignUpRequestDto + ): Call> + + @POST("/login") + fun signIn( + @Body request: SignInRequestDto + ): Call> +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/data/remote/UserService.kt b/app/src/main/java/org/sopt/and/data/remote/UserService.kt new file mode 100644 index 0000000..6985cc6 --- /dev/null +++ b/app/src/main/java/org/sopt/and/data/remote/UserService.kt @@ -0,0 +1,14 @@ +package org.sopt.and.data.remote + +import org.sopt.and.data.dto.BaseResponse +import org.sopt.and.data.dto.response.MyHobbyResponseDto +import retrofit2.Call +import retrofit2.http.GET +import retrofit2.http.Header + +interface UserService { + @GET("/user/my-hobby") + fun getMyHobby( + @Header("token") token: String + ): Call> +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/core/data/repositoryimpl/DummyPopularProgramRepositoryImpl.kt b/app/src/main/java/org/sopt/and/data/repositoryimpl/DummyPopularProgramRepositoryImpl.kt similarity index 94% rename from app/src/main/java/org/sopt/and/core/data/repositoryimpl/DummyPopularProgramRepositoryImpl.kt rename to app/src/main/java/org/sopt/and/data/repositoryimpl/DummyPopularProgramRepositoryImpl.kt index 336e509..3b82168 100644 --- a/app/src/main/java/org/sopt/and/core/data/repositoryimpl/DummyPopularProgramRepositoryImpl.kt +++ b/app/src/main/java/org/sopt/and/data/repositoryimpl/DummyPopularProgramRepositoryImpl.kt @@ -1,8 +1,8 @@ -package org.sopt.and.core.data.repositoryimpl +package org.sopt.and.data.repositoryimpl import org.sopt.and.R -import org.sopt.and.core.data.repository.PopularProgramRepository import org.sopt.and.core.model.Program +import org.sopt.and.domain.repository.PopularProgramRepository import javax.inject.Inject class DummyPopularProgramRepositoryImpl @Inject constructor() : PopularProgramRepository { diff --git a/app/src/main/java/org/sopt/and/core/data/repositoryimpl/DummyRecommendationRepositoryImpl.kt b/app/src/main/java/org/sopt/and/data/repositoryimpl/DummyRecommendationRepositoryImpl.kt similarity index 96% rename from app/src/main/java/org/sopt/and/core/data/repositoryimpl/DummyRecommendationRepositoryImpl.kt rename to app/src/main/java/org/sopt/and/data/repositoryimpl/DummyRecommendationRepositoryImpl.kt index 2709891..6b9734a 100644 --- a/app/src/main/java/org/sopt/and/core/data/repositoryimpl/DummyRecommendationRepositoryImpl.kt +++ b/app/src/main/java/org/sopt/and/data/repositoryimpl/DummyRecommendationRepositoryImpl.kt @@ -1,9 +1,9 @@ -package org.sopt.and.core.data.repositoryimpl +package org.sopt.and.data.repositoryimpl import org.sopt.and.R -import org.sopt.and.core.data.repository.RecommendationRepository import org.sopt.and.core.model.HomeRecommendation import org.sopt.and.core.model.Program +import org.sopt.and.domain.repository.RecommendationRepository import javax.inject.Inject class DummyRecommendationRepositoryImpl @Inject constructor( diff --git a/app/src/main/java/org/sopt/and/data/repositoryimpl/MyHobbyRepositoryImpl.kt b/app/src/main/java/org/sopt/and/data/repositoryimpl/MyHobbyRepositoryImpl.kt new file mode 100644 index 0000000..91fe88b --- /dev/null +++ b/app/src/main/java/org/sopt/and/data/repositoryimpl/MyHobbyRepositoryImpl.kt @@ -0,0 +1,50 @@ +package org.sopt.and.data.repositoryimpl + +import android.util.Log +import org.sopt.and.data.datasource.UserDataSource +import org.sopt.and.data.dto.BaseResponse +import org.sopt.and.data.dto.response.MyHobbyResponseDto +import org.sopt.and.domain.entity.Hobby +import org.sopt.and.domain.repository.MyHobbyRepository +import retrofit2.Call +import retrofit2.Callback +import retrofit2.Response +import javax.inject.Inject +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException +import kotlin.coroutines.suspendCoroutine + +class MyHobbyRepositoryImpl @Inject constructor( + private val userDataSource: UserDataSource +) : MyHobbyRepository { + override suspend fun getMyHobby(token: String): Result = runCatching { + suspendCoroutine { continuation -> + userDataSource.getMyHobby(token) + .enqueue(object : Callback> { + override fun onResponse( + call: Call>, + response: Response> + ) { + if (response.isSuccessful) { + response.body()?.result?.hobby?.let { + val hobby = Hobby(it) + Log.d("MyHobby", it) + continuation.resume(hobby) + } + } else { + continuation.resumeWithException(Exception()) + } + } + + override fun onFailure( + call: Call>, + response: Throwable + ) { + Log.d("MyHobby", "fail") + continuation.resumeWithException(response) + } + }) + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/data/repositoryimpl/SignInRepositoryImpl.kt b/app/src/main/java/org/sopt/and/data/repositoryimpl/SignInRepositoryImpl.kt new file mode 100644 index 0000000..15a86ba --- /dev/null +++ b/app/src/main/java/org/sopt/and/data/repositoryimpl/SignInRepositoryImpl.kt @@ -0,0 +1,63 @@ +package org.sopt.and.data.repositoryimpl + +import org.sopt.and.data.datasource.AuthDataSource +import org.sopt.and.data.dto.BaseResponse +import org.sopt.and.data.dto.response.SignInResponseDto +import org.sopt.and.data.mapper.toSignInRequest +import org.sopt.and.domain.entity.SignInResponse +import org.sopt.and.domain.entity.User +import org.sopt.and.domain.repository.SignInRepository +import retrofit2.Call +import retrofit2.Callback +import retrofit2.Response +import javax.inject.Inject +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException +import kotlin.coroutines.suspendCoroutine + +class SignInRepositoryImpl @Inject constructor( + private val userDataSource: AuthDataSource +) : SignInRepository { + + override suspend fun signInUser(user: User): Result = runCatching { + suspendCoroutine { continuation -> + userDataSource.postSignIn(user.toSignInRequest()).enqueue(object : + Callback> { + override fun onResponse( + call: Call>, + response: Response> + ) { + if (response.isSuccessful) { + response.body()?.result?.token?.let { + continuation.resume( + SignInResponse( + token = it, + message = MESSAGE_SUCCESS + ) + ) + } + } else { + continuation.resume( + SignInResponse( + message = MESSAGE_FAILURE + ) + ) + } + } + + override fun onFailure( + call: Call>, + throwable: Throwable + ) { + continuation.resumeWithException(throwable) + } + }) + } + } + + companion object { + private const val MESSAGE_SUCCESS = "로그인에 성공했습니다." + private const val MESSAGE_FAILURE = "아이디 또는 비밀번호가 일치하지 않습니다." + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/data/repositoryimpl/SignUpRepositoryImpl.kt b/app/src/main/java/org/sopt/and/data/repositoryimpl/SignUpRepositoryImpl.kt new file mode 100644 index 0000000..b3d773e --- /dev/null +++ b/app/src/main/java/org/sopt/and/data/repositoryimpl/SignUpRepositoryImpl.kt @@ -0,0 +1,63 @@ +package org.sopt.and.data.repositoryimpl + +import org.json.JSONObject +import org.sopt.and.R +import org.sopt.and.data.datasource.AuthDataSource +import org.sopt.and.data.dto.BaseResponse +import org.sopt.and.data.dto.response.SignUpResponseDto +import org.sopt.and.data.mapper.toSignUpRequest +import org.sopt.and.domain.entity.SignUpResponse +import org.sopt.and.domain.entity.User +import org.sopt.and.domain.repository.SignUpRepository +import retrofit2.Call +import retrofit2.Callback +import retrofit2.Response +import javax.inject.Inject +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException +import kotlin.coroutines.suspendCoroutine + +class SignUpRepositoryImpl @Inject constructor( + private val userDataSource: AuthDataSource +) : SignUpRepository { + + override suspend fun registerUser(user: User): Result = runCatching { + suspendCoroutine { continuation -> + userDataSource.postSignUp(user.toSignUpRequest()).enqueue(object : + Callback> { + override fun onResponse( + call: Call>, + response: Response> + ) { + if (response.isSuccessful) { + val userId = response.body()?.result?.userNumber + continuation.resume( + SignUpResponse( + id = userId, + message = R.string.signup_toast_success) + ) + } else { + val code = response.errorBody()?.string()?.let { JSONObject(it) }?.getString("code").toString() + continuation.resume( + SignUpResponse(message = getMessageByCode(code)) + ) + } + } + + override fun onFailure( + call: Call>, + throwable: Throwable + ) { + continuation.resumeWithException(throwable) + } + }) + } + } + + private fun getMessageByCode(code: String): Int = + when (code) { + "00" -> R.string.signup_toast_failure_id_exist + "01" -> R.string.signup_toast_failure_out_of_range + else -> R.string.signup_toast_failure_unknown + } +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/core/data/repositoryimpl/StarredProgramRepositoryImpl.kt b/app/src/main/java/org/sopt/and/data/repositoryimpl/StarredProgramRepositoryImpl.kt similarity index 71% rename from app/src/main/java/org/sopt/and/core/data/repositoryimpl/StarredProgramRepositoryImpl.kt rename to app/src/main/java/org/sopt/and/data/repositoryimpl/StarredProgramRepositoryImpl.kt index bfb9bb2..c68b2bf 100644 --- a/app/src/main/java/org/sopt/and/core/data/repositoryimpl/StarredProgramRepositoryImpl.kt +++ b/app/src/main/java/org/sopt/and/data/repositoryimpl/StarredProgramRepositoryImpl.kt @@ -1,11 +1,11 @@ -package org.sopt.and.core.data.repositoryimpl +package org.sopt.and.data.repositoryimpl import kotlinx.coroutines.flow.Flow -import org.sopt.and.core.data.local.database.StarredProgramDatabase -import org.sopt.and.core.data.local.entity.StarredProgramEntity -import org.sopt.and.core.data.mapper.toStarredProgramEntity -import org.sopt.and.core.data.repository.StarredProgramRepository import org.sopt.and.core.model.Program +import org.sopt.and.data.local.database.StarredProgramDatabase +import org.sopt.and.data.local.entity.StarredProgramEntity +import org.sopt.and.data.mapper.toStarredProgramEntity +import org.sopt.and.domain.repository.StarredProgramRepository import javax.inject.Inject class StarredProgramRepositoryImpl @Inject constructor( diff --git a/app/src/main/java/org/sopt/and/domain/entity/Hobby.kt b/app/src/main/java/org/sopt/and/domain/entity/Hobby.kt new file mode 100644 index 0000000..f27f2f8 --- /dev/null +++ b/app/src/main/java/org/sopt/and/domain/entity/Hobby.kt @@ -0,0 +1,5 @@ +package org.sopt.and.domain.entity + +data class Hobby( + val hobby: String +) diff --git a/app/src/main/java/org/sopt/and/domain/entity/SignInResponse.kt b/app/src/main/java/org/sopt/and/domain/entity/SignInResponse.kt new file mode 100644 index 0000000..1865199 --- /dev/null +++ b/app/src/main/java/org/sopt/and/domain/entity/SignInResponse.kt @@ -0,0 +1,6 @@ +package org.sopt.and.domain.entity + +data class SignInResponse( + val token: String? = null, + val message: String? = null, +) \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/domain/entity/SignUpResponse.kt b/app/src/main/java/org/sopt/and/domain/entity/SignUpResponse.kt new file mode 100644 index 0000000..f6d30c8 --- /dev/null +++ b/app/src/main/java/org/sopt/and/domain/entity/SignUpResponse.kt @@ -0,0 +1,8 @@ +package org.sopt.and.domain.entity + +import androidx.annotation.StringRes + +data class SignUpResponse( + val id: Int? = null, + @StringRes val message: Int +) \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/domain/entity/User.kt b/app/src/main/java/org/sopt/and/domain/entity/User.kt new file mode 100644 index 0000000..7359c2c --- /dev/null +++ b/app/src/main/java/org/sopt/and/domain/entity/User.kt @@ -0,0 +1,7 @@ +package org.sopt.and.domain.entity + +data class User( + val userName: String, + val password: String, + val hobby: String +) diff --git a/app/src/main/java/org/sopt/and/domain/repository/MyHobbyRepository.kt b/app/src/main/java/org/sopt/and/domain/repository/MyHobbyRepository.kt new file mode 100644 index 0000000..44873dc --- /dev/null +++ b/app/src/main/java/org/sopt/and/domain/repository/MyHobbyRepository.kt @@ -0,0 +1,7 @@ +package org.sopt.and.domain.repository + +import org.sopt.and.domain.entity.Hobby + +interface MyHobbyRepository { + suspend fun getMyHobby(token: String): Result +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/core/data/repository/PopularProgramRepository.kt b/app/src/main/java/org/sopt/and/domain/repository/PopularProgramRepository.kt similarity index 79% rename from app/src/main/java/org/sopt/and/core/data/repository/PopularProgramRepository.kt rename to app/src/main/java/org/sopt/and/domain/repository/PopularProgramRepository.kt index 95eda76..e742ffe 100644 --- a/app/src/main/java/org/sopt/and/core/data/repository/PopularProgramRepository.kt +++ b/app/src/main/java/org/sopt/and/domain/repository/PopularProgramRepository.kt @@ -1,4 +1,4 @@ -package org.sopt.and.core.data.repository +package org.sopt.and.domain.repository import org.sopt.and.core.model.Program diff --git a/app/src/main/java/org/sopt/and/core/data/repository/RecommendationRepository.kt b/app/src/main/java/org/sopt/and/domain/repository/RecommendationRepository.kt similarity index 84% rename from app/src/main/java/org/sopt/and/core/data/repository/RecommendationRepository.kt rename to app/src/main/java/org/sopt/and/domain/repository/RecommendationRepository.kt index 78784c2..ff72eeb 100644 --- a/app/src/main/java/org/sopt/and/core/data/repository/RecommendationRepository.kt +++ b/app/src/main/java/org/sopt/and/domain/repository/RecommendationRepository.kt @@ -1,4 +1,4 @@ -package org.sopt.and.core.data.repository +package org.sopt.and.domain.repository import org.sopt.and.core.model.HomeRecommendation diff --git a/app/src/main/java/org/sopt/and/domain/repository/SignInRepository.kt b/app/src/main/java/org/sopt/and/domain/repository/SignInRepository.kt new file mode 100644 index 0000000..70a8783 --- /dev/null +++ b/app/src/main/java/org/sopt/and/domain/repository/SignInRepository.kt @@ -0,0 +1,8 @@ +package org.sopt.and.domain.repository + +import org.sopt.and.domain.entity.SignInResponse +import org.sopt.and.domain.entity.User + +interface SignInRepository { + suspend fun signInUser(user: User): Result +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/domain/repository/SignUpRepository.kt b/app/src/main/java/org/sopt/and/domain/repository/SignUpRepository.kt new file mode 100644 index 0000000..fbbd70b --- /dev/null +++ b/app/src/main/java/org/sopt/and/domain/repository/SignUpRepository.kt @@ -0,0 +1,8 @@ +package org.sopt.and.domain.repository + +import org.sopt.and.domain.entity.SignUpResponse +import org.sopt.and.domain.entity.User + +interface SignUpRepository { + suspend fun registerUser(user: User): Result +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/core/data/repository/StarredProgramRepository.kt b/app/src/main/java/org/sopt/and/domain/repository/StarredProgramRepository.kt similarity index 73% rename from app/src/main/java/org/sopt/and/core/data/repository/StarredProgramRepository.kt rename to app/src/main/java/org/sopt/and/domain/repository/StarredProgramRepository.kt index cf17f32..3e28b2c 100644 --- a/app/src/main/java/org/sopt/and/core/data/repository/StarredProgramRepository.kt +++ b/app/src/main/java/org/sopt/and/domain/repository/StarredProgramRepository.kt @@ -1,8 +1,8 @@ -package org.sopt.and.core.data.repository +package org.sopt.and.domain.repository import kotlinx.coroutines.flow.Flow -import org.sopt.and.core.data.local.entity.StarredProgramEntity import org.sopt.and.core.model.Program +import org.sopt.and.data.local.entity.StarredProgramEntity interface StarredProgramRepository { fun getStarredPrograms(): Flow> diff --git a/app/src/main/java/org/sopt/and/presentation/home/HomeViewModel.kt b/app/src/main/java/org/sopt/and/presentation/home/HomeViewModel.kt index 521f0e3..51bf680 100644 --- a/app/src/main/java/org/sopt/and/presentation/home/HomeViewModel.kt +++ b/app/src/main/java/org/sopt/and/presentation/home/HomeViewModel.kt @@ -5,7 +5,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update -import org.sopt.and.core.data.repository.RecommendationRepository +import org.sopt.and.domain.repository.RecommendationRepository import org.sopt.and.presentation.home.state.HomeUiState import javax.inject.Inject diff --git a/app/src/main/java/org/sopt/and/presentation/home/component/ProgramRow.kt b/app/src/main/java/org/sopt/and/presentation/home/component/ProgramRow.kt index e9b6563..c9af8cc 100644 --- a/app/src/main/java/org/sopt/and/presentation/home/component/ProgramRow.kt +++ b/app/src/main/java/org/sopt/and/presentation/home/component/ProgramRow.kt @@ -29,7 +29,6 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import org.sopt.and.R -import org.sopt.and.core.data.repositoryimpl.DummyPopularProgramRepositoryImpl import org.sopt.and.core.designsystem.theme.WavveBackground import org.sopt.and.core.designsystem.theme.White import org.sopt.and.core.extension.noRippleClickable @@ -94,7 +93,7 @@ fun ProgramRow( private fun ProgramRowPreview() { ProgramRow( title = "남의 삶을 훔쳐보는 공인중개사", - programList = DummyPopularProgramRepositoryImpl.dummyPopularSeries, + programList = emptyList(), modifier = Modifier .background(WavveBackground) .wrapContentHeight() diff --git a/app/src/main/java/org/sopt/and/presentation/home/component/RankedProgramRow.kt b/app/src/main/java/org/sopt/and/presentation/home/component/RankedProgramRow.kt index 4284a23..2765282 100644 --- a/app/src/main/java/org/sopt/and/presentation/home/component/RankedProgramRow.kt +++ b/app/src/main/java/org/sopt/and/presentation/home/component/RankedProgramRow.kt @@ -32,7 +32,6 @@ import androidx.compose.ui.unit.sp import coil.compose.AsyncImage import coil.request.ImageRequest import org.sopt.and.R -import org.sopt.and.core.data.repositoryimpl.DummyPopularProgramRepositoryImpl import org.sopt.and.core.designsystem.theme.WavveBackground import org.sopt.and.core.designsystem.theme.White import org.sopt.and.core.extension.noRippleClickable @@ -112,7 +111,7 @@ fun RankedProgramRow( private fun ProgramRowPreview() { RankedProgramRow( title = "오늘의 TOP 20", - programList = DummyPopularProgramRepositoryImpl.dummyPopularSeries, + programList = emptyList(), modifier = Modifier .background(WavveBackground) .wrapContentHeight() diff --git a/app/src/main/java/org/sopt/and/presentation/main/MainScreen.kt b/app/src/main/java/org/sopt/and/presentation/main/MainScreen.kt index be25300..3bbd793 100644 --- a/app/src/main/java/org/sopt/and/presentation/main/MainScreen.kt +++ b/app/src/main/java/org/sopt/and/presentation/main/MainScreen.kt @@ -51,7 +51,7 @@ fun MainScreen( @Composable private fun getStartDestination(): Route { val preference = LocalPreference.current - return if (preference.id.isEmpty() || preference.password.isEmpty()) { + return if (preference.token.isEmpty()) { SignIn } else { Home diff --git a/app/src/main/java/org/sopt/and/presentation/mypage/MyPageScreen.kt b/app/src/main/java/org/sopt/and/presentation/mypage/MyPageScreen.kt index 7cdeeeb..d2c89eb 100644 --- a/app/src/main/java/org/sopt/and/presentation/mypage/MyPageScreen.kt +++ b/app/src/main/java/org/sopt/and/presentation/mypage/MyPageScreen.kt @@ -66,18 +66,22 @@ fun MyPageRoute( .collect { sideEffect -> when (sideEffect) { is MyPageSideEffect.OnLogout -> { - preference.clearIdPassword() + preference.clearToken() onLogout() } } } } + LaunchedEffect(true) { + viewModel.getMyHobby(preference.token) + } + Box( modifier = modifier ) { MyPageScreen( - email = preference.id, + hobby = uiState.hobby, snackBarHost = snackBarHost, onLogoutButtonClick = viewModel::onLogoutButtonClick, onProgramPress = { program -> @@ -131,7 +135,7 @@ fun MyPageRoute( @Composable private fun MyPageScreen( - email: String, + hobby: String, uiState: MyPageUiState, snackBarHost: SnackbarHostState, onLogoutButtonClick: () -> Unit, @@ -147,7 +151,7 @@ private fun MyPageScreen( .verticalScroll(scrollState) ) { ProfileTopBar( - email = email, + email = hobby, image = painterResource(R.drawable.ic_launcher_foreground), ) diff --git a/app/src/main/java/org/sopt/and/presentation/mypage/MyPageViewModel.kt b/app/src/main/java/org/sopt/and/presentation/mypage/MyPageViewModel.kt index f5d9b20..6ea6c25 100644 --- a/app/src/main/java/org/sopt/and/presentation/mypage/MyPageViewModel.kt +++ b/app/src/main/java/org/sopt/and/presentation/mypage/MyPageViewModel.kt @@ -13,15 +13,17 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch -import org.sopt.and.core.data.repository.StarredProgramRepository import org.sopt.and.core.model.Program +import org.sopt.and.domain.repository.MyHobbyRepository +import org.sopt.and.domain.repository.StarredProgramRepository import org.sopt.and.presentation.mypage.state.MyPageInteractionState import org.sopt.and.presentation.mypage.state.MyPageUiState import javax.inject.Inject @HiltViewModel class MyPageViewModel @Inject constructor( - private val starredProgramRepository: StarredProgramRepository + private val starredProgramRepository: StarredProgramRepository, + private val myHobbyRepository: MyHobbyRepository ) : ViewModel() { private var interactionState = MutableStateFlow(MyPageInteractionState()) private val starredState: StateFlow> = @@ -37,6 +39,7 @@ class MyPageViewModel @Inject constructor( interactionState, starredState ) { uiState, starredState -> MyPageUiState().copy( + hobby = uiState.hobby, searchDialogVisibility = uiState.searchDialogVisibility, deleteDialogVisibility = uiState.deleteDialogVisibility, pressedProgram = uiState.pressedProgram, @@ -51,6 +54,15 @@ class MyPageViewModel @Inject constructor( private var _sideEffect = MutableSharedFlow() val sideEffect = _sideEffect.asSharedFlow() + fun getMyHobby(token: String) = viewModelScope.launch { + myHobbyRepository.getMyHobby(token) + .onSuccess { hobby -> + interactionState.update { currentState -> + currentState.copy(hobby = hobby.hobby) + } + } + } + fun onLogoutButtonClick() = viewModelScope.launch { _sideEffect.emit(MyPageSideEffect.OnLogout) } diff --git a/app/src/main/java/org/sopt/and/presentation/mypage/state/MyPageInteractionState.kt b/app/src/main/java/org/sopt/and/presentation/mypage/state/MyPageInteractionState.kt index 8a698c2..326eebe 100644 --- a/app/src/main/java/org/sopt/and/presentation/mypage/state/MyPageInteractionState.kt +++ b/app/src/main/java/org/sopt/and/presentation/mypage/state/MyPageInteractionState.kt @@ -3,6 +3,7 @@ package org.sopt.and.presentation.mypage.state import org.sopt.and.core.model.Program data class MyPageInteractionState( + val hobby: String = "", val searchDialogVisibility: Boolean = false, val deleteDialogVisibility: Boolean = false, val pressedProgram: Program? = null, diff --git a/app/src/main/java/org/sopt/and/presentation/mypage/state/MyPageUiState.kt b/app/src/main/java/org/sopt/and/presentation/mypage/state/MyPageUiState.kt index 2f2ad62..feb7f48 100644 --- a/app/src/main/java/org/sopt/and/presentation/mypage/state/MyPageUiState.kt +++ b/app/src/main/java/org/sopt/and/presentation/mypage/state/MyPageUiState.kt @@ -3,6 +3,7 @@ package org.sopt.and.presentation.mypage.state import org.sopt.and.core.model.Program data class MyPageUiState( + val hobby: String = "", val searchDialogVisibility: Boolean = false, val deleteDialogVisibility: Boolean = false, val pressedProgram: Program? = null, diff --git a/app/src/main/java/org/sopt/and/presentation/search/SearchViewModel.kt b/app/src/main/java/org/sopt/and/presentation/search/SearchViewModel.kt index 0cde20d..acbd011 100644 --- a/app/src/main/java/org/sopt/and/presentation/search/SearchViewModel.kt +++ b/app/src/main/java/org/sopt/and/presentation/search/SearchViewModel.kt @@ -5,8 +5,8 @@ import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update -import org.sopt.and.core.data.repository.PopularProgramRepository import org.sopt.and.core.model.Program +import org.sopt.and.domain.repository.PopularProgramRepository import org.sopt.and.presentation.search.state.SearchUiState import javax.inject.Inject diff --git a/app/src/main/java/org/sopt/and/presentation/signin/SignInScreen.kt b/app/src/main/java/org/sopt/and/presentation/signin/SignInScreen.kt index 1e7a66d..00959e9 100644 --- a/app/src/main/java/org/sopt/and/presentation/signin/SignInScreen.kt +++ b/app/src/main/java/org/sopt/and/presentation/signin/SignInScreen.kt @@ -44,10 +44,8 @@ import org.sopt.and.presentation.signin.state.SignInUiState @Composable fun SignInRoute( - signUpId: String, - signUpPassword: String, navigateToSignUp: () -> Unit, - navigateToMyPage: () -> Unit, + navigateToHome: () -> Unit, modifier: Modifier = Modifier, viewModel: SignInViewModel = hiltViewModel() ) { @@ -72,12 +70,11 @@ fun SignInRoute( messageId = sideEffect.message ) - SignInSideEffect.NavigateToMyPage -> { + is SignInSideEffect.NavigateToHome -> { with(preference) { - id = uiState.id - password = uiState.password + token = sideEffect.token } - navigateToMyPage() + navigateToHome() } SignInSideEffect.NavigateToSignUp -> { @@ -93,7 +90,7 @@ fun SignInRoute( snackBarHost = snackBarHost, onIdChange = viewModel::updateId, onPasswordChange = viewModel::updatePassword, - onLoginClick = { viewModel.onLoginButtonClick(signUpId, signUpPassword) }, + onLoginClick = viewModel::onSignInButtonClick, onSignUpClick = viewModel::onSignUpButtonClick, modifier = modifier ) diff --git a/app/src/main/java/org/sopt/and/presentation/signin/SignInSideEffect.kt b/app/src/main/java/org/sopt/and/presentation/signin/SignInSideEffect.kt index 2307945..6100b8c 100644 --- a/app/src/main/java/org/sopt/and/presentation/signin/SignInSideEffect.kt +++ b/app/src/main/java/org/sopt/and/presentation/signin/SignInSideEffect.kt @@ -5,7 +5,7 @@ import androidx.annotation.StringRes sealed class SignInSideEffect { data class Toast(@StringRes val message: Int) : SignInSideEffect() data class SnackBar(@StringRes val message: Int) : SignInSideEffect() - data object NavigateToMyPage : SignInSideEffect() + data class NavigateToHome(val token: String) : SignInSideEffect() data object NavigateToSignUp : SignInSideEffect() } diff --git a/app/src/main/java/org/sopt/and/presentation/signin/SignInViewModel.kt b/app/src/main/java/org/sopt/and/presentation/signin/SignInViewModel.kt index 8e916cf..f1c4599 100644 --- a/app/src/main/java/org/sopt/and/presentation/signin/SignInViewModel.kt +++ b/app/src/main/java/org/sopt/and/presentation/signin/SignInViewModel.kt @@ -10,11 +10,15 @@ import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import org.sopt.and.R +import org.sopt.and.domain.entity.User +import org.sopt.and.domain.repository.SignInRepository import org.sopt.and.presentation.signin.state.SignInUiState import javax.inject.Inject @HiltViewModel -class SignInViewModel @Inject constructor() : ViewModel() { +class SignInViewModel @Inject constructor( + private val signInRepository: SignInRepository +) : ViewModel() { private var _uiState = MutableStateFlow(SignInUiState()) val uiState = _uiState.asStateFlow() @@ -33,25 +37,23 @@ class SignInViewModel @Inject constructor() : ViewModel() { ) } - fun onLoginButtonClick(id: String, password: String) = viewModelScope.launch { - if (isLoginPossible(id, password)) { - _sideEffect.emit(SignInSideEffect.NavigateToMyPage) - } else { - _sideEffect.emit(SignInSideEffect.SnackBar(R.string.signin_snackbar_fail)) + fun onSignUpButtonClick() { + viewModelScope.launch { + _sideEffect.emit(SignInSideEffect.NavigateToSignUp) } } - fun onSignUpButtonClick() = viewModelScope.launch { - _sideEffect.emit(SignInSideEffect.NavigateToSignUp) - } - - /*id, password -> 회원가입 화면에서 가져온 아이디 비번*/ - private fun isLoginPossible(id: String, password: String): Boolean { - val isIdCorrect = _uiState.value.id == id && id.isNotBlank() - val isPasswordCorrect = _uiState.value.password == password && password.isNotBlank() - - return isIdCorrect && isPasswordCorrect + fun onSignInButtonClick() = viewModelScope.launch { + val user = with(_uiState.value) { User(id, password, "") } + signInRepository.signInUser(user).onSuccess { response -> + val token = response.token + if(token != null) { + _sideEffect.emit(SignInSideEffect.NavigateToHome(token)) + } else { + _sideEffect.emit(SignInSideEffect.SnackBar(R.string.signin_snackbar_fail)) + } + }.onFailure { + _sideEffect.emit(SignInSideEffect.SnackBar(R.string.signin_snackbar_fail)) + } } - - } \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/presentation/signin/navigation/SignInNavigation.kt b/app/src/main/java/org/sopt/and/presentation/signin/navigation/SignInNavigation.kt index 20b59a7..ae3eef7 100644 --- a/app/src/main/java/org/sopt/and/presentation/signin/navigation/SignInNavigation.kt +++ b/app/src/main/java/org/sopt/and/presentation/signin/navigation/SignInNavigation.kt @@ -7,10 +7,8 @@ import androidx.navigation.NavHostController import androidx.navigation.NavOptions import androidx.navigation.compose.composable import kotlinx.serialization.Serializable -import org.sopt.and.core.extension.getId -import org.sopt.and.core.extension.getPassword import org.sopt.and.core.navigation.Route -import org.sopt.and.presentation.mypage.navigation.navigateToMyPage +import org.sopt.and.presentation.home.navigation.navigateToHome import org.sopt.and.presentation.signin.SignInRoute import org.sopt.and.presentation.signup.navigation.navigateToSignUp @@ -24,9 +22,7 @@ fun NavGraphBuilder.signInScreen( composable { SignInRoute( navigateToSignUp = navController::navigateToSignUp, - navigateToMyPage = navController::navigateToMyPage, - signUpId = navController.getId(), - signUpPassword = navController.getPassword(), + navigateToHome = navController::navigateToHome, modifier = modifier ) } diff --git a/app/src/main/java/org/sopt/and/presentation/signup/SignUpScreen.kt b/app/src/main/java/org/sopt/and/presentation/signup/SignUpScreen.kt index 481d3ca..7c602c3 100644 --- a/app/src/main/java/org/sopt/and/presentation/signup/SignUpScreen.kt +++ b/app/src/main/java/org/sopt/and/presentation/signup/SignUpScreen.kt @@ -10,17 +10,16 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.flowWithLifecycle -import androidx.lifecycle.viewmodel.compose.viewModel import org.sopt.and.R import org.sopt.and.core.designsystem.component.SocialAccountGroup import org.sopt.and.core.designsystem.component.text.BulletAnnotedText @@ -36,7 +35,7 @@ import org.sopt.and.presentation.signup.state.SignUpUiState fun SignUpRoute( navigateUp: (String, String) -> Unit, modifier: Modifier = Modifier, - viewModel: SignUpViewModel = viewModel(), + viewModel: SignUpViewModel = hiltViewModel(), ) { val uiState by viewModel.uiState.collectAsStateWithLifecycle() val lifecycleOwner = LocalLifecycleOwner.current @@ -57,9 +56,8 @@ fun SignUpRoute( uiState = uiState, onIdChange = viewModel::updateId, onPasswordChange = viewModel::updatePassword, - onSignUpButtonPress = { - if (uiState.isButtonEnabled) viewModel.checkTextFields() - }, + onHobbyChange = viewModel::updateHobby, + onSignUpButtonPress = viewModel::registerUser, onCloseClick = {} ) } @@ -70,6 +68,7 @@ private fun SignUpScreen( onCloseClick: () -> Unit, onIdChange: (String) -> Unit, onPasswordChange: (String) -> Unit, + onHobbyChange: (String) -> Unit, onSignUpButtonPress: () -> Unit, modifier: Modifier = Modifier, ) { @@ -91,7 +90,6 @@ private fun SignUpScreen( hint = stringResource(R.string.signup_text_field_id_hint), onValueChange = onIdChange, value = uiState.id, - cursorBrush = SolidColor(Color.Blue), modifier = Modifier.padding(top = 20.dp, start = 5.dp, end = 5.dp) ) Text( @@ -105,7 +103,7 @@ private fun SignUpScreen( hint = stringResource(R.string.signup_text_field_pw_hint), value = uiState.password, onValueChange = onPasswordChange, - modifier = Modifier.padding(top = 10.dp, start = 5.dp, end = 5.dp) + modifier = Modifier.padding(top = 15.dp, start = 5.dp, end = 5.dp) ) Text( text = stringResource(R.string.signup_text_field_pw_guide), @@ -114,6 +112,19 @@ private fun SignUpScreen( modifier = Modifier.padding(start = 5.dp, end = 5.dp) ) + WavveBasicTextField( + hint = stringResource(R.string.signup_text_field_hobby_hint), + value = uiState.hobby, + onValueChange = onHobbyChange, + modifier = Modifier.padding(top = 15.dp, start = 5.dp, end = 5.dp) + ) + Text( + text = stringResource(R.string.signup_text_field_hobby_guide), + color = Color.Gray, + fontSize = 12.sp, + modifier = Modifier.padding(start = 5.dp, end = 5.dp) + ) + SocialAccountGroup(modifier = modifier.padding(top = 40.dp)) BulletAnnotedText( @@ -138,6 +149,7 @@ fun SignUpScreenPreview() { onCloseClick = {}, onIdChange = {}, onPasswordChange = {}, + onHobbyChange = {}, onSignUpButtonPress = {} ) } \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/presentation/signup/SignUpViewModel.kt b/app/src/main/java/org/sopt/and/presentation/signup/SignUpViewModel.kt index e114dc6..39838f3 100644 --- a/app/src/main/java/org/sopt/and/presentation/signup/SignUpViewModel.kt +++ b/app/src/main/java/org/sopt/and/presentation/signup/SignUpViewModel.kt @@ -2,6 +2,7 @@ package org.sopt.and.presentation.signup import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asSharedFlow @@ -9,9 +10,15 @@ import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import org.sopt.and.R +import org.sopt.and.domain.entity.User +import org.sopt.and.domain.repository.SignUpRepository import org.sopt.and.presentation.signup.state.SignUpUiState +import javax.inject.Inject -class SignUpViewModel : ViewModel() { +@HiltViewModel +class SignUpViewModel @Inject constructor( + private val signUpRepository: SignUpRepository +) : ViewModel() { private val _uiState = MutableStateFlow(SignUpUiState()) val uiState = _uiState.asStateFlow() @@ -32,54 +39,34 @@ class SignUpViewModel : ViewModel() { updateButtonEnabled() } + fun updateHobby(hobby: String) { + _uiState.update { currentState -> + currentState.copy(hobby = hobby) + } + updateButtonEnabled() + } + private fun updateButtonEnabled() = _uiState.update { currentState -> currentState.copy( - isButtonEnabled = _uiState.value.id.isNotBlank() && _uiState.value.password.isNotBlank() + isButtonEnabled = _uiState.value.id.isNotBlank() + && _uiState.value.password.isNotBlank() + && _uiState.value.hobby.isNotBlank() ) } - fun checkTextFields() = viewModelScope.launch { - if (_uiState.value.isButtonEnabled) { - val isValidEmail = isValidEmail(_uiState.value.id) - val isValidPassword = isValidPassword(_uiState.value.password) - - when { - isValidEmail && isValidPassword -> { - with(_sideEffect) { - emit(SignUpSideEffect.Toast(R.string.signup_toast_success)) - emit(SignUpSideEffect.NavigateUp) + fun registerUser() = viewModelScope.launch { + with(_uiState.value) { + if (isButtonEnabled) { + signUpRepository.registerUser(User(id, password, hobby)) + .onSuccess { response -> + _sideEffect.emit(SignUpSideEffect.Toast(response.message)) + if (response.id != null) { + _sideEffect.emit(SignUpSideEffect.NavigateUp) + } + }.onFailure { + _sideEffect.emit(SignUpSideEffect.Toast(R.string.signup_toast_failure_unknown)) } - } - - !isValidEmail -> _sideEffect.emit(SignUpSideEffect.Toast(R.string.signup_toast_failure_email)) - else -> _sideEffect.emit(SignUpSideEffect.Toast(R.string.signup_toast_failure_password)) } } } - - private fun isValidEmail(email: String): Boolean = email.matches(EMAIL_REGEX.toRegex()) - - private fun isValidPassword(password: String): Boolean { - var count = 0 - - if (password.contains(UPPER_CASE_REGEX.toRegex())) count++ - if (password.contains(LOWER_CASE_REGEX.toRegex())) count++ - if (password.contains(DIGIT_REGEX.toRegex())) count++ - if (password.contains(SPECIAL_CHAR_REGEX.toRegex())) count++ - - return password.length in PWD_LENGTH_MIN..PWD_LENGTH_MAX && count >= PWD_TYPE_MIX - } - - companion object { - private const val PWD_LENGTH_MIN = 8 - private const val PWD_LENGTH_MAX = 20 - private const val PWD_TYPE_MIX = 3 - - const val EMAIL_REGEX = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\$" - const val UPPER_CASE_REGEX = "[A-Z]" - const val LOWER_CASE_REGEX = "[a-z]" - const val DIGIT_REGEX = "[0-9]" - const val SPECIAL_CHAR_REGEX = "[!@#\$%^&*(),.?\":{}|<>]" - - } } \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/presentation/signup/state/SignUpUiState.kt b/app/src/main/java/org/sopt/and/presentation/signup/state/SignUpUiState.kt index a6592a9..87f8716 100644 --- a/app/src/main/java/org/sopt/and/presentation/signup/state/SignUpUiState.kt +++ b/app/src/main/java/org/sopt/and/presentation/signup/state/SignUpUiState.kt @@ -3,5 +3,6 @@ package org.sopt.and.presentation.signup.state data class SignUpUiState( val id: String = "", val password: String = "", + val hobby: String = "", val isButtonEnabled: Boolean = false ) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9487a88..7fb8b4b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -7,16 +7,21 @@ 만으로\n Wavve를 즐길 수 있어요! - wavve@example.com - 로그인, 비밀번호 찾기, 알림에 사용되니 정확한 이메일을 입력해주세요 + Wavve 아이디 설정 + 로그인, 비밀번호 찾기, 알림에 사용됩니다. 8자 이내로 입력해주세요. Wavve 비밀번호 설정 - 비밀번호는 8~20자 이내로 영문 대소문자, 숫자, 특수문자 중 3가지 이상 혼용하여 입력해주세요. + 비밀번호는 8자 이내로 입력해주세요. + 취미 (예시: 영화, 음악, 게임) + 취미는 8자 이내로 입력해주세요. 또는 다른 서비스 계정으로 가입 "\tSNS계정을 간편하게 가입하여 서비스를 이용하실 수 있습니다. 기\n\t존 POOQ 계정 또는 Wavve 계정과는 연동되지 않으니 이용에 참고\n\t하세요." Wavve 회원가입 회원가입을 성공했습니다. 이메일 형식이 올바르지 않습니다. 비밀번호 형식이 올바르지 않습니다. + 모든 입력은 8자 이하여야 합니다. + 이미 존재하는 아이디입니다. + 회원가입에 실패하였습니다. WAVVE @@ -30,6 +35,7 @@ 또는 다른 서비스 계정으로 로그인 "\tSNS계정을 간편하게 가입하여 서비스를 이용하실 수 있습니다.\n\t기존 POOQ 계정 또는 Wavve 계정과는 연동되지 않으니 이용에 참고하세요." 아이디 또는 비밀번호가 일치하지 않습니다. + 로그인에 성공했습니다. 닫기 @@ -43,7 +49,6 @@ 시청내역이 없어요. 관심 프로그램 관심 프로그램이 없어요. - 로그인에 성공했습니다. 닫기 로그아웃 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7b787ac..ff7a96b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -33,6 +33,11 @@ room = "2.5.1" ## Coil coil = "2.7.0" +## Retrofit +okhttp = "4.12.0" +retrofit = "2.11.0" +retrofitJsonConverter = "1.0.0" + [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } @@ -79,6 +84,14 @@ room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = ## coil coil = {group = "io.coil-kt", name="coil-compose", version.ref="coil"} +## retrofit +okhttp = { group = "com.squareup.okhttp3", name = "okhttp" } +okhttp-bom = { group = "com.squareup.okhttp3", name = "okhttp-bom", version.ref = "okhttp" } +okhttp-logging = { group = "com.squareup.okhttp3", name = "logging-interceptor", version.ref = "okhttp" } +retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" } +retrofit-kotlinx-serialization-converter = { group = "com.jakewharton.retrofit", name = "retrofit2-kotlinx-serialization-converter", version.ref = "retrofitJsonConverter" } + + [plugins] android-application = { id = "com.android.application", version.ref = "agp" } kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }