회원가입, 로그인 페이지 추가
| @@ -112,5 +112,7 @@ dependencies { | ||||
|     // permission | ||||
|     implementation "io.github.ParkSangGwon:tedpermission-normal:3.3.0" | ||||
|  | ||||
|     implementation 'com.github.dhaval2404:imagepicker:2.1' | ||||
|  | ||||
|     implementation 'com.google.android.gms:play-services-oss-licenses:17.0.1' | ||||
| } | ||||
|   | ||||
| @@ -2,6 +2,8 @@ | ||||
| <manifest xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     xmlns:tools="http://schemas.android.com/tools"> | ||||
|  | ||||
|     <uses-permission android:name="android.permission.INTERNET" /> | ||||
|  | ||||
|     <application | ||||
|         android:name=".app.SodaLiveApp" | ||||
|         android:allowBackup="true" | ||||
| @@ -11,7 +13,6 @@ | ||||
|         android:icon="@mipmap/ic_launcher" | ||||
|         android:label="@string/app_name" | ||||
|         android:largeHeap="true" | ||||
|         android:roundIcon="@mipmap/ic_launcher_round" | ||||
|         android:supportsRtl="true" | ||||
|         android:theme="@style/Theme.SodaLive" | ||||
|         android:usesCleartextTraffic="true" | ||||
| @@ -26,6 +27,10 @@ | ||||
|             </intent-filter> | ||||
|         </activity> | ||||
|         <activity android:name=".main.MainActivity" /> | ||||
|         <activity android:name=".user.login.LoginActivity" /> | ||||
|         <activity android:name=".user.signup.SignUpActivity" /> | ||||
|         <activity android:name=".settings.TermsActivity" /> | ||||
|         <activity android:name=".user.find_password.FindPasswordActivity" /> | ||||
|  | ||||
|         <activity | ||||
|             android:name="com.google.android.gms.oss.licenses.OssLicensesMenuActivity" | ||||
|   | ||||
| @@ -0,0 +1,9 @@ | ||||
| package kr.co.vividnext.sodalive.common | ||||
|  | ||||
| import retrofit2.Retrofit | ||||
|  | ||||
| class ApiBuilder { | ||||
|     fun <T> build(retrofit: Retrofit, service: Class<T>): T { | ||||
|         return retrofit.create(service) | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,10 @@ | ||||
| package kr.co.vividnext.sodalive.common | ||||
|  | ||||
| import com.google.gson.annotations.SerializedName | ||||
|  | ||||
| data class ApiResponse<T>( | ||||
|     @SerializedName("success") val success: Boolean, | ||||
|     @SerializedName("data") val data: T? = null, | ||||
|     @SerializedName("message") val message: String? = null, | ||||
|     @SerializedName("errorProperty") val errorProperty: String? = null | ||||
| ) | ||||
| @@ -1,4 +1,12 @@ | ||||
| package kr.co.vividnext.sodalive.common | ||||
|  | ||||
| object Constants { | ||||
|     const val PREF_TOKEN = "pref_token" | ||||
|     const val PREF_EMAIL = "pref_email" | ||||
|     const val PREF_USER_ID = "pref_user_id" | ||||
|     const val PREF_NICKNAME = "pref_nickname" | ||||
|     const val PREF_PROFILE_IMAGE = "pref_profile_image" | ||||
|  | ||||
|     const val EXTRA_DATA = "extra_data" | ||||
|     const val EXTRA_TERMS = "extra_terms" | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,49 @@ | ||||
| package kr.co.vividnext.sodalive.common | ||||
|  | ||||
| import android.app.Activity | ||||
| import android.graphics.Color | ||||
| import android.graphics.drawable.AnimationDrawable | ||||
| import android.graphics.drawable.ColorDrawable | ||||
| import android.view.LayoutInflater | ||||
| import android.view.WindowManager | ||||
| import androidx.appcompat.app.AlertDialog | ||||
| import kr.co.vividnext.sodalive.databinding.DialogLoadingBinding | ||||
| import kr.co.vividnext.sodalive.extensions.dpToPx | ||||
|  | ||||
| class LoadingDialog( | ||||
|     activity: Activity, | ||||
|     layoutInflater: LayoutInflater | ||||
| ) { | ||||
|     private val alertDialog: AlertDialog | ||||
|     private val dialogView = DialogLoadingBinding.inflate(layoutInflater) | ||||
|     private val animationDrawable: AnimationDrawable | ||||
|  | ||||
|     init { | ||||
|         val dialogBuilder = AlertDialog.Builder(activity) | ||||
|         dialogBuilder.setView(dialogView.root) | ||||
|  | ||||
|         animationDrawable = dialogView.tvLoading.compoundDrawables[1] as AnimationDrawable | ||||
|  | ||||
|         alertDialog = dialogBuilder.create() | ||||
|         alertDialog.setCancelable(false) | ||||
|         alertDialog.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) | ||||
|     } | ||||
|  | ||||
|     fun show(width: Int, message: String = "") { | ||||
|         alertDialog.show() | ||||
|         animationDrawable.start() | ||||
|         dialogView.tvLoading.text = message | ||||
|  | ||||
|         val lp = WindowManager.LayoutParams() | ||||
|         lp.copyFrom(alertDialog.window?.attributes) | ||||
|         lp.width = width - (26.7f.dpToPx()).toInt() | ||||
|         lp.height = WindowManager.LayoutParams.WRAP_CONTENT | ||||
|  | ||||
|         alertDialog.window?.attributes = lp | ||||
|     } | ||||
|  | ||||
|     fun dismiss() { | ||||
|         animationDrawable.stop() | ||||
|         alertDialog.dismiss() | ||||
|     } | ||||
| } | ||||
| @@ -43,4 +43,34 @@ object SharedPreferenceManager { | ||||
|             else -> throw UnsupportedOperationException("Error") | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     var token: String | ||||
|         get() = sharedPreferences[Constants.PREF_TOKEN, ""] | ||||
|         set(value) { | ||||
|             sharedPreferences[Constants.PREF_TOKEN] = value | ||||
|         } | ||||
|  | ||||
|     var userId: Long | ||||
|         get() = sharedPreferences[Constants.PREF_USER_ID, 0] | ||||
|         set(value) { | ||||
|             sharedPreferences[Constants.PREF_USER_ID] = value | ||||
|         } | ||||
|  | ||||
|     var nickname: String | ||||
|         get() = sharedPreferences[Constants.PREF_NICKNAME, ""] | ||||
|         set(value) { | ||||
|             sharedPreferences[Constants.PREF_NICKNAME] = value | ||||
|         } | ||||
|  | ||||
|     var email: String | ||||
|         get() = sharedPreferences[Constants.PREF_EMAIL, ""] | ||||
|         set(value) { | ||||
|             sharedPreferences[Constants.PREF_EMAIL] = value | ||||
|         } | ||||
|  | ||||
|     var profileImage: String | ||||
|         get() = sharedPreferences[Constants.PREF_PROFILE_IMAGE, ""] | ||||
|         set(value) { | ||||
|             sharedPreferences[Constants.PREF_PROFILE_IMAGE] = value | ||||
|         } | ||||
| } | ||||
|   | ||||
| @@ -3,10 +3,20 @@ package kr.co.vividnext.sodalive.di | ||||
| import android.content.Context | ||||
| import com.google.gson.GsonBuilder | ||||
| import kr.co.vividnext.sodalive.BuildConfig | ||||
| import kr.co.vividnext.sodalive.common.ApiBuilder | ||||
| import kr.co.vividnext.sodalive.network.TokenAuthenticator | ||||
| import kr.co.vividnext.sodalive.settings.TermsApi | ||||
| import kr.co.vividnext.sodalive.settings.TermsRepository | ||||
| import kr.co.vividnext.sodalive.settings.TermsViewModel | ||||
| import kr.co.vividnext.sodalive.user.UserApi | ||||
| import kr.co.vividnext.sodalive.user.UserRepository | ||||
| import kr.co.vividnext.sodalive.user.find_password.FindPasswordViewModel | ||||
| import kr.co.vividnext.sodalive.user.login.LoginViewModel | ||||
| import kr.co.vividnext.sodalive.user.signup.SignUpViewModel | ||||
| import okhttp3.OkHttpClient | ||||
| import okhttp3.logging.HttpLoggingInterceptor | ||||
| import org.koin.android.ext.koin.androidContext | ||||
| import org.koin.androidx.viewmodel.dsl.viewModel | ||||
| import org.koin.core.context.startKoin | ||||
| import org.koin.dsl.module | ||||
| import retrofit2.Retrofit | ||||
| @@ -44,11 +54,22 @@ class AppDI(private val context: Context, isDebugMode: Boolean) { | ||||
|                 .client(get()) | ||||
|                 .build() | ||||
|         } | ||||
|  | ||||
|         single { ApiBuilder().build(get(), UserApi::class.java) } | ||||
|         single { ApiBuilder().build(get(), TermsApi::class.java) } | ||||
|     } | ||||
|  | ||||
|     private val viewModelModule = module {} | ||||
|     private val viewModelModule = module { | ||||
|         viewModel { LoginViewModel(get()) } | ||||
|         viewModel { SignUpViewModel(get()) } | ||||
|         viewModel { TermsViewModel(get()) } | ||||
|         viewModel { FindPasswordViewModel(get()) } | ||||
|     } | ||||
|  | ||||
|     private val repositoryModule = module {} | ||||
|     private val repositoryModule = module { | ||||
|         factory { UserRepository(get()) } | ||||
|         factory { TermsRepository(get()) } | ||||
|     } | ||||
|  | ||||
|     private val moduleList = listOf( | ||||
|         networkModule, | ||||
|   | ||||
| @@ -0,0 +1,18 @@ | ||||
| package kr.co.vividnext.sodalive.extensions | ||||
|  | ||||
| import android.content.res.Resources | ||||
| import android.util.DisplayMetrics | ||||
| import java.text.DecimalFormat | ||||
|  | ||||
| fun Float.dpToPx(): Float { | ||||
|     val metrics = Resources.getSystem().displayMetrics | ||||
|     return this * (metrics.densityDpi.toFloat() / DisplayMetrics.DENSITY_DEFAULT) | ||||
| } | ||||
|  | ||||
| fun Int.dpToPx(): Float { | ||||
|     val metrics = Resources.getSystem().displayMetrics | ||||
|     return this.toFloat() * (metrics.densityDpi.toFloat() / DisplayMetrics.DENSITY_DEFAULT) | ||||
| } | ||||
|  | ||||
| fun Int.moneyFormat(): String = DecimalFormat("###,###").format(this) | ||||
| fun Long.moneyFormat(): String = DecimalFormat("###,###").format(this) | ||||
| @@ -0,0 +1,8 @@ | ||||
| package kr.co.vividnext.sodalive.settings | ||||
|  | ||||
| import com.google.gson.annotations.SerializedName | ||||
|  | ||||
| data class GetTermsResponse( | ||||
|     @SerializedName("title") val title: String, | ||||
|     @SerializedName("description") val description: String | ||||
| ) | ||||
| @@ -0,0 +1,84 @@ | ||||
| package kr.co.vividnext.sodalive.settings | ||||
|  | ||||
| import android.annotation.SuppressLint | ||||
| import android.os.Bundle | ||||
| import android.widget.Toast | ||||
| import androidx.webkit.WebSettingsCompat | ||||
| import androidx.webkit.WebViewFeature | ||||
| import kr.co.vividnext.sodalive.base.BaseActivity | ||||
| import kr.co.vividnext.sodalive.common.Constants | ||||
| import kr.co.vividnext.sodalive.common.LoadingDialog | ||||
| import kr.co.vividnext.sodalive.databinding.ActivityTermsBinding | ||||
| import org.koin.android.ext.android.inject | ||||
|  | ||||
| class TermsActivity : BaseActivity<ActivityTermsBinding>(ActivityTermsBinding::inflate) { | ||||
|  | ||||
|     private val viewModel: TermsViewModel by inject() | ||||
|  | ||||
|     private lateinit var loadingDialog: LoadingDialog | ||||
|  | ||||
|     override fun onCreate(savedInstanceState: Bundle?) { | ||||
|         super.onCreate(savedInstanceState) | ||||
|         bindData() | ||||
|  | ||||
|         val terms = intent.getStringExtra(Constants.EXTRA_TERMS) ?: "terms" | ||||
|         if (terms == "privacy") { | ||||
|             viewModel.getPrivacyPolicy() | ||||
|         } else { | ||||
|             viewModel.getTermsOfService() | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @SuppressLint("SetJavaScriptEnabled") | ||||
|     override fun setupView() { | ||||
|         if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) { | ||||
|             WebSettingsCompat.setForceDark( | ||||
|                 binding.webView.settings, | ||||
|                 WebSettingsCompat.FORCE_DARK_ON | ||||
|             ) | ||||
|         } | ||||
|  | ||||
|         loadingDialog = LoadingDialog(this, layoutInflater) | ||||
|         binding.toolbar.tvBack.setOnClickListener { finish() } | ||||
|  | ||||
|         binding.webView.settings.apply { | ||||
|             javaScriptEnabled = false // 자바스크립트 실행 허용 | ||||
|             javaScriptCanOpenWindowsAutomatically = false // 자바스크립트에서 새창 실 행 허용 | ||||
|             setSupportMultipleWindows(false) // 새 창 실행 허용 | ||||
|             loadWithOverviewMode = true // 메타 태그 허용 | ||||
|  | ||||
|             useWideViewPort = true // 화면 사이즈 맞추기 허용 | ||||
|             setSupportZoom(false) // 화면 줌 허용 | ||||
|             builtInZoomControls = false // 화면 확대 축소 허용 여부 | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private fun bindData() { | ||||
|         viewModel.isLoading.observe(this) { | ||||
|             if (it) { | ||||
|                 loadingDialog.show(screenWidth, "공지사항을 불러오고 있습니다.") | ||||
|             } else { | ||||
|                 loadingDialog.dismiss() | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         viewModel.toastLiveData.observe(this) { | ||||
|             it?.let { Toast.makeText(applicationContext, it, Toast.LENGTH_LONG).show() } | ||||
|         } | ||||
|  | ||||
|         viewModel.titleLiveData.observe(this) { | ||||
|             binding.toolbar.tvBack.text = it | ||||
|         } | ||||
|  | ||||
|         viewModel.termsLiveData.observe(this) { | ||||
|             val viewPort = | ||||
|                 "<meta name=\"viewport\" content=\"width=device-width, initial-scale=0.8\">" | ||||
|             val data = viewPort + it | ||||
|             binding.webView.loadData( | ||||
|                 data, | ||||
|                 "text/html; charset=utf-8", | ||||
|                 "utf-8" | ||||
|             ) | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,13 @@ | ||||
| package kr.co.vividnext.sodalive.settings | ||||
|  | ||||
| import io.reactivex.rxjava3.core.Single | ||||
| import kr.co.vividnext.sodalive.common.ApiResponse | ||||
| import retrofit2.http.GET | ||||
|  | ||||
| interface TermsApi { | ||||
|     @GET("/stplat/terms_of_service") | ||||
|     fun getTermsOfService(): Single<ApiResponse<GetTermsResponse>> | ||||
|  | ||||
|     @GET("/stplat/privacy_policy") | ||||
|     fun getPrivacyPolicy(): Single<ApiResponse<GetTermsResponse>> | ||||
| } | ||||
| @@ -0,0 +1,6 @@ | ||||
| package kr.co.vividnext.sodalive.settings | ||||
|  | ||||
| class TermsRepository(private val api: TermsApi) { | ||||
|     fun getTermsOfService() = api.getTermsOfService() | ||||
|     fun getPrivacyPolicy() = api.getPrivacyPolicy() | ||||
| } | ||||
| @@ -0,0 +1,92 @@ | ||||
| package kr.co.vividnext.sodalive.settings | ||||
|  | ||||
| import androidx.lifecycle.LiveData | ||||
| import androidx.lifecycle.MutableLiveData | ||||
| import com.orhanobut.logger.Logger | ||||
| import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers | ||||
| import io.reactivex.rxjava3.schedulers.Schedulers | ||||
| import kr.co.vividnext.sodalive.base.BaseViewModel | ||||
|  | ||||
| class TermsViewModel(private val repository: TermsRepository) : BaseViewModel() { | ||||
|     private val _titleLiveData = MutableLiveData<String>() | ||||
|     val titleLiveData: LiveData<String> | ||||
|         get() = _titleLiveData | ||||
|  | ||||
|     private val _termsLiveData = MutableLiveData<String>() | ||||
|     val termsLiveData: LiveData<String> | ||||
|         get() = _termsLiveData | ||||
|  | ||||
|     private val _toastLiveData = MutableLiveData<String?>() | ||||
|     val toastLiveData: LiveData<String?> | ||||
|         get() = _toastLiveData | ||||
|  | ||||
|     private var _isLoading = MutableLiveData(false) | ||||
|     val isLoading: LiveData<Boolean> | ||||
|         get() = _isLoading | ||||
|  | ||||
|     fun getTermsOfService() { | ||||
|         _isLoading.value = true | ||||
|  | ||||
|         compositeDisposable.add( | ||||
|             repository.getTermsOfService() | ||||
|                 .subscribeOn(Schedulers.io()) | ||||
|                 .observeOn(AndroidSchedulers.mainThread()) | ||||
|                 .subscribe( | ||||
|                     { | ||||
|                         _isLoading.value = false | ||||
|  | ||||
|                         if (it.success && it.data != null) { | ||||
|                             _titleLiveData.postValue(it.data.title) | ||||
|                             _termsLiveData.postValue(it.data.description) | ||||
|                         } else { | ||||
|                             if (it.message != null) { | ||||
|                                 _toastLiveData.postValue(it.message) | ||||
|                             } else { | ||||
|                                 _toastLiveData.postValue( | ||||
|                                     "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요." | ||||
|                                 ) | ||||
|                             } | ||||
|                         } | ||||
|                     }, | ||||
|                     { | ||||
|                         _isLoading.value = false | ||||
|                         it.message?.let { message -> Logger.e(message) } | ||||
|                         _toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.") | ||||
|                     } | ||||
|                 ) | ||||
|         ) | ||||
|     } | ||||
|  | ||||
|     fun getPrivacyPolicy() { | ||||
|         _isLoading.value = true | ||||
|  | ||||
|         compositeDisposable.add( | ||||
|             repository.getPrivacyPolicy() | ||||
|                 .subscribeOn(Schedulers.io()) | ||||
|                 .observeOn(AndroidSchedulers.mainThread()) | ||||
|                 .subscribe( | ||||
|                     { | ||||
|                         _isLoading.value = false | ||||
|  | ||||
|                         if (it.success && it.data != null) { | ||||
|                             _titleLiveData.postValue(it.data.title) | ||||
|                             _termsLiveData.postValue(it.data.description) | ||||
|                         } else { | ||||
|                             if (it.message != null) { | ||||
|                                 _toastLiveData.postValue(it.message) | ||||
|                             } else { | ||||
|                                 _toastLiveData.postValue( | ||||
|                                     "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요." | ||||
|                                 ) | ||||
|                             } | ||||
|                         } | ||||
|                     }, | ||||
|                     { | ||||
|                         _isLoading.value = false | ||||
|                         it.message?.let { message -> Logger.e(message) } | ||||
|                         _toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.") | ||||
|                     } | ||||
|                 ) | ||||
|         ) | ||||
|     } | ||||
| } | ||||
| @@ -6,8 +6,10 @@ import android.os.Bundle | ||||
| import android.os.Handler | ||||
| import android.os.Looper | ||||
| import kr.co.vividnext.sodalive.base.BaseActivity | ||||
| import kr.co.vividnext.sodalive.common.SharedPreferenceManager | ||||
| import kr.co.vividnext.sodalive.databinding.ActivitySplashBinding | ||||
| import kr.co.vividnext.sodalive.main.MainActivity | ||||
| import kr.co.vividnext.sodalive.user.login.LoginActivity | ||||
|  | ||||
| @SuppressLint("CustomSplashScreen") | ||||
| class SplashActivity : BaseActivity<ActivitySplashBinding>(ActivitySplashBinding::inflate) { | ||||
| @@ -17,6 +19,14 @@ class SplashActivity : BaseActivity<ActivitySplashBinding>(ActivitySplashBinding | ||||
|     override fun onCreate(savedInstanceState: Bundle?) { | ||||
|         super.onCreate(savedInstanceState) | ||||
|  | ||||
|         if (SharedPreferenceManager.token.isBlank()) { | ||||
|             showLoginActivity() | ||||
|         } else { | ||||
|             showMainActivity() | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private fun showMainActivity() { | ||||
|         handler.postDelayed({ | ||||
|             startActivity( | ||||
|                 Intent(applicationContext, MainActivity::class.java).apply { | ||||
| @@ -28,5 +38,17 @@ class SplashActivity : BaseActivity<ActivitySplashBinding>(ActivitySplashBinding | ||||
|         }, 500) | ||||
|     } | ||||
|  | ||||
|     private fun showLoginActivity() { | ||||
|         handler.postDelayed({ | ||||
|             startActivity( | ||||
|                 Intent(applicationContext, LoginActivity::class.java).apply { | ||||
|                     intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) | ||||
|                     intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP) | ||||
|                 } | ||||
|             ) | ||||
|             finish() | ||||
|         }, 500) | ||||
|     } | ||||
|  | ||||
|     override fun setupView() {} | ||||
| } | ||||
|   | ||||
							
								
								
									
										12
									
								
								app/src/main/java/kr/co/vividnext/sodalive/user/Gender.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,12 @@ | ||||
| package kr.co.vividnext.sodalive.user | ||||
|  | ||||
| import com.google.gson.annotations.SerializedName | ||||
|  | ||||
| enum class Gender { | ||||
|     @SerializedName("MALE") | ||||
|     MALE, | ||||
|     @SerializedName("FEMALE") | ||||
|     FEMALE, | ||||
|     @SerializedName("NONE") | ||||
|     NONE | ||||
| } | ||||
							
								
								
									
										28
									
								
								app/src/main/java/kr/co/vividnext/sodalive/user/UserApi.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,28 @@ | ||||
| package kr.co.vividnext.sodalive.user | ||||
|  | ||||
| import io.reactivex.rxjava3.core.Single | ||||
| import kr.co.vividnext.sodalive.common.ApiResponse | ||||
| import kr.co.vividnext.sodalive.user.find_password.ForgotPasswordRequest | ||||
| import kr.co.vividnext.sodalive.user.login.LoginRequest | ||||
| import kr.co.vividnext.sodalive.user.login.LoginResponse | ||||
| import okhttp3.MultipartBody | ||||
| import okhttp3.RequestBody | ||||
| import retrofit2.http.Body | ||||
| import retrofit2.http.Multipart | ||||
| import retrofit2.http.POST | ||||
| import retrofit2.http.Part | ||||
|  | ||||
| interface UserApi { | ||||
|     @POST("/member/login") | ||||
|     fun login(@Body request: LoginRequest): Single<ApiResponse<LoginResponse>> | ||||
|  | ||||
|     @POST("/member/signup") | ||||
|     @Multipart | ||||
|     fun signUp( | ||||
|         @Part profileImage: MultipartBody.Part?, | ||||
|         @Part("request") request: RequestBody | ||||
|     ): Single<ApiResponse<LoginResponse>> | ||||
|  | ||||
|     @POST("/member/forgot-password") | ||||
|     fun findPassword(@Body request: ForgotPasswordRequest): Single<ApiResponse<Any>> | ||||
| } | ||||
| @@ -0,0 +1,17 @@ | ||||
| package kr.co.vividnext.sodalive.user | ||||
|  | ||||
| import kr.co.vividnext.sodalive.user.find_password.ForgotPasswordRequest | ||||
| import kr.co.vividnext.sodalive.user.login.LoginRequest | ||||
| import okhttp3.MultipartBody | ||||
| import okhttp3.RequestBody | ||||
|  | ||||
| class UserRepository(private val userApi: UserApi) { | ||||
|     fun login(request: LoginRequest) = userApi.login(request) | ||||
|  | ||||
|     fun signUp(profileImage: MultipartBody.Part?, request: RequestBody) = userApi.signUp( | ||||
|         profileImage, | ||||
|         request | ||||
|     ) | ||||
|  | ||||
|     fun findPassword(request: ForgotPasswordRequest) = userApi.findPassword(request = request) | ||||
| } | ||||
| @@ -0,0 +1,67 @@ | ||||
| package kr.co.vividnext.sodalive.user.find_password | ||||
|  | ||||
| import android.content.Intent | ||||
| import android.net.Uri | ||||
| import android.os.Bundle | ||||
| import android.widget.Toast | ||||
| import com.jakewharton.rxbinding4.widget.textChanges | ||||
| import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers | ||||
| import io.reactivex.rxjava3.schedulers.Schedulers | ||||
| import kr.co.vividnext.sodalive.base.BaseActivity | ||||
| import kr.co.vividnext.sodalive.common.LoadingDialog | ||||
| import kr.co.vividnext.sodalive.databinding.ActivityFindPasswordBinding | ||||
| import org.koin.android.ext.android.inject | ||||
|  | ||||
| class FindPasswordActivity : BaseActivity<ActivityFindPasswordBinding>( | ||||
|     ActivityFindPasswordBinding::inflate | ||||
| ) { | ||||
|  | ||||
|     private val viewModel: FindPasswordViewModel by inject() | ||||
|  | ||||
|     private lateinit var loadingDialog: LoadingDialog | ||||
|  | ||||
|     override fun onCreate(savedInstanceState: Bundle?) { | ||||
|         super.onCreate(savedInstanceState) | ||||
|         bindData() | ||||
|     } | ||||
|  | ||||
|     override fun setupView() { | ||||
|         loadingDialog = LoadingDialog(this, layoutInflater) | ||||
|         binding.toolbar.tvBack.text = "비밀번호 재설정" | ||||
|         binding.toolbar.tvBack.setOnClickListener { finish() } | ||||
|  | ||||
|         binding.tvServiceCenter.setOnClickListener { | ||||
|             startActivity( | ||||
|                 Intent( | ||||
|                     Intent.ACTION_VIEW, | ||||
|                     Uri.parse("http://pf.kakao.com/_sZaeb") | ||||
|                 ) | ||||
|             ) | ||||
|         } | ||||
|  | ||||
|         binding.tvFindPassword.setOnClickListener { viewModel.findPassword { finish() } } | ||||
|     } | ||||
|  | ||||
|     private fun bindData() { | ||||
|         compositeDisposable.add( | ||||
|             binding.etEmail.textChanges().skip(1) | ||||
|                 .subscribeOn(Schedulers.io()) | ||||
|                 .observeOn(AndroidSchedulers.mainThread()) | ||||
|                 .subscribe { | ||||
|                     viewModel.email = it.toString() | ||||
|                 } | ||||
|         ) | ||||
|  | ||||
|         viewModel.toastLiveData.observe(this) { | ||||
|             it?.let { Toast.makeText(applicationContext, it, Toast.LENGTH_LONG).show() } | ||||
|         } | ||||
|  | ||||
|         viewModel.isLoading.observe(this) { | ||||
|             if (it) { | ||||
|                 loadingDialog.show(screenWidth, "") | ||||
|             } else { | ||||
|                 loadingDialog.dismiss() | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,61 @@ | ||||
| package kr.co.vividnext.sodalive.user.find_password | ||||
|  | ||||
| import androidx.lifecycle.LiveData | ||||
| import androidx.lifecycle.MutableLiveData | ||||
| import com.orhanobut.logger.Logger | ||||
| import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers | ||||
| import io.reactivex.rxjava3.schedulers.Schedulers | ||||
| import kr.co.vividnext.sodalive.base.BaseViewModel | ||||
| import kr.co.vividnext.sodalive.user.UserRepository | ||||
|  | ||||
| class FindPasswordViewModel(private val repository: UserRepository) : BaseViewModel() { | ||||
|  | ||||
|     var email = "" | ||||
|  | ||||
|     private var _isLoading = MutableLiveData(false) | ||||
|     val isLoading: LiveData<Boolean> | ||||
|         get() = _isLoading | ||||
|  | ||||
|     private val _toastLiveData = MutableLiveData<String?>() | ||||
|     val toastLiveData: LiveData<String?> | ||||
|         get() = _toastLiveData | ||||
|  | ||||
|     fun findPassword(onSuccess: () -> Unit) { | ||||
|         if (email.isBlank()) { | ||||
|             _toastLiveData.postValue("이메일을 입력하세요.") | ||||
|             return | ||||
|         } | ||||
|  | ||||
|         _isLoading.value = true | ||||
|         val request = ForgotPasswordRequest(email = email) | ||||
|         compositeDisposable.add( | ||||
|             repository.findPassword(request) | ||||
|                 .subscribeOn(Schedulers.io()) | ||||
|                 .observeOn(AndroidSchedulers.mainThread()) | ||||
|                 .subscribe( | ||||
|                     { | ||||
|                         _isLoading.value = false | ||||
|                         if (it.success) { | ||||
|                             _toastLiveData.postValue( | ||||
|                                 "임시 비밀번호가 입력하신 이메일로 발송되었습니다. 이메일을 확인해 주세요." | ||||
|                             ) | ||||
|                             onSuccess() | ||||
|                         } else { | ||||
|                             if (it.message != null) { | ||||
|                                 _toastLiveData.postValue(it.message) | ||||
|                             } else { | ||||
|                                 _toastLiveData.postValue( | ||||
|                                     "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요." | ||||
|                                 ) | ||||
|                             } | ||||
|                         } | ||||
|                     }, | ||||
|                     { | ||||
|                         _isLoading.value = false | ||||
|                         it.message?.let { message -> Logger.e(message) } | ||||
|                         _toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.") | ||||
|                     } | ||||
|                 ) | ||||
|         ) | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,5 @@ | ||||
| package kr.co.vividnext.sodalive.user.find_password | ||||
|  | ||||
| import com.google.gson.annotations.SerializedName | ||||
|  | ||||
| data class ForgotPasswordRequest(@SerializedName("email") val email: String) | ||||
| @@ -0,0 +1,123 @@ | ||||
| package kr.co.vividnext.sodalive.user.login | ||||
|  | ||||
| import android.content.Intent | ||||
| import android.os.Bundle | ||||
| import android.text.InputType | ||||
| import android.widget.Toast | ||||
| import com.jakewharton.rxbinding4.widget.textChanges | ||||
| import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers | ||||
| import io.reactivex.rxjava3.schedulers.Schedulers | ||||
| import kr.co.vividnext.sodalive.base.BaseActivity | ||||
| import kr.co.vividnext.sodalive.common.Constants | ||||
| import kr.co.vividnext.sodalive.common.LoadingDialog | ||||
| import kr.co.vividnext.sodalive.databinding.ActivityLoginBinding | ||||
| import kr.co.vividnext.sodalive.main.MainActivity | ||||
| import kr.co.vividnext.sodalive.user.find_password.FindPasswordActivity | ||||
| import kr.co.vividnext.sodalive.user.signup.SignUpActivity | ||||
| import org.koin.android.ext.android.inject | ||||
|  | ||||
| class LoginActivity : BaseActivity<ActivityLoginBinding>(ActivityLoginBinding::inflate) { | ||||
|  | ||||
|     private val viewModel: LoginViewModel by inject() | ||||
|  | ||||
|     private lateinit var loadingDialog: LoadingDialog | ||||
|  | ||||
|     override fun onCreate(savedInstanceState: Bundle?) { | ||||
|         super.onCreate(savedInstanceState) | ||||
|  | ||||
|         bindData() | ||||
|     } | ||||
|  | ||||
|     override fun setupView() { | ||||
|         binding.tvToolbar.text = "로그인" | ||||
|         loadingDialog = LoadingDialog(this, layoutInflater) | ||||
|  | ||||
|         binding.tvLogin.setOnClickListener { | ||||
|             viewModel.login { | ||||
|                 it?.let { Toast.makeText(applicationContext, it, Toast.LENGTH_LONG).show() } | ||||
|                 finishAffinity() | ||||
|                 val nextIntent = Intent(applicationContext, MainActivity::class.java) | ||||
|                 val extras = intent.getBundleExtra(Constants.EXTRA_DATA) | ||||
|                     ?: if (intent.extras != null) { | ||||
|                         intent.extras | ||||
|                     } else { | ||||
|                         null | ||||
|                     } | ||||
|                 if (extras != null) { | ||||
|                     nextIntent.putExtra(Constants.EXTRA_DATA, extras) | ||||
|                 } | ||||
|                 intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) | ||||
|                 intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP) | ||||
|                 startActivity(nextIntent) | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         binding.tvSignUp.setOnClickListener { | ||||
|             val nextIntent = Intent(applicationContext, SignUpActivity::class.java) | ||||
|             val extras = intent.getBundleExtra(Constants.EXTRA_DATA) | ||||
|                 ?: if (intent.extras != null) { | ||||
|                     intent.extras | ||||
|                 } else { | ||||
|                     null | ||||
|                 } | ||||
|             if (extras != null) { | ||||
|                 nextIntent.putExtra(Constants.EXTRA_DATA, extras) | ||||
|             } | ||||
|             startActivity(nextIntent) | ||||
|         } | ||||
|  | ||||
|         binding.tvForgotPassword.setOnClickListener { | ||||
|             startActivity( | ||||
|                 Intent( | ||||
|                     applicationContext, | ||||
|                     FindPasswordActivity::class.java | ||||
|                 ) | ||||
|             ) | ||||
|         } | ||||
|  | ||||
|         binding.tvVisiblePassword.setOnClickListener { viewModel.onClickVisiblePassword() } | ||||
|     } | ||||
|  | ||||
|     private fun bindData() { | ||||
|         compositeDisposable.add( | ||||
|             binding.etEmail.textChanges().skip(1) | ||||
|                 .subscribeOn(Schedulers.io()) | ||||
|                 .observeOn(AndroidSchedulers.mainThread()) | ||||
|                 .subscribe { | ||||
|                     viewModel.email = it.toString() | ||||
|                 } | ||||
|         ) | ||||
|  | ||||
|         compositeDisposable.add( | ||||
|             binding.etPassword.textChanges().skip(1) | ||||
|                 .subscribeOn(Schedulers.io()) | ||||
|                 .observeOn(AndroidSchedulers.mainThread()) | ||||
|                 .subscribe { | ||||
|                     viewModel.password = it.toString() | ||||
|                 } | ||||
|         ) | ||||
|  | ||||
|         viewModel.toastLiveData.observe(this) { | ||||
|             it?.let { Toast.makeText(applicationContext, it, Toast.LENGTH_LONG).show() } | ||||
|         } | ||||
|  | ||||
|         viewModel.isLoading.observe(this) { | ||||
|             if (it) { | ||||
|                 loadingDialog.show(screenWidth) | ||||
|             } else { | ||||
|                 loadingDialog.dismiss() | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         viewModel.visiblePasswordLiveData.observe(this) { | ||||
|             binding.tvVisiblePassword.isSelected = it | ||||
|             if (it) { | ||||
|                 binding.etPassword.inputType = | ||||
|                     InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD | ||||
|             } else { | ||||
|                 binding.etPassword.inputType = | ||||
|                     InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,8 @@ | ||||
| package kr.co.vividnext.sodalive.user.login | ||||
|  | ||||
| import com.google.gson.annotations.SerializedName | ||||
|  | ||||
| data class LoginRequest( | ||||
|     @SerializedName("email") val email: String, | ||||
|     @SerializedName("password") val password: String | ||||
| ) | ||||
| @@ -0,0 +1,11 @@ | ||||
| package kr.co.vividnext.sodalive.user.login | ||||
|  | ||||
| import com.google.gson.annotations.SerializedName | ||||
|  | ||||
| data class LoginResponse( | ||||
|     @SerializedName("userId") val userId: Long, | ||||
|     @SerializedName("token") val token: String, | ||||
|     @SerializedName("nickname") val nickname: String, | ||||
|     @SerializedName("email") val email: String, | ||||
|     @SerializedName("profileImage") val profileImage: String | ||||
| ) | ||||
| @@ -0,0 +1,78 @@ | ||||
| package kr.co.vividnext.sodalive.user.login | ||||
|  | ||||
| import androidx.lifecycle.LiveData | ||||
| import androidx.lifecycle.MutableLiveData | ||||
| import com.orhanobut.logger.Logger | ||||
| import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers | ||||
| import io.reactivex.rxjava3.schedulers.Schedulers | ||||
| import kr.co.vividnext.sodalive.base.BaseViewModel | ||||
| import kr.co.vividnext.sodalive.common.SharedPreferenceManager | ||||
| import kr.co.vividnext.sodalive.user.UserRepository | ||||
|  | ||||
| class LoginViewModel(private val repository: UserRepository) : BaseViewModel() { | ||||
|  | ||||
|     var email = "" | ||||
|     var password = "" | ||||
|  | ||||
|     private val _visiblePasswordLiveData = MutableLiveData(false) | ||||
|     val visiblePasswordLiveData: LiveData<Boolean> | ||||
|         get() = _visiblePasswordLiveData | ||||
|  | ||||
|     private val _toastLiveData = MutableLiveData<String?>() | ||||
|     val toastLiveData: LiveData<String?> | ||||
|         get() = _toastLiveData | ||||
|  | ||||
|     private var _isLoading = MutableLiveData(false) | ||||
|     val isLoading: LiveData<Boolean> | ||||
|         get() = _isLoading | ||||
|  | ||||
|     fun login(onSuccess: (String?) -> Unit) { | ||||
|         if (email.isBlank()) { | ||||
|             _toastLiveData.postValue("이메일을 입력하세요.") | ||||
|             return | ||||
|         } | ||||
|  | ||||
|         if (password.isBlank()) { | ||||
|             _toastLiveData.postValue("비밃번호를 입력하세요.") | ||||
|             return | ||||
|         } | ||||
|  | ||||
|         _isLoading.value = true | ||||
|         val request = LoginRequest(email, password) | ||||
|         compositeDisposable.add( | ||||
|             repository.login(request) | ||||
|                 .subscribeOn(Schedulers.io()) | ||||
|                 .observeOn(AndroidSchedulers.mainThread()) | ||||
|                 .subscribe( | ||||
|                     { | ||||
|                         _isLoading.value = false | ||||
|                         if (it.success && it.data != null) { | ||||
|                             SharedPreferenceManager.token = it.data.token | ||||
|                             SharedPreferenceManager.email = it.data.email | ||||
|                             SharedPreferenceManager.userId = it.data.userId | ||||
|                             SharedPreferenceManager.nickname = it.data.nickname | ||||
|                             SharedPreferenceManager.profileImage = it.data.profileImage | ||||
|                             onSuccess(it.message) | ||||
|                         } else { | ||||
|                             if (it.message != null) { | ||||
|                                 _toastLiveData.postValue(it.message) | ||||
|                             } else { | ||||
|                                 _toastLiveData.postValue( | ||||
|                                     "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요." | ||||
|                                 ) | ||||
|                             } | ||||
|                         } | ||||
|                     }, | ||||
|                     { | ||||
|                         _isLoading.value = false | ||||
|                         it.message?.let { message -> Logger.e(message) } | ||||
|                         _toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.") | ||||
|                     } | ||||
|                 ) | ||||
|         ) | ||||
|     } | ||||
|  | ||||
|     fun onClickVisiblePassword() { | ||||
|         _visiblePasswordLiveData.postValue(!_visiblePasswordLiveData.value!!) | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,254 @@ | ||||
| package kr.co.vividnext.sodalive.user.signup | ||||
|  | ||||
| import android.content.Intent | ||||
| import android.os.Bundle | ||||
| import android.view.View | ||||
| import android.widget.Toast | ||||
| import androidx.activity.OnBackPressedCallback | ||||
| import androidx.activity.result.contract.ActivityResultContracts | ||||
| import coil.load | ||||
| import coil.transform.RoundedCornersTransformation | ||||
| import com.github.dhaval2404.imagepicker.ImagePicker | ||||
| import com.jakewharton.rxbinding4.widget.textChanges | ||||
| import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers | ||||
| import io.reactivex.rxjava3.schedulers.Schedulers | ||||
| import kr.co.vividnext.sodalive.base.BaseActivity | ||||
| import kr.co.vividnext.sodalive.common.Constants | ||||
| import kr.co.vividnext.sodalive.common.LoadingDialog | ||||
| import kr.co.vividnext.sodalive.common.RealPathUtil | ||||
| import kr.co.vividnext.sodalive.databinding.ActivitySignupBinding | ||||
| import kr.co.vividnext.sodalive.extensions.dpToPx | ||||
| import kr.co.vividnext.sodalive.main.MainActivity | ||||
| import kr.co.vividnext.sodalive.settings.TermsActivity | ||||
| import kr.co.vividnext.sodalive.user.Gender | ||||
| import org.koin.android.ext.android.inject | ||||
|  | ||||
| class SignUpActivity : BaseActivity<ActivitySignupBinding>(ActivitySignupBinding::inflate) { | ||||
|  | ||||
|     private val viewModel: SignUpViewModel by inject() | ||||
|     private val onBackPressedCallback = object : OnBackPressedCallback(true) { | ||||
|         override fun handleOnBackPressed() { | ||||
|             onClickBackButton() | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private lateinit var loadingDialog: LoadingDialog | ||||
|  | ||||
|     private val imageResult = | ||||
|         registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> | ||||
|             val resultCode = result.resultCode | ||||
|             val data = result.data | ||||
|  | ||||
|             if (resultCode == RESULT_OK) { | ||||
|                 // Image Uri will not be null for RESULT_OK | ||||
|                 val fileUri = data?.data!! | ||||
|                 binding.ivProfile.background = null | ||||
|                 binding.ivProfile.load(fileUri) { | ||||
|                     crossfade(true) | ||||
|                     transformations(RoundedCornersTransformation(13.3f.dpToPx())) | ||||
|                 } | ||||
|                 viewModel.profileImageUri = fileUri | ||||
|             } else if (resultCode == ImagePicker.RESULT_ERROR) { | ||||
|                 Toast.makeText(this, ImagePicker.getError(data), Toast.LENGTH_SHORT).show() | ||||
|             } | ||||
|         } | ||||
|  | ||||
|     override fun onCreate(savedInstanceState: Bundle?) { | ||||
|         super.onCreate(savedInstanceState) | ||||
|         onBackPressedDispatcher.addCallback(this, onBackPressedCallback) | ||||
|  | ||||
|         viewModel.getRealPathFromURI = { | ||||
|             RealPathUtil.getRealPath(applicationContext, it) | ||||
|         } | ||||
|  | ||||
|         bindData() | ||||
|     } | ||||
|  | ||||
|     override fun setupView() { | ||||
|         binding.toolbar.tvBack.text = "회원가입" | ||||
|         binding.toolbar.tvBack.setOnClickListener { onClickBackButton() } | ||||
|  | ||||
|         loadingDialog = LoadingDialog(this, layoutInflater) | ||||
|  | ||||
|         binding.ivPhotoPicker.setOnClickListener { | ||||
|             ImagePicker.with(this) | ||||
|                 .crop() | ||||
|                 .galleryOnly() | ||||
|                 .galleryMimeTypes( // Exclude gif images | ||||
|                     mimeTypes = arrayOf( | ||||
|                         "image/png", | ||||
|                         "image/jpg", | ||||
|                         "image/jpeg" | ||||
|                     ) | ||||
|                 ) | ||||
|                 .createIntent { imageResult.launch(it) } | ||||
|         } | ||||
|  | ||||
|         binding.tvMale.setOnClickListener { | ||||
|             viewModel.changeGender(Gender.MALE) | ||||
|         } | ||||
|  | ||||
|         binding.tvFemale.setOnClickListener { | ||||
|             viewModel.changeGender(Gender.FEMALE) | ||||
|         } | ||||
|  | ||||
|         binding.tvNone.setOnClickListener { | ||||
|             viewModel.changeGender(Gender.NONE) | ||||
|         } | ||||
|  | ||||
|         binding.tvTermsOfService.setOnClickListener { | ||||
|             val intent = Intent(applicationContext, TermsActivity::class.java) | ||||
|             intent.putExtra("terms", "terms") | ||||
|             startActivity(intent) | ||||
|         } | ||||
|  | ||||
|         binding.tvPrivacyPolicy.setOnClickListener { | ||||
|             val intent = Intent(applicationContext, TermsActivity::class.java) | ||||
|             intent.putExtra("terms", "privacy") | ||||
|             startActivity(intent) | ||||
|         } | ||||
|  | ||||
|         binding.ivTermsOfService.setOnClickListener { | ||||
|             viewModel.onClickCheckboxTermsOfService() | ||||
|         } | ||||
|  | ||||
|         binding.ivPrivacyPolicy.setOnClickListener { | ||||
|             viewModel.onClickCheckboxPrivacyPolicy() | ||||
|         } | ||||
|  | ||||
|         binding.tvSignUp.setOnClickListener { | ||||
|             viewModel.signUp { | ||||
|                 it?.let { Toast.makeText(applicationContext, it, Toast.LENGTH_LONG).show() } | ||||
|                 finishAffinity() | ||||
|                 val nextIntent = Intent(applicationContext, MainActivity::class.java) | ||||
|                 val extras = intent.getBundleExtra(Constants.EXTRA_DATA) | ||||
|                     ?: if (intent.extras != null) { | ||||
|                         intent.extras | ||||
|                     } else { | ||||
|                         null | ||||
|                     } | ||||
|                 if (extras != null) { | ||||
|                     nextIntent.putExtra(Constants.EXTRA_DATA, extras) | ||||
|                 } | ||||
|                 startActivity(nextIntent) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private fun bindData() { | ||||
|         compositeDisposable.add( | ||||
|             binding.etEmail.textChanges().skip(1) | ||||
|                 .subscribeOn(Schedulers.io()) | ||||
|                 .observeOn(AndroidSchedulers.mainThread()) | ||||
|                 .subscribe { | ||||
|                     viewModel.email = it.toString() | ||||
|                 } | ||||
|         ) | ||||
|  | ||||
|         compositeDisposable.add( | ||||
|             binding.etPassword.textChanges().skip(1) | ||||
|                 .subscribeOn(Schedulers.io()) | ||||
|                 .observeOn(AndroidSchedulers.mainThread()) | ||||
|                 .subscribe { | ||||
|                     viewModel.password = it.toString() | ||||
|                 } | ||||
|         ) | ||||
|  | ||||
|         compositeDisposable.add( | ||||
|             binding.etPasswordRe.textChanges().skip(1) | ||||
|                 .subscribeOn(Schedulers.io()) | ||||
|                 .observeOn(AndroidSchedulers.mainThread()) | ||||
|                 .subscribe { | ||||
|                     viewModel.passwordRe = it.toString() | ||||
|                 } | ||||
|         ) | ||||
|  | ||||
|         compositeDisposable.add( | ||||
|             binding.etNickname.textChanges().skip(1) | ||||
|                 .subscribeOn(Schedulers.io()) | ||||
|                 .observeOn(AndroidSchedulers.mainThread()) | ||||
|                 .subscribe { | ||||
|                     viewModel.nickname = it.toString() | ||||
|                 } | ||||
|         ) | ||||
|  | ||||
|         viewModel.isAgreeTermsOfServiceLiveData.observe(this) { | ||||
|             binding.ivTermsOfService.isSelected = it | ||||
|         } | ||||
|  | ||||
|         viewModel.isAgreePrivacyPolicyLiveData.observe(this) { | ||||
|             binding.ivPrivacyPolicy.isSelected = it | ||||
|         } | ||||
|  | ||||
|         viewModel.genderLiveData.observe(this) { | ||||
|             binding.tvMale.isSelected = false | ||||
|             binding.tvFemale.isSelected = false | ||||
|             binding.tvNone.isSelected = false | ||||
|  | ||||
|             when (it) { | ||||
|                 Gender.MALE -> binding.tvMale.isSelected = true | ||||
|                 Gender.FEMALE -> binding.tvFemale.isSelected = true | ||||
|                 Gender.NONE -> binding.tvNone.isSelected = true | ||||
|                 else -> { | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         viewModel.signUpErrorLiveData.observe(this) { | ||||
|             Toast.makeText(applicationContext, it.message, Toast.LENGTH_LONG).show() | ||||
|  | ||||
|             when (it.errorProperty) { | ||||
|                 "email" -> { | ||||
|                     viewModel.setStep(step = SignUpViewModel.EmailSignUpStep.STEP_1) | ||||
|                     binding.etEmail.error = it.message | ||||
|                     binding.etEmail.requestFocus() | ||||
|                 } | ||||
|  | ||||
|                 "password" -> { | ||||
|                     viewModel.setStep(step = SignUpViewModel.EmailSignUpStep.STEP_1) | ||||
|                     binding.etPassword.error = it.message | ||||
|                     binding.etPassword.requestFocus() | ||||
|                 } | ||||
|  | ||||
|                 "nickname" -> { | ||||
|                     binding.etNickname.error = it.message | ||||
|                     binding.etNickname.requestFocus() | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         viewModel.isLoading.observe(this) { | ||||
|             if (it) { | ||||
|                 loadingDialog.show(screenWidth) | ||||
|             } else { | ||||
|                 loadingDialog.dismiss() | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         viewModel.toastLiveData.observe(this) { | ||||
|             it?.let { Toast.makeText(applicationContext, it, Toast.LENGTH_LONG).show() } | ||||
|         } | ||||
|  | ||||
|         viewModel.stepLiveData.observe(this) { | ||||
|             if (it == SignUpViewModel.EmailSignUpStep.STEP_2) { | ||||
|                 binding.toolbar.tvBack.text = "프로필 설정" | ||||
|                 binding.tvSignUp.text = "회원가입" | ||||
|                 binding.llStep1.visibility = View.GONE | ||||
|                 binding.llStep2.visibility = View.VISIBLE | ||||
|             } else { | ||||
|                 binding.toolbar.tvBack.text = "회원가입" | ||||
|                 binding.tvSignUp.text = "다음" | ||||
|                 binding.llStep1.visibility = View.VISIBLE | ||||
|                 binding.llStep2.visibility = View.GONE | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private fun onClickBackButton() { | ||||
|         if (viewModel.stepLiveData.value!! == SignUpViewModel.EmailSignUpStep.STEP_2) { | ||||
|             viewModel.setStep(SignUpViewModel.EmailSignUpStep.STEP_1) | ||||
|         } else { | ||||
|             finish() | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,8 @@ | ||||
| package kr.co.vividnext.sodalive.user.signup | ||||
|  | ||||
| import com.google.gson.annotations.SerializedName | ||||
|  | ||||
| data class SignUpError( | ||||
|     @SerializedName("errorProperty") val errorProperty: String, | ||||
|     @SerializedName("message") val message: String | ||||
| ) | ||||
| @@ -0,0 +1,14 @@ | ||||
| package kr.co.vividnext.sodalive.user.signup | ||||
|  | ||||
| import com.google.gson.annotations.SerializedName | ||||
| import kr.co.vividnext.sodalive.user.Gender | ||||
|  | ||||
| data class SignUpRequest( | ||||
|     @SerializedName("email") val email: String, | ||||
|     @SerializedName("password") val password: String, | ||||
|     @SerializedName("nickname") val nickname: String, | ||||
|     @SerializedName("gender") val gender: Gender, | ||||
|     @SerializedName("isAgreeTermsOfService") val isAgreeTermsOfService: Boolean, | ||||
|     @SerializedName("isAgreePrivacyPolicy") val isAgreePrivacyPolicy: Boolean, | ||||
|     @SerializedName("container") val container: String = "aos" | ||||
| ) | ||||
| @@ -0,0 +1,241 @@ | ||||
| package kr.co.vividnext.sodalive.user.signup | ||||
|  | ||||
| import android.net.Uri | ||||
| import androidx.core.util.PatternsCompat | ||||
| import androidx.lifecycle.LiveData | ||||
| import androidx.lifecycle.MutableLiveData | ||||
| import com.google.gson.Gson | ||||
| import com.google.gson.annotations.SerializedName | ||||
| import com.orhanobut.logger.Logger | ||||
| import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers | ||||
| import io.reactivex.rxjava3.schedulers.Schedulers | ||||
| import kr.co.vividnext.sodalive.base.BaseViewModel | ||||
| import kr.co.vividnext.sodalive.common.SharedPreferenceManager | ||||
| import kr.co.vividnext.sodalive.user.Gender | ||||
| import kr.co.vividnext.sodalive.user.UserRepository | ||||
| import okhttp3.MediaType.Companion.toMediaType | ||||
| import okhttp3.MultipartBody | ||||
| import okhttp3.RequestBody.Companion.asRequestBody | ||||
| import okhttp3.RequestBody.Companion.toRequestBody | ||||
| import java.io.File | ||||
|  | ||||
| class SignUpViewModel(private val repository: UserRepository) : BaseViewModel() { | ||||
|     enum class EmailSignUpStep { | ||||
|         @SerializedName("STEP_1") STEP_1, | ||||
|         @SerializedName("STEP_2") STEP_2 | ||||
|     } | ||||
|  | ||||
|     var email = "" | ||||
|     var password = "" | ||||
|     var passwordRe = "" | ||||
|     var nickname = "" | ||||
|     var profileImageUri: Uri? = null | ||||
|  | ||||
|     private val _genderLiveData = MutableLiveData(Gender.NONE) | ||||
|     val genderLiveData: LiveData<Gender> | ||||
|         get() = _genderLiveData | ||||
|  | ||||
|     private val _isAgreeTermsOfServiceLiveData = MutableLiveData(false) | ||||
|     val isAgreeTermsOfServiceLiveData: LiveData<Boolean> | ||||
|         get() = _isAgreeTermsOfServiceLiveData | ||||
|  | ||||
|     private val _isAgreePrivacyPolicyLiveData = MutableLiveData(false) | ||||
|     val isAgreePrivacyPolicyLiveData: LiveData<Boolean> | ||||
|         get() = _isAgreePrivacyPolicyLiveData | ||||
|  | ||||
|     private val _signUpErrorLiveData = MutableLiveData<SignUpError>() | ||||
|     val signUpErrorLiveData: LiveData<SignUpError> | ||||
|         get() = _signUpErrorLiveData | ||||
|  | ||||
|     private val _toastLiveData = MutableLiveData<String?>() | ||||
|     val toastLiveData: LiveData<String?> | ||||
|         get() = _toastLiveData | ||||
|  | ||||
|     private var _isLoading = MutableLiveData(false) | ||||
|     val isLoading: LiveData<Boolean> | ||||
|         get() = _isLoading | ||||
|  | ||||
|     private val _stepLiveData = MutableLiveData(EmailSignUpStep.STEP_1) | ||||
|     val stepLiveData: LiveData<EmailSignUpStep> | ||||
|         get() = _stepLiveData | ||||
|  | ||||
|     lateinit var getRealPathFromURI: (Uri) -> String? | ||||
|  | ||||
|     fun setStep(step: EmailSignUpStep = EmailSignUpStep.STEP_2) { | ||||
|         _stepLiveData.postValue(step) | ||||
|     } | ||||
|  | ||||
|     fun signUp(onSuccess: (String?) -> Unit) { | ||||
|         if (stepLiveData.value!! == EmailSignUpStep.STEP_1) { | ||||
|             if (validationStep1()) return | ||||
|  | ||||
|             setStep() | ||||
|             return | ||||
|         } | ||||
|  | ||||
|         if (validationStep2()) return | ||||
|  | ||||
|         val request = SignUpRequest( | ||||
|             email = email, | ||||
|             password = password, | ||||
|             nickname = nickname, | ||||
|             gender = _genderLiveData.value!!, | ||||
|             isAgreeTermsOfService = _isAgreeTermsOfServiceLiveData.value!!, | ||||
|             isAgreePrivacyPolicy = _isAgreePrivacyPolicyLiveData.value!! | ||||
|         ) | ||||
|  | ||||
|         val requestJson = Gson().toJson(request) | ||||
|  | ||||
|         val profileImage = if (profileImageUri != null) { | ||||
|             val file = File(getRealPathFromURI(profileImageUri!!)) | ||||
|             MultipartBody.Part.createFormData( | ||||
|                 "profileImage", | ||||
|                 file.name, | ||||
|                 file.asRequestBody("image/*".toMediaType()) | ||||
|             ) | ||||
|         } else { | ||||
|             null | ||||
|         } | ||||
|  | ||||
|         _isLoading.value = true | ||||
|         compositeDisposable.add( | ||||
|             repository.signUp( | ||||
|                 profileImage, | ||||
|                 requestJson.toRequestBody("text/plain".toMediaType()) | ||||
|             ) | ||||
|                 .subscribeOn(Schedulers.io()) | ||||
|                 .observeOn(AndroidSchedulers.mainThread()) | ||||
|                 .subscribe( | ||||
|                     { | ||||
|                         if (it.success && it.data != null) { | ||||
|                             SharedPreferenceManager.token = it.data.token | ||||
|                             SharedPreferenceManager.email = it.data.email | ||||
|                             SharedPreferenceManager.userId = it.data.userId | ||||
|                             SharedPreferenceManager.nickname = it.data.nickname | ||||
|                             SharedPreferenceManager.profileImage = it.data.profileImage | ||||
|                             _isLoading.value = false | ||||
|                             onSuccess(it.message) | ||||
|                         } else { | ||||
|                             _isLoading.value = false | ||||
|                             if (it.errorProperty != null && it.message != null) { | ||||
|                                 _signUpErrorLiveData.postValue( | ||||
|                                     SignUpError(it.errorProperty, it.message) | ||||
|                                 ) | ||||
|                             } else if (it.message != null) { | ||||
|                                 _toastLiveData.postValue(it.message) | ||||
|                             } else { | ||||
|                                 _toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.") | ||||
|                             } | ||||
|                         } | ||||
|                     }, | ||||
|                     { | ||||
|                         it.message?.let { message -> Logger.e(message) } | ||||
|                         _toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.") | ||||
|                     } | ||||
|                 ) | ||||
|         ) | ||||
|     } | ||||
|  | ||||
|     private fun validationStep1(): Boolean { | ||||
|         if (email.isBlank()) { | ||||
|             _signUpErrorLiveData.postValue( | ||||
|                 SignUpError( | ||||
|                     "email", | ||||
|                     "이메일을 입력하세요." | ||||
|                 ) | ||||
|             ) | ||||
|  | ||||
|             return true | ||||
|         } | ||||
|  | ||||
|         if (password.isBlank()) { | ||||
|             _signUpErrorLiveData.postValue( | ||||
|                 SignUpError( | ||||
|                     "password", | ||||
|                     "비밀번호를 입력하세요." | ||||
|                 ) | ||||
|             ) | ||||
|  | ||||
|             return true | ||||
|         } | ||||
|  | ||||
|         if (password != passwordRe) { | ||||
|             _signUpErrorLiveData.postValue( | ||||
|                 SignUpError( | ||||
|                     "password", | ||||
|                     "비밀번호가 일치하지 않습니다." | ||||
|                 ) | ||||
|             ) | ||||
|  | ||||
|             return true | ||||
|         } | ||||
|  | ||||
|         if ( | ||||
|             !_isAgreePrivacyPolicyLiveData.value!! || | ||||
|             !_isAgreeTermsOfServiceLiveData.value!! | ||||
|         ) { | ||||
|             _signUpErrorLiveData.postValue( | ||||
|                 SignUpError( | ||||
|                     "", | ||||
|                     "약관에 동의하셔야 회원가입이 가능합니다." | ||||
|                 ) | ||||
|             ) | ||||
|  | ||||
|             return true | ||||
|         } | ||||
|  | ||||
|         if (!PatternsCompat.EMAIL_ADDRESS.matcher(email).matches()) { | ||||
|             _signUpErrorLiveData.postValue( | ||||
|                 SignUpError( | ||||
|                     "email", | ||||
|                     "올바른 이메일을 입력해 주세요" | ||||
|                 ) | ||||
|             ) | ||||
|  | ||||
|             return true | ||||
|         } | ||||
|  | ||||
|         if ( | ||||
|             "^(?=.*[A-Za-z])(?=.*\\d)[A-Za-z\\d$@!%*#?&]{8,}$" | ||||
|                 .toRegex() | ||||
|                 .matches(password) | ||||
|                 .not() | ||||
|         ) { | ||||
|             _signUpErrorLiveData.postValue( | ||||
|                 SignUpError( | ||||
|                     "password", | ||||
|                     "영문, 숫자 포함 8자 이상의 비밀번호를 입력해 주세요." | ||||
|                 ) | ||||
|             ) | ||||
|         } | ||||
|  | ||||
|         return false | ||||
|     } | ||||
|  | ||||
|     private fun validationStep2(): Boolean { | ||||
|         if (nickname.isBlank() || nickname.length < 2) { | ||||
|             _signUpErrorLiveData.postValue( | ||||
|                 SignUpError( | ||||
|                     "nickname", | ||||
|                     "닉네임은 2자 이상 입력해 주세요." | ||||
|                 ) | ||||
|             ) | ||||
|  | ||||
|             return true | ||||
|         } | ||||
|  | ||||
|         return false | ||||
|     } | ||||
|  | ||||
|     fun onClickCheckboxTermsOfService() { | ||||
|         _isAgreeTermsOfServiceLiveData.postValue(!_isAgreeTermsOfServiceLiveData.value!!) | ||||
|     } | ||||
|  | ||||
|     fun onClickCheckboxPrivacyPolicy() { | ||||
|         _isAgreePrivacyPolicyLiveData.postValue(!_isAgreePrivacyPolicyLiveData.value!!) | ||||
|     } | ||||
|  | ||||
|     fun changeGender(gender: Gender) { | ||||
|         _genderLiveData.postValue(gender) | ||||
|     } | ||||
| } | ||||
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/drawable-hdpi/loading_1.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 6.3 KiB | 
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/drawable-hdpi/loading_2.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 3.7 KiB | 
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/drawable-hdpi/loading_3.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 3.2 KiB | 
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/drawable-hdpi/loading_4.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 5.1 KiB | 
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/drawable-hdpi/loading_5.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 4.6 KiB | 
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/drawable-xxhdpi/btn_radio_select_normal.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 509 B | 
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/drawable-xxhdpi/btn_radio_select_selected.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 668 B | 
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/drawable-xxhdpi/btn_select_checked.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 544 B | 
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/drawable-xxhdpi/btn_select_normal.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 680 B | 
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/drawable-xxhdpi/ic_back.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 274 B | 
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/drawable-xxhdpi/ic_camera.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 479 B | 
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/drawable-xxhdpi/ic_headphones_purple.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.3 KiB | 
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/drawable-xxxhdpi/ic_logo.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 55 KiB | 
							
								
								
									
										8
									
								
								app/src/main/res/drawable/bg_round_corner_10_9970ff.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,8 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <shape xmlns:android="http://schemas.android.com/apk/res/android"> | ||||
|     <solid android:color="@color/color_9970ff" /> | ||||
|     <corners android:radius="10dp" /> | ||||
|     <stroke | ||||
|         android:width="1dp" | ||||
|         android:color="@color/color_9970ff" /> | ||||
| </shape> | ||||
| @@ -0,0 +1,8 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <shape xmlns:android="http://schemas.android.com/apk/res/android"> | ||||
|     <solid android:color="@color/color_222222" /> | ||||
|     <corners android:radius="16.7dp" /> | ||||
|     <stroke | ||||
|         android:width="1dp" | ||||
|         android:color="@color/color_222222" /> | ||||
| </shape> | ||||
| @@ -0,0 +1,8 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <shape xmlns:android="http://schemas.android.com/apk/res/android"> | ||||
|     <solid android:color="@color/color_9970ff" /> | ||||
|     <corners android:radius="33.3dp" /> | ||||
|     <stroke | ||||
|         android:width="1dp" | ||||
|         android:color="@color/color_9970ff" /> | ||||
| </shape> | ||||
							
								
								
									
										8
									
								
								app/src/main/res/drawable/bg_round_corner_6_7_222222.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,8 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <shape xmlns:android="http://schemas.android.com/apk/res/android"> | ||||
|     <solid android:color="@color/color_222222" /> | ||||
|     <corners android:radius="6.7dp" /> | ||||
|     <stroke | ||||
|         android:width="1dp" | ||||
|         android:color="@color/color_222222" /> | ||||
| </shape> | ||||
							
								
								
									
										8
									
								
								app/src/main/res/drawable/bg_round_corner_6_7_9970ff.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,8 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <shape xmlns:android="http://schemas.android.com/apk/res/android"> | ||||
|     <solid android:color="@color/color_9970ff" /> | ||||
|     <corners android:radius="6.7dp" /> | ||||
|     <stroke | ||||
|         android:width="1dp" | ||||
|         android:color="@color/color_9970ff" /> | ||||
| </shape> | ||||
| @@ -0,0 +1,8 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <shape xmlns:android="http://schemas.android.com/apk/res/android"> | ||||
|     <solid android:color="@android:color/transparent" /> | ||||
|     <corners android:radius="8dp" /> | ||||
|     <stroke | ||||
|         android:width="1dp" | ||||
|         android:color="@color/color_9970ff" /> | ||||
| </shape> | ||||
							
								
								
									
										5
									
								
								app/src/main/res/drawable/edit_text_cursor.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,5 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <shape xmlns:android="http://schemas.android.com/apk/res/android"> | ||||
|     <solid android:color="@color/color_9970ff" /> | ||||
|     <size android:width="1.5dp" /> | ||||
| </shape> | ||||
							
								
								
									
										5
									
								
								app/src/main/res/drawable/edittext_underline.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,5 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <selector xmlns:android="http://schemas.android.com/apk/res/android"> | ||||
|     <item android:drawable="@drawable/edittext_underline_normal" android:state_focused="false" /> | ||||
|     <item android:drawable="@drawable/edittext_underline_focused" android:state_focused="true" /> | ||||
| </selector> | ||||
							
								
								
									
										22
									
								
								app/src/main/res/drawable/edittext_underline_focused.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,22 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <layer-list xmlns:android="http://schemas.android.com/apk/res/android" > | ||||
|     <item | ||||
|         android:bottom="1dp" | ||||
|         android:left="-2dp" | ||||
|         android:right="-2dp" | ||||
|         android:top="-2dp"> | ||||
|         <shape android:shape="rectangle" > | ||||
|             <stroke | ||||
|                 android:width="1px" | ||||
|                 android:color="@color/color_9970ff" /> | ||||
|  | ||||
|             <solid android:color="#00FFFFFF" /> | ||||
|  | ||||
|             <padding | ||||
|                 android:bottom="5dp" | ||||
|                 android:left="5dp" | ||||
|                 android:right="5dp" | ||||
|                 android:top="5dp" /> | ||||
|         </shape> | ||||
|     </item> | ||||
| </layer-list> | ||||
							
								
								
									
										22
									
								
								app/src/main/res/drawable/edittext_underline_normal.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,22 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <layer-list xmlns:android="http://schemas.android.com/apk/res/android" > | ||||
|     <item | ||||
|         android:bottom="1dp" | ||||
|         android:left="-2dp" | ||||
|         android:right="-2dp" | ||||
|         android:top="-2dp"> | ||||
|         <shape android:shape="rectangle" > | ||||
|             <stroke | ||||
|                 android:width="1dp" | ||||
|                 android:color="@color/color_b3909090" /> | ||||
|  | ||||
|             <solid android:color="#00FFFFFF" /> | ||||
|  | ||||
|             <padding | ||||
|                 android:bottom="5dp" | ||||
|                 android:left="5dp" | ||||
|                 android:right="5dp" | ||||
|                 android:top="5dp" /> | ||||
|         </shape> | ||||
|     </item> | ||||
| </layer-list> | ||||
							
								
								
									
										5
									
								
								app/src/main/res/drawable/ic_radio_button_select.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,5 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <selector xmlns:android="http://schemas.android.com/apk/res/android"> | ||||
|     <item android:drawable="@drawable/btn_radio_select_normal" android:state_selected="false" /> | ||||
|     <item android:drawable="@drawable/btn_radio_select_selected" android:state_selected="true" /> | ||||
| </selector> | ||||
							
								
								
									
										5
									
								
								app/src/main/res/drawable/ic_select.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,5 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <selector xmlns:android="http://schemas.android.com/apk/res/android"> | ||||
|     <item android:drawable="@drawable/btn_select_normal" android:state_selected="false" /> | ||||
|     <item android:drawable="@drawable/btn_select_checked" android:state_selected="true" /> | ||||
| </selector> | ||||
							
								
								
									
										8
									
								
								app/src/main/res/drawable/loading.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,8 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <animation-list xmlns:android="http://schemas.android.com/apk/res/android"> | ||||
|     <item android:drawable="@drawable/loading_1" android:duration="500" /> | ||||
|     <item android:drawable="@drawable/loading_2" android:duration="500" /> | ||||
|     <item android:drawable="@drawable/loading_3" android:duration="500" /> | ||||
|     <item android:drawable="@drawable/loading_4" android:duration="500" /> | ||||
|     <item android:drawable="@drawable/loading_5" android:duration="500" /> | ||||
| </animation-list> | ||||
							
								
								
									
										114
									
								
								app/src/main/res/layout/activity_find_password.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,114 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     xmlns:app="http://schemas.android.com/apk/res-auto" | ||||
|     xmlns:tools="http://schemas.android.com/tools" | ||||
|     android:layout_width="match_parent" | ||||
|     android:layout_height="match_parent"> | ||||
|  | ||||
|     <include | ||||
|         android:id="@+id/toolbar" | ||||
|         layout="@layout/detail_toolbar" /> | ||||
|  | ||||
|     <ScrollView | ||||
|         android:layout_width="0dp" | ||||
|         android:layout_height="0dp" | ||||
|         app:layout_constraintBottom_toBottomOf="parent" | ||||
|         app:layout_constraintEnd_toEndOf="parent" | ||||
|         app:layout_constraintStart_toStartOf="parent" | ||||
|         app:layout_constraintTop_toBottomOf="@+id/toolbar"> | ||||
|  | ||||
|         <LinearLayout | ||||
|             android:layout_width="match_parent" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:orientation="vertical"> | ||||
|  | ||||
|             <TextView | ||||
|                 android:layout_width="match_parent" | ||||
|                 android:layout_height="wrap_content" | ||||
|                 android:layout_marginHorizontal="26.7dp" | ||||
|                 android:layout_marginTop="40dp" | ||||
|                 android:fontFamily="@font/gmarket_sans_bold" | ||||
|                 android:gravity="center" | ||||
|                 android:lineSpacingExtra="6dp" | ||||
|                 android:text="회원가입한 이메일 주소로\n임시 비밀번호를 보내드립니다." | ||||
|                 android:textColor="@color/color_eeeeee" | ||||
|                 android:textSize="16sp" /> | ||||
|  | ||||
|             <TextView | ||||
|                 android:layout_width="match_parent" | ||||
|                 android:layout_height="wrap_content" | ||||
|                 android:layout_marginHorizontal="26.7dp" | ||||
|                 android:layout_marginTop="40dp" | ||||
|                 android:fontFamily="@font/gmarket_sans_medium" | ||||
|                 android:gravity="center" | ||||
|                 android:lineSpacingExtra="6dp" | ||||
|                 android:text="임시 비밀번호로 로그인 후, 마이페이지 > 프로필 설정에서\n비밀번호를 변경하고 이용하세요." | ||||
|                 android:textColor="@color/color_909090" | ||||
|                 android:textSize="12sp" /> | ||||
|  | ||||
|             <LinearLayout | ||||
|                 android:layout_width="match_parent" | ||||
|                 android:layout_height="wrap_content" | ||||
|                 android:layout_marginHorizontal="26.7dp" | ||||
|                 android:layout_marginTop="40dp" | ||||
|                 android:orientation="vertical"> | ||||
|  | ||||
|                 <TextView | ||||
|                     android:layout_width="wrap_content" | ||||
|                     android:layout_height="wrap_content" | ||||
|                     android:layout_marginHorizontal="6.7dp" | ||||
|                     android:fontFamily="@font/gmarket_sans_medium" | ||||
|                     android:text="이메일" | ||||
|                     android:textColor="@color/color_eeeeee" | ||||
|                     android:textSize="12sp" /> | ||||
|  | ||||
|                 <EditText | ||||
|                     android:id="@+id/et_email" | ||||
|                     android:layout_width="match_parent" | ||||
|                     android:layout_height="wrap_content" | ||||
|                     android:background="@drawable/edittext_underline" | ||||
|                     android:fontFamily="@font/gmarket_sans_medium" | ||||
|                     android:hint="이메일 주소를 입력하세요" | ||||
|                     android:importantForAutofill="no" | ||||
|                     android:inputType="textWebEmailAddress" | ||||
|                     android:paddingHorizontal="6.7dp" | ||||
|                     android:paddingTop="12dp" | ||||
|                     android:paddingBottom="8dp" | ||||
|                     android:textColor="@color/color_eeeeee" | ||||
|                     android:textColorHint="@color/color_777777" | ||||
|                     android:textCursorDrawable="@drawable/edit_text_cursor" | ||||
|                     android:textSize="13.3sp" | ||||
|                     android:theme="@style/EditTextStyle" | ||||
|                     tools:ignore="LabelFor" /> | ||||
|             </LinearLayout> | ||||
|  | ||||
|             <TextView | ||||
|                 android:id="@+id/tv_find_password" | ||||
|                 android:layout_width="match_parent" | ||||
|                 android:layout_height="wrap_content" | ||||
|                 android:layout_marginHorizontal="13.3dp" | ||||
|                 android:layout_marginTop="60dp" | ||||
|                 android:background="@drawable/bg_round_corner_6_7_9970ff" | ||||
|                 android:fontFamily="@font/gmarket_sans_bold" | ||||
|                 android:gravity="center" | ||||
|                 android:paddingVertical="16dp" | ||||
|                 android:text="임시 비밀번호 받기" | ||||
|                 android:textColor="@color/white" | ||||
|                 android:textSize="15sp" /> | ||||
|  | ||||
|             <TextView | ||||
|                 android:id="@+id/tv_service_center" | ||||
|                 android:layout_width="wrap_content" | ||||
|                 android:layout_height="wrap_content" | ||||
|                 android:layout_gravity="center" | ||||
|                 android:layout_marginTop="93dp" | ||||
|                 android:background="@drawable/bg_round_corner_8_transparent_9970ff" | ||||
|                 android:drawablePadding="13.3dp" | ||||
|                 android:paddingHorizontal="18.7dp" | ||||
|                 android:paddingVertical="10.7dp" | ||||
|                 android:text="고객센터로 문의하기" | ||||
|                 android:textColor="@color/color_9970ff" | ||||
|                 app:drawableStartCompat="@drawable/ic_headphones_purple" /> | ||||
|         </LinearLayout> | ||||
|     </ScrollView> | ||||
| </androidx.constraintlayout.widget.ConstraintLayout> | ||||
							
								
								
									
										168
									
								
								app/src/main/res/layout/activity_login.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,168 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     xmlns:app="http://schemas.android.com/apk/res-auto" | ||||
|     xmlns:tools="http://schemas.android.com/tools" | ||||
|     android:layout_width="match_parent" | ||||
|     android:layout_height="match_parent" | ||||
|     android:background="@color/black"> | ||||
|  | ||||
|     <TextView | ||||
|         android:id="@+id/tv_toolbar" | ||||
|         android:layout_width="0dp" | ||||
|         android:layout_height="51.7dp" | ||||
|         android:background="@color/black" | ||||
|         android:fontFamily="@font/gmarket_sans_bold" | ||||
|         android:gravity="center_vertical" | ||||
|         android:paddingHorizontal="16.7dp" | ||||
|         android:textColor="@color/color_eeeeee" | ||||
|         android:textSize="18.3sp" | ||||
|         app:layout_constraintEnd_toEndOf="parent" | ||||
|         app:layout_constraintStart_toStartOf="parent" | ||||
|         app:layout_constraintTop_toTopOf="parent" | ||||
|         tools:text="바로, 상담 가능한 요즘친구" /> | ||||
|  | ||||
|     <LinearLayout | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:orientation="vertical" | ||||
|         app:layout_constraintBottom_toBottomOf="parent" | ||||
|         app:layout_constraintEnd_toEndOf="parent" | ||||
|         app:layout_constraintStart_toStartOf="parent" | ||||
|         app:layout_constraintTop_toTopOf="parent"> | ||||
|  | ||||
|         <LinearLayout | ||||
|             android:layout_width="match_parent" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:layout_marginHorizontal="26.7dp" | ||||
|             android:orientation="vertical"> | ||||
|  | ||||
|             <TextView | ||||
|                 android:layout_width="wrap_content" | ||||
|                 android:layout_height="wrap_content" | ||||
|                 android:layout_marginHorizontal="6.7dp" | ||||
|                 android:fontFamily="@font/gmarket_sans_medium" | ||||
|                 android:text="이메일" | ||||
|                 android:textColor="@color/color_eeeeee" | ||||
|                 android:textSize="12sp" /> | ||||
|  | ||||
|             <EditText | ||||
|                 android:id="@+id/et_email" | ||||
|                 android:layout_width="match_parent" | ||||
|                 android:layout_height="wrap_content" | ||||
|                 android:background="@drawable/edittext_underline" | ||||
|                 android:fontFamily="@font/gmarket_sans_medium" | ||||
|                 android:hint="이메일 주소를 입력하세요" | ||||
|                 android:importantForAutofill="no" | ||||
|                 android:inputType="textWebEmailAddress" | ||||
|                 android:paddingHorizontal="6.7dp" | ||||
|                 android:paddingTop="12dp" | ||||
|                 android:paddingBottom="8dp" | ||||
|                 android:textColor="@color/color_eeeeee" | ||||
|                 android:textColorHint="@color/color_777777" | ||||
|                 android:textCursorDrawable="@drawable/edit_text_cursor" | ||||
|                 android:textSize="13.3sp" | ||||
|                 android:theme="@style/EditTextStyle" | ||||
|                 tools:ignore="LabelFor" /> | ||||
|  | ||||
|         </LinearLayout> | ||||
|  | ||||
|         <LinearLayout | ||||
|             android:layout_width="match_parent" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:layout_marginHorizontal="26.7dp" | ||||
|             android:layout_marginTop="33.3dp" | ||||
|             android:orientation="vertical"> | ||||
|  | ||||
|             <TextView | ||||
|                 android:layout_width="wrap_content" | ||||
|                 android:layout_height="wrap_content" | ||||
|                 android:layout_marginHorizontal="6.7dp" | ||||
|                 android:fontFamily="@font/gmarket_sans_medium" | ||||
|                 android:text="비밀번호" | ||||
|                 android:textColor="@color/color_eeeeee" | ||||
|                 android:textSize="12sp" /> | ||||
|  | ||||
|             <EditText | ||||
|                 android:id="@+id/et_password" | ||||
|                 android:layout_width="match_parent" | ||||
|                 android:layout_height="wrap_content" | ||||
|                 android:background="@drawable/edittext_underline" | ||||
|                 android:fontFamily="@font/gmarket_sans_medium" | ||||
|                 android:hint="비밀번호를 입력하세요" | ||||
|                 android:importantForAutofill="no" | ||||
|                 android:inputType="textWebPassword" | ||||
|                 android:paddingHorizontal="6.7dp" | ||||
|                 android:paddingTop="12dp" | ||||
|                 android:paddingBottom="8dp" | ||||
|                 android:textColor="@color/color_eeeeee" | ||||
|                 android:textColorHint="@color/color_777777" | ||||
|                 android:textCursorDrawable="@drawable/edit_text_cursor" | ||||
|                 android:textSize="13.3sp" | ||||
|                 android:theme="@style/EditTextStyle" | ||||
|                 tools:ignore="LabelFor" /> | ||||
|  | ||||
|             <TextView | ||||
|                 android:id="@+id/tv_visible_password" | ||||
|                 android:layout_width="match_parent" | ||||
|                 android:layout_height="wrap_content" | ||||
|                 android:layout_marginTop="20dp" | ||||
|                 android:drawablePadding="13.3dp" | ||||
|                 android:fontFamily="@font/gmarket_sans_medium" | ||||
|                 android:gravity="center_vertical" | ||||
|                 android:text="비밀번호 표시" | ||||
|                 android:textColor="@color/color_eeeeee" | ||||
|                 android:textSize="13.3sp" | ||||
|                 app:drawableStartCompat="@drawable/ic_select" /> | ||||
|  | ||||
|         </LinearLayout> | ||||
|  | ||||
|         <TextView | ||||
|             android:id="@+id/tv_login" | ||||
|             android:layout_width="match_parent" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:layout_marginHorizontal="13.3dp" | ||||
|             android:layout_marginTop="40dp" | ||||
|             android:background="@drawable/bg_round_corner_6_7_9970ff" | ||||
|             android:fontFamily="@font/gmarket_sans_bold" | ||||
|             android:gravity="center" | ||||
|             android:paddingVertical="16dp" | ||||
|             android:text="로그인" | ||||
|             android:textColor="@color/white" | ||||
|             android:textSize="15sp" /> | ||||
|  | ||||
|         <LinearLayout | ||||
|             android:layout_width="wrap_content" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:layout_gravity="center" | ||||
|             android:layout_marginTop="40dp" | ||||
|             android:orientation="horizontal"> | ||||
|  | ||||
|             <TextView | ||||
|                 android:id="@+id/tv_forgot_password" | ||||
|                 android:layout_width="wrap_content" | ||||
|                 android:layout_height="wrap_content" | ||||
|                 android:fontFamily="@font/gmarket_sans_medium" | ||||
|                 android:text="비밀번호 재설정" | ||||
|                 android:textColor="@color/color_bbbbbb" | ||||
|                 android:textSize="13.3sp" /> | ||||
|  | ||||
|             <TextView | ||||
|                 android:layout_width="wrap_content" | ||||
|                 android:layout_height="wrap_content" | ||||
|                 android:layout_marginHorizontal="4dp" | ||||
|                 android:fontFamily="@font/gmarket_sans_medium" | ||||
|                 android:text="|" | ||||
|                 android:textColor="@color/color_bbbbbb" | ||||
|                 android:textSize="13.3sp" /> | ||||
|  | ||||
|             <TextView | ||||
|                 android:id="@+id/tv_sign_up" | ||||
|                 android:layout_width="wrap_content" | ||||
|                 android:layout_height="wrap_content" | ||||
|                 android:fontFamily="@font/gmarket_sans_medium" | ||||
|                 android:text="회원가입" | ||||
|                 android:textColor="@color/color_bbbbbb" | ||||
|                 android:textSize="13.3sp" /> | ||||
|         </LinearLayout> | ||||
|     </LinearLayout> | ||||
| </androidx.constraintlayout.widget.ConstraintLayout> | ||||
							
								
								
									
										448
									
								
								app/src/main/res/layout/activity_signup.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,448 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     xmlns:app="http://schemas.android.com/apk/res-auto" | ||||
|     xmlns:tools="http://schemas.android.com/tools" | ||||
|     android:layout_width="match_parent" | ||||
|     android:layout_height="match_parent" | ||||
|     android:background="@color/black" | ||||
|     android:orientation="vertical"> | ||||
|  | ||||
|     <include | ||||
|         android:id="@+id/toolbar" | ||||
|         layout="@layout/detail_toolbar" /> | ||||
|  | ||||
|     <ScrollView | ||||
|         android:layout_width="0dp" | ||||
|         android:layout_height="0dp" | ||||
|         app:layout_constraintBottom_toBottomOf="parent" | ||||
|         app:layout_constraintEnd_toEndOf="parent" | ||||
|         app:layout_constraintStart_toStartOf="parent" | ||||
|         app:layout_constraintTop_toBottomOf="@+id/toolbar"> | ||||
|  | ||||
|         <LinearLayout | ||||
|             android:layout_width="match_parent" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:orientation="vertical"> | ||||
|  | ||||
|             <LinearLayout | ||||
|                 android:id="@+id/ll_step_1" | ||||
|                 android:layout_width="match_parent" | ||||
|                 android:layout_height="wrap_content" | ||||
|                 android:orientation="vertical"> | ||||
|  | ||||
|                 <LinearLayout | ||||
|                     android:layout_width="match_parent" | ||||
|                     android:layout_height="wrap_content" | ||||
|                     android:layout_marginHorizontal="13.3dp" | ||||
|                     android:layout_marginTop="13.3dp" | ||||
|                     android:background="@drawable/bg_round_corner_6_7_222222" | ||||
|                     android:orientation="vertical" | ||||
|                     android:paddingHorizontal="13.3dp" | ||||
|                     android:paddingVertical="20dp"> | ||||
|  | ||||
|                     <LinearLayout | ||||
|                         android:layout_width="match_parent" | ||||
|                         android:layout_height="wrap_content" | ||||
|                         android:orientation="vertical"> | ||||
|  | ||||
|                         <TextView | ||||
|                             android:layout_width="wrap_content" | ||||
|                             android:layout_height="wrap_content" | ||||
|                             android:layout_marginHorizontal="6.7dp" | ||||
|                             android:fontFamily="@font/gmarket_sans_medium" | ||||
|                             android:text="이메일" | ||||
|                             android:textColor="@color/color_eeeeee" | ||||
|                             android:textSize="12sp" /> | ||||
|  | ||||
|                         <EditText | ||||
|                             android:id="@+id/et_email" | ||||
|                             android:layout_width="match_parent" | ||||
|                             android:layout_height="wrap_content" | ||||
|                             android:background="@drawable/edittext_underline" | ||||
|                             android:fontFamily="@font/gmarket_sans_medium" | ||||
|                             android:hint="이메일 주소를 입력하세요" | ||||
|                             android:importantForAutofill="no" | ||||
|                             android:inputType="textWebEmailAddress" | ||||
|                             android:paddingHorizontal="6.7dp" | ||||
|                             android:paddingTop="12dp" | ||||
|                             android:paddingBottom="8dp" | ||||
|                             android:textColor="@color/color_eeeeee" | ||||
|                             android:textColorHint="@color/color_777777" | ||||
|                             android:textCursorDrawable="@drawable/edit_text_cursor" | ||||
|                             android:textSize="13.3sp" | ||||
|                             android:theme="@style/EditTextStyle" | ||||
|                             tools:ignore="LabelFor" /> | ||||
|  | ||||
|                     </LinearLayout> | ||||
|  | ||||
|                     <LinearLayout | ||||
|                         android:layout_width="match_parent" | ||||
|                         android:layout_height="wrap_content" | ||||
|                         android:layout_marginTop="26.7dp" | ||||
|                         android:orientation="vertical"> | ||||
|  | ||||
|                         <TextView | ||||
|                             android:layout_width="wrap_content" | ||||
|                             android:layout_height="wrap_content" | ||||
|                             android:layout_marginHorizontal="6.7dp" | ||||
|                             android:fontFamily="@font/gmarket_sans_medium" | ||||
|                             android:text="비밀번호" | ||||
|                             android:textColor="@color/color_eeeeee" | ||||
|                             android:textSize="12sp" /> | ||||
|  | ||||
|                         <EditText | ||||
|                             android:id="@+id/et_password" | ||||
|                             android:layout_width="match_parent" | ||||
|                             android:layout_height="wrap_content" | ||||
|                             android:background="@drawable/edittext_underline" | ||||
|                             android:fontFamily="@font/gmarket_sans_medium" | ||||
|                             android:hint="비밀번호 (영문, 숫자 포함 8~20자)" | ||||
|                             android:importantForAutofill="no" | ||||
|                             android:inputType="textWebPassword" | ||||
|                             android:paddingHorizontal="6.7dp" | ||||
|                             android:paddingTop="12dp" | ||||
|                             android:paddingBottom="8dp" | ||||
|                             android:textColor="@color/color_eeeeee" | ||||
|                             android:textColorHint="@color/color_777777" | ||||
|                             android:textCursorDrawable="@drawable/edit_text_cursor" | ||||
|                             android:textSize="13.3sp" | ||||
|                             android:theme="@style/EditTextStyle" | ||||
|                             tools:ignore="LabelFor" /> | ||||
|  | ||||
|                     </LinearLayout> | ||||
|  | ||||
|                     <LinearLayout | ||||
|                         android:layout_width="match_parent" | ||||
|                         android:layout_height="wrap_content" | ||||
|                         android:layout_marginTop="26.7dp" | ||||
|                         android:orientation="vertical"> | ||||
|  | ||||
|                         <TextView | ||||
|                             android:layout_width="wrap_content" | ||||
|                             android:layout_height="wrap_content" | ||||
|                             android:layout_marginHorizontal="6.7dp" | ||||
|                             android:fontFamily="@font/gmarket_sans_medium" | ||||
|                             android:text="비밀번호 확인" | ||||
|                             android:textColor="@color/color_eeeeee" | ||||
|                             android:textSize="12sp" /> | ||||
|  | ||||
|                         <EditText | ||||
|                             android:id="@+id/et_password_re" | ||||
|                             android:layout_width="match_parent" | ||||
|                             android:layout_height="wrap_content" | ||||
|                             android:background="@drawable/edittext_underline" | ||||
|                             android:fontFamily="@font/gmarket_sans_medium" | ||||
|                             android:hint="비밀번호를 다시 입력해 주세요." | ||||
|                             android:importantForAutofill="no" | ||||
|                             android:inputType="textWebPassword" | ||||
|                             android:paddingHorizontal="6.7dp" | ||||
|                             android:paddingTop="12dp" | ||||
|                             android:paddingBottom="8dp" | ||||
|                             android:textColor="@color/color_eeeeee" | ||||
|                             android:textColorHint="@color/color_777777" | ||||
|                             android:textCursorDrawable="@drawable/edit_text_cursor" | ||||
|                             android:textSize="13.3sp" | ||||
|                             android:theme="@style/EditTextStyle" | ||||
|                             tools:ignore="LabelFor" /> | ||||
|  | ||||
|                     </LinearLayout> | ||||
|                 </LinearLayout> | ||||
|  | ||||
|                 <LinearLayout | ||||
|                     android:layout_width="match_parent" | ||||
|                     android:layout_height="wrap_content" | ||||
|                     android:layout_marginHorizontal="13.3dp" | ||||
|                     android:layout_marginTop="13.3dp" | ||||
|                     android:background="@drawable/bg_round_corner_6_7_222222" | ||||
|                     android:orientation="vertical" | ||||
|                     android:padding="13.3dp"> | ||||
|  | ||||
|                     <RelativeLayout | ||||
|                         android:layout_width="match_parent" | ||||
|                         android:layout_height="50dp"> | ||||
|  | ||||
|                         <TextView | ||||
|                             android:layout_width="wrap_content" | ||||
|                             android:layout_height="wrap_content" | ||||
|                             android:layout_centerVertical="true" | ||||
|                             android:layout_marginStart="6.7dp" | ||||
|                             android:fontFamily="@font/gmarket_sans_bold" | ||||
|                             android:text="약관 동의" | ||||
|                             android:textColor="@color/color_eeeeee" | ||||
|                             android:textSize="12sp" /> | ||||
|  | ||||
|                         <View | ||||
|                             android:layout_width="match_parent" | ||||
|                             android:layout_height="1dp" | ||||
|                             android:layout_alignParentBottom="true" | ||||
|                             android:background="@color/color_909090" /> | ||||
|  | ||||
|                     </RelativeLayout> | ||||
|  | ||||
|                     <RelativeLayout | ||||
|                         android:layout_width="match_parent" | ||||
|                         android:layout_height="50dp"> | ||||
|  | ||||
|                         <LinearLayout | ||||
|                             android:layout_width="wrap_content" | ||||
|                             android:layout_height="wrap_content" | ||||
|                             android:layout_centerVertical="true" | ||||
|                             android:layout_marginStart="6.7dp"> | ||||
|  | ||||
|                             <TextView | ||||
|                                 android:id="@+id/tv_terms_of_service" | ||||
|                                 android:layout_width="wrap_content" | ||||
|                                 android:layout_height="wrap_content" | ||||
|                                 android:fontFamily="@font/gmarket_sans_medium" | ||||
|                                 android:text="이용약관" | ||||
|                                 android:textColor="@color/color_eeeeee" | ||||
|                                 android:textSize="12sp" /> | ||||
|  | ||||
|                             <TextView | ||||
|                                 android:layout_width="wrap_content" | ||||
|                                 android:layout_height="wrap_content" | ||||
|                                 android:layout_marginStart="6.7dp" | ||||
|                                 android:fontFamily="@font/gmarket_sans_medium" | ||||
|                                 android:text="(필수)" | ||||
|                                 android:textColor="@color/color_9970ff" | ||||
|                                 android:textSize="12sp" /> | ||||
|  | ||||
|                         </LinearLayout> | ||||
|  | ||||
|                         <ImageView | ||||
|                             android:id="@+id/iv_terms_of_service" | ||||
|                             android:layout_width="wrap_content" | ||||
|                             android:layout_height="wrap_content" | ||||
|                             android:layout_alignParentEnd="true" | ||||
|                             android:layout_centerVertical="true" | ||||
|                             android:layout_marginEnd="6.7dp" | ||||
|                             android:contentDescription="@null" | ||||
|                             android:src="@drawable/ic_select" /> | ||||
|  | ||||
|                         <View | ||||
|                             android:layout_width="match_parent" | ||||
|                             android:layout_height="1dp" | ||||
|                             android:layout_alignParentBottom="true" | ||||
|                             android:background="@color/color_909090" /> | ||||
|  | ||||
|                     </RelativeLayout> | ||||
|  | ||||
|                     <RelativeLayout | ||||
|                         android:layout_width="match_parent" | ||||
|                         android:layout_height="50dp"> | ||||
|  | ||||
|                         <LinearLayout | ||||
|                             android:layout_width="wrap_content" | ||||
|                             android:layout_height="wrap_content" | ||||
|                             android:layout_centerVertical="true" | ||||
|                             android:layout_marginStart="6.7dp"> | ||||
|  | ||||
|                             <TextView | ||||
|                                 android:id="@+id/tv_privacy_policy" | ||||
|                                 android:layout_width="wrap_content" | ||||
|                                 android:layout_height="wrap_content" | ||||
|                                 android:fontFamily="@font/gmarket_sans_medium" | ||||
|                                 android:text="개인정보수집 및 이용동의" | ||||
|                                 android:textColor="@color/color_eeeeee" | ||||
|                                 android:textSize="12sp" /> | ||||
|  | ||||
|                             <TextView | ||||
|                                 android:layout_width="wrap_content" | ||||
|                                 android:layout_height="wrap_content" | ||||
|                                 android:layout_marginStart="6.7dp" | ||||
|                                 android:fontFamily="@font/gmarket_sans_medium" | ||||
|                                 android:text="(필수)" | ||||
|                                 android:textColor="@color/color_9970ff" | ||||
|                                 android:textSize="12sp" /> | ||||
|  | ||||
|                         </LinearLayout> | ||||
|  | ||||
|                         <ImageView | ||||
|                             android:id="@+id/iv_privacy_policy" | ||||
|                             android:layout_width="wrap_content" | ||||
|                             android:layout_height="wrap_content" | ||||
|                             android:layout_alignParentEnd="true" | ||||
|                             android:layout_centerVertical="true" | ||||
|                             android:layout_marginEnd="6.7dp" | ||||
|                             android:contentDescription="@null" | ||||
|                             android:src="@drawable/ic_select" /> | ||||
|  | ||||
|                         <View | ||||
|                             android:layout_width="match_parent" | ||||
|                             android:layout_height="1dp" | ||||
|                             android:layout_alignParentBottom="true" | ||||
|                             android:background="@color/color_909090" /> | ||||
|  | ||||
|                     </RelativeLayout> | ||||
|                 </LinearLayout> | ||||
|             </LinearLayout> | ||||
|  | ||||
|             <LinearLayout | ||||
|                 android:id="@+id/ll_step_2" | ||||
|                 android:layout_width="match_parent" | ||||
|                 android:layout_height="wrap_content" | ||||
|                 android:gravity="center" | ||||
|                 android:orientation="vertical" | ||||
|                 android:visibility="gone"> | ||||
|  | ||||
|                 <RelativeLayout | ||||
|                     android:layout_width="96.7dp" | ||||
|                     android:layout_height="116.8dp" | ||||
|                     android:layout_marginTop="13.3dp"> | ||||
|  | ||||
|                     <ImageView | ||||
|                         android:id="@+id/iv_profile" | ||||
|                         android:layout_width="80dp" | ||||
|                         android:layout_height="116.8dp" | ||||
|                         android:adjustViewBounds="true" | ||||
|                         android:background="@color/color_3e3358" | ||||
|                         android:contentDescription="@null" | ||||
|                         android:src="@drawable/ic_logo" /> | ||||
|  | ||||
|                     <ImageView | ||||
|                         android:id="@+id/iv_photo_picker" | ||||
|                         android:layout_width="wrap_content" | ||||
|                         android:layout_height="wrap_content" | ||||
|                         android:layout_alignParentEnd="true" | ||||
|                         android:layout_alignParentBottom="true" | ||||
|                         android:background="@drawable/bg_round_corner_33_3_9970ff" | ||||
|                         android:contentDescription="@null" | ||||
|                         android:padding="10dp" | ||||
|                         android:src="@drawable/ic_camera" /> | ||||
|                 </RelativeLayout> | ||||
|  | ||||
|                 <LinearLayout | ||||
|                     android:layout_width="match_parent" | ||||
|                     android:layout_height="wrap_content" | ||||
|                     android:layout_marginHorizontal="13.3dp" | ||||
|                     android:layout_marginTop="13.3dp" | ||||
|                     android:background="@drawable/bg_round_corner_6_7_222222" | ||||
|                     android:orientation="vertical" | ||||
|                     android:paddingHorizontal="13.3dp" | ||||
|                     android:paddingTop="20dp" | ||||
|                     android:paddingBottom="0dp"> | ||||
|  | ||||
|                     <LinearLayout | ||||
|                         android:layout_width="match_parent" | ||||
|                         android:layout_height="wrap_content" | ||||
|                         android:orientation="vertical"> | ||||
|  | ||||
|                         <TextView | ||||
|                             android:layout_width="wrap_content" | ||||
|                             android:layout_height="wrap_content" | ||||
|                             android:layout_marginHorizontal="6.7dp" | ||||
|                             android:fontFamily="@font/gmarket_sans_medium" | ||||
|                             android:text="닉네임 (최대 12자)" | ||||
|                             android:textColor="@color/color_eeeeee" | ||||
|                             android:textSize="12sp" /> | ||||
|  | ||||
|                         <EditText | ||||
|                             android:id="@+id/et_nickname" | ||||
|                             android:layout_width="match_parent" | ||||
|                             android:layout_height="wrap_content" | ||||
|                             android:background="@drawable/edittext_underline" | ||||
|                             android:fontFamily="@font/gmarket_sans_medium" | ||||
|                             android:hint="닉네임" | ||||
|                             android:importantForAutofill="no" | ||||
|                             android:inputType="textWebEditText" | ||||
|                             android:maxLength="12" | ||||
|                             android:paddingHorizontal="6.7dp" | ||||
|                             android:textColor="@color/color_eeeeee" | ||||
|                             android:textColorHint="@color/color_777777" | ||||
|                             android:textCursorDrawable="@drawable/edit_text_cursor" | ||||
|                             android:textSize="13.3sp" | ||||
|                             android:theme="@style/EditTextStyle" | ||||
|                             tools:ignore="LabelFor" /> | ||||
|                     </LinearLayout> | ||||
|  | ||||
|                     <LinearLayout | ||||
|                         android:layout_width="match_parent" | ||||
|                         android:layout_height="wrap_content" | ||||
|                         android:layout_marginTop="16.7dp" | ||||
|                         android:orientation="vertical"> | ||||
|  | ||||
|                         <TextView | ||||
|                             android:layout_width="wrap_content" | ||||
|                             android:layout_height="wrap_content" | ||||
|                             android:layout_marginHorizontal="6.7dp" | ||||
|                             android:fontFamily="@font/gmarket_sans_bold" | ||||
|                             android:text="성별" | ||||
|                             android:textColor="@color/color_eeeeee" | ||||
|                             android:textSize="12sp" /> | ||||
|  | ||||
|                         <LinearLayout | ||||
|                             android:layout_width="match_parent" | ||||
|                             android:layout_height="wrap_content" | ||||
|                             android:orientation="horizontal" | ||||
|                             android:paddingHorizontal="6.7dp" | ||||
|                             android:paddingVertical="13.3dp"> | ||||
|  | ||||
|                             <TextView | ||||
|                                 android:id="@+id/tv_female" | ||||
|                                 android:layout_width="0dp" | ||||
|                                 android:layout_height="wrap_content" | ||||
|                                 android:layout_weight="1" | ||||
|                                 android:button="@null" | ||||
|                                 android:drawablePadding="13.3dp" | ||||
|                                 android:fontFamily="@font/gmarket_sans_medium" | ||||
|                                 android:text="여자" | ||||
|                                 android:textColor="@color/color_eeeeee" | ||||
|                                 android:textSize="13.3sp" | ||||
|                                 app:drawableStartCompat="@drawable/ic_radio_button_select" /> | ||||
|  | ||||
|                             <TextView | ||||
|                                 android:id="@+id/tv_male" | ||||
|                                 android:layout_width="0dp" | ||||
|                                 android:layout_height="wrap_content" | ||||
|                                 android:layout_weight="1" | ||||
|                                 android:button="@null" | ||||
|                                 android:drawablePadding="13.3dp" | ||||
|                                 android:fontFamily="@font/gmarket_sans_medium" | ||||
|                                 android:text="남자" | ||||
|                                 android:textColor="@color/color_eeeeee" | ||||
|                                 android:textSize="13.3sp" | ||||
|                                 app:drawableStartCompat="@drawable/ic_radio_button_select" /> | ||||
|  | ||||
|                             <TextView | ||||
|                                 android:id="@+id/tv_none" | ||||
|                                 android:layout_width="0dp" | ||||
|                                 android:layout_height="wrap_content" | ||||
|                                 android:layout_weight="1" | ||||
|                                 android:button="@null" | ||||
|                                 android:drawablePadding="13.3dp" | ||||
|                                 android:fontFamily="@font/gmarket_sans_medium" | ||||
|                                 android:text="공개 안 함" | ||||
|                                 android:textColor="@color/color_eeeeee" | ||||
|                                 android:textSize="13.3sp" | ||||
|                                 app:drawableStartCompat="@drawable/ic_radio_button_select" /> | ||||
|  | ||||
|                         </LinearLayout> | ||||
|                     </LinearLayout> | ||||
|                 </LinearLayout> | ||||
|             </LinearLayout> | ||||
|  | ||||
|             <FrameLayout | ||||
|                 android:layout_width="match_parent" | ||||
|                 android:layout_height="wrap_content" | ||||
|                 android:layout_marginHorizontal="13.3dp" | ||||
|                 android:layout_marginTop="26.7dp" | ||||
|                 android:background="@drawable/bg_round_corner_16_7_222222" | ||||
|                 android:paddingHorizontal="13.3dp" | ||||
|                 android:paddingVertical="13.7dp"> | ||||
|  | ||||
|                 <TextView | ||||
|                     android:id="@+id/tv_sign_up" | ||||
|                     android:layout_width="match_parent" | ||||
|                     android:layout_height="wrap_content" | ||||
|                     android:background="@drawable/bg_round_corner_10_9970ff" | ||||
|                     android:fontFamily="@font/gmarket_sans_bold" | ||||
|                     android:gravity="center" | ||||
|                     android:paddingVertical="16dp" | ||||
|                     android:text="회원가입" | ||||
|                     android:textColor="@color/white" | ||||
|                     android:textSize="18.3sp" /> | ||||
|  | ||||
|             </FrameLayout> | ||||
|         </LinearLayout> | ||||
|     </ScrollView> | ||||
| </androidx.constraintlayout.widget.ConstraintLayout> | ||||
							
								
								
									
										22
									
								
								app/src/main/res/layout/activity_terms.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,22 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     xmlns:app="http://schemas.android.com/apk/res-auto" | ||||
|     android:layout_width="match_parent" | ||||
|     android:layout_height="match_parent" | ||||
|     android:background="@color/black"> | ||||
|  | ||||
|     <include | ||||
|         android:id="@+id/toolbar" | ||||
|         layout="@layout/detail_toolbar" /> | ||||
|  | ||||
|     <WebView | ||||
|         android:id="@+id/web_view" | ||||
|         android:layout_width="0dp" | ||||
|         android:layout_height="0dp" | ||||
|         android:layout_margin="13.3dp" | ||||
|         app:layout_constraintBottom_toBottomOf="parent" | ||||
|         app:layout_constraintEnd_toEndOf="parent" | ||||
|         app:layout_constraintStart_toStartOf="parent" | ||||
|         app:layout_constraintTop_toBottomOf="@+id/toolbar" /> | ||||
|  | ||||
| </androidx.constraintlayout.widget.ConstraintLayout> | ||||
							
								
								
									
										23
									
								
								app/src/main/res/layout/detail_toolbar.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,23 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     xmlns:app="http://schemas.android.com/apk/res-auto" | ||||
|     xmlns:tools="http://schemas.android.com/tools" | ||||
|     android:layout_width="match_parent" | ||||
|     android:layout_height="51.7dp" | ||||
|     android:background="@color/black" | ||||
|     android:paddingHorizontal="13.3dp"> | ||||
|  | ||||
|     <TextView | ||||
|         android:id="@+id/tv_back" | ||||
|         android:layout_width="wrap_content" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:layout_centerVertical="true" | ||||
|         android:drawablePadding="6.7dp" | ||||
|         android:fontFamily="@font/gmarket_sans_bold" | ||||
|         android:textColor="@color/color_eeeeee" | ||||
|         android:textSize="18.3sp" | ||||
|         app:drawableStartCompat="@drawable/ic_back" | ||||
|         tools:ignore="RelativeOverlap" | ||||
|         tools:text="소다라이브" /> | ||||
|  | ||||
| </RelativeLayout> | ||||
							
								
								
									
										22
									
								
								app/src/main/res/layout/dialog_loading.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,22 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     xmlns:app="http://schemas.android.com/apk/res-auto" | ||||
|     xmlns:tools="http://schemas.android.com/tools" | ||||
|     android:layout_width="match_parent" | ||||
|     android:layout_height="match_parent" | ||||
|     android:layout_gravity="center" | ||||
|     android:gravity="center" | ||||
|     android:orientation="vertical"> | ||||
|  | ||||
|     <TextView | ||||
|         android:id="@+id/tv_loading" | ||||
|         android:layout_width="wrap_content" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:layout_marginTop="10dp" | ||||
|         android:drawablePadding="10dp" | ||||
|         android:fontFamily="@font/gmarket_sans_medium" | ||||
|         android:textSize="13sp" | ||||
|         app:drawableTopCompat="@drawable/loading" | ||||
|         tools:text="로딩중 입니다..." /> | ||||
|  | ||||
| </LinearLayout> | ||||
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/mipmap-hdpi/ic_launcher.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 9.3 KiB | 
| Before Width: | Height: | Size: 1.4 KiB | 
| Before Width: | Height: | Size: 2.8 KiB | 
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/mipmap-xhdpi/ic_launcher.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 13 KiB | 
| Before Width: | Height: | Size: 1.9 KiB | 
| Before Width: | Height: | Size: 3.8 KiB | 
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/mipmap-xxhdpi/ic_launcher.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 20 KiB | 
| Before Width: | Height: | Size: 2.8 KiB | 
| Before Width: | Height: | Size: 5.8 KiB | 
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 29 KiB | 
| Before Width: | Height: | Size: 3.8 KiB | 
| Before Width: | Height: | Size: 7.6 KiB | 
| @@ -4,4 +4,12 @@ | ||||
|     <color name="white">#FFFFFFFF</color> | ||||
|  | ||||
|     <color name="color_9970ff">#9970FF</color> | ||||
|     <color name="color_eeeeee">#EEEEEE</color> | ||||
|     <color name="color_777777">#777777</color> | ||||
|     <color name="color_bbbbbb">#BBBBBB</color> | ||||
|     <color name="color_222222">#222222</color> | ||||
|     <color name="color_909090">#909090</color> | ||||
|     <color name="color_3e3358">#3E3358</color> | ||||
|  | ||||
|     <color name="color_b3909090">#B3909090</color> | ||||
| </resources> | ||||
|   | ||||
| @@ -17,4 +17,9 @@ | ||||
|         <item name="android:statusBarColor">@color/black</item> | ||||
|         <item name="android:navigationBarColor">@color/black</item> | ||||
|     </style> | ||||
|  | ||||
|     <style name="EditTextStyle" parent="Theme.AppCompat.Light"> | ||||
|         <item name="colorControlNormal">@color/color_9970ff</item> | ||||
|         <item name="colorControlActivated">@color/color_9970ff</item> | ||||
|     </style> | ||||
| </resources> | ||||
|   | ||||
| @@ -10,6 +10,7 @@ dependencyResolutionManagement { | ||||
|     repositories { | ||||
|         google() | ||||
|         mavenCentral() | ||||
|         maven { url 'https://jitpack.io' } | ||||
|     } | ||||
| } | ||||
| rootProject.name = "SodaLive" | ||||
|   | ||||