회원가입, 로그인 페이지 추가

This commit is contained in:
klaus 2023-07-24 05:38:49 +09:00
parent c1054c5ede
commit d562e9199c
76 changed files with 2238 additions and 3 deletions

View File

@ -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'
}

View File

@ -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"

View File

@ -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)
}
}

View File

@ -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
)

View File

@ -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"
}

View File

@ -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()
}
}

View File

@ -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
}
}

View File

@ -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,

View File

@ -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)

View File

@ -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
)

View File

@ -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"
)
}
}
}

View File

@ -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>>
}

View File

@ -0,0 +1,6 @@
package kr.co.vividnext.sodalive.settings
class TermsRepository(private val api: TermsApi) {
fun getTermsOfService() = api.getTermsOfService()
fun getPrivacyPolicy() = api.getPrivacyPolicy()
}

View File

@ -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("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
}
)
)
}
}

View File

@ -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() {}
}

View 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
}

View 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>>
}

View File

@ -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)
}

View File

@ -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()
}
}
}
}

View File

@ -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("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
}
)
)
}
}

View File

@ -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)

View File

@ -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
}
}
}
}

View File

@ -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
)

View File

@ -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
)

View File

@ -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!!)
}
}

View File

@ -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()
}
}
}

View File

@ -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
)

View File

@ -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"
)

View File

@ -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)
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 509 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 668 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 680 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 274 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 479 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

View 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>

View 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="16.7dp" />
<stroke
android:width="1dp"
android:color="@color/color_222222" />
</shape>

View 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="33.3dp" />
<stroke
android:width="1dp"
android:color="@color/color_9970ff" />
</shape>

View 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>

View 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>

View 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="@android:color/transparent" />
<corners android:radius="8dp" />
<stroke
android:width="1dp"
android:color="@color/color_9970ff" />
</shape>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

View File

@ -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>

View File

@ -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>

View File

@ -10,6 +10,7 @@ dependencyResolutionManagement {
repositories {
google()
mavenCentral()
maven { url 'https://jitpack.io' }
}
}
rootProject.name = "SodaLive"