diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 0000000..7cefc9d --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +NewSecondAssignment \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index d5d35ec..860da66 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,6 +1,6 @@ - + diff --git a/README.md b/README.md index 1234a35..7eed444 100644 --- a/README.md +++ b/README.md @@ -1,174 +1,225 @@ # 28th BE SOPT -## Android Seminar week4 Assignment +## Android Seminar week7 Assignment -![캡처](https://user-images.githubusercontent.com/70841402/118386974-45d59500-b656-11eb-9df7-f9f6120e1ca0.PNG) +![image](https://user-images.githubusercontent.com/70841402/121808894-8e07c780-cc95-11eb-821e-9b4bd7dc695b.png) -### Level1:안린이 탈출하기 -* 해당 링크를 보면서 로그인, 회원가입 통신 구현하기 +### Level1:안린이 탈출하기 - 1 - ✔ [Retrofit2 공식문서](http://devflow.github.io/retrofit-kr/) - - ✔ [SOPT signIn/signUp](https://www.notion.so/API-f960755d414d4c8181c2e0516c4a82a7) - -* PostMan 테스트 사진 - - SignIn - ![화면 캡처 2021-05-16 163912](https://user-images.githubusercontent.com/70841402/118389763-6c9bc780-b666-11eb-9248-de6d5da7bd03.png) - - - SignUp - ![화면 캡처 2021-05-16 155732](https://user-images.githubusercontent.com/70841402/118389767-76252f80-b666-11eb-9e72-8d80c31b9bc1.png) +1️⃣ SignIn Activity로 처음 들어왔을때 SharedPreference에서 ID/PW가 있다면? +-> 로그인 과정을 건너뛴다 + +https://user-images.githubusercontent.com/70841402/121809702-cd83e300-cc98-11eb-976d-e625f2ef4ba8.mp4 + +▪ SignInActivity + ```kotlin + override fun onCreate(savedInstanceState: Bundle?) { + + Log.d("TAG", "onCreate") + + super.onCreate(savedInstanceState) + binding = ActivitySignInBinding.inflate(layoutInflater) + setContentView(binding.root) + + searchUserAuthStorage() + setButtonEvent() + } -* 회원가입 완료 + 로그인 완료 구현 gif - - 회원가입 완료 - - ![ezgif com-gif-maker](https://user-images.githubusercontent.com/70841402/118389837-d7e59980-b666-11eb-9cb5-3ce33eb054b8.gif) - - - 로그인 완료 - - ![ezgif com-gif-maker (1)](https://user-images.githubusercontent.com/70841402/118389903-3ca0f400-b667-11eb-8986-79a937c8c14a.gif) - -* retrofit interface - - ```kotlin - interface SoptService { - @POST("/login/signin") - fun postLogin( - @Body body:RequestLoginData - ): Call - - // 서버에 POST라는 행위를 요청 - // /login/signup이란 식별자에 해당하는 데이터를 body에 담아 보낸다 - @POST("/login/signup") - fun postSignUp( - @Body body:RequestSignUpData - ):Call - } + private fun searchUserAuthStorage() { + with(SoptUserAuthStorage.getInstance(this)) { + if (hasUserData()) { + requestLogin(getUserData().let { RequestLoginData(it.id, it.password) }) + } + } + } + ... ``` - ```kotlin - object ServiceCreator { - private const val BASE_URL = "http://cherishserver.com" +▪ SoptUserAuthStorage - private val retrofit:Retrofit = Retrofit.Builder() - .baseUrl(BASE_URL) - .addConverterFactory(GsonConverterFactory.create()) - .build() + ```kotlin + ... + fun hasUserData(): Boolean { + with(getUserData()) { + return id.isNotEmpty() && password.isNotEmpty() + } + } + + fun getUserData(): SoptUserInfo = SoptUserInfo( + id = sharedPreferences.getString(USER_ID_KEY, "") ?: "", + password = sharedPreferences.getString(USER_PW_KEY, "") ?: "" + ) + ... + ``` - val soptService:SoptService = retrofit.create(SoptService::class.java) - } - ``` -* callback 연결 부분 - - SignIn +2️⃣ 로그인할 때 성공하면 SharedPreference에 집어 넣는다. + +▪ SignInActivity + ```kotlin + + private fun setButtonEvent() { + with(binding) { + buttonSignIn.setOnClickListener { loginButtonClickEvent() } + buttonSignUpStart.setOnClickListener { signUpButtonStartClickEvent() } + } + } + + private fun loginButtonClickEvent() { + val requestLoginData = RequestLoginData( + id = binding.editTextSignInIdInput.text.toString(), + password = binding.editTextSignInPasswordInput.text.toString() + ) + requestLogin(requestLoginData) + } + + private fun requestLogin(requestLoginData: RequestLoginData) { + val call: Call = ServiceCreator.soptService + .postLogin(requestLoginData) + call.enqueueUtil( + onSuccess = { response -> + val data = response.data + showToast(data?.user_nickname.toString()) + with(SoptUserAuthStorage.getInstance(this)) { + saveUserData(requestLoginData.let { SoptUserInfo(it.id, it.password) }) + } + startHomeActivity() + } + ) + } + ``` +▪ SoptUserAuthStorage + ```kotlin + ... + fun saveUserData(userData: SoptUserInfo) { + editor.putString(USER_ID_KEY, userData.id) + .putString(USER_PW_KEY, userData.password) + .apply() + } + ... + ``` + + +3️⃣ 서비스에서 로그아웃하면 SharedPreference를 clear한다. + +https://user-images.githubusercontent.com/70841402/121809715-dc6a9580-cc98-11eb-9787-531ec6de4cf6.mp4 + +▪ HomeActivity + ```kotlin + private fun signOutButtonClickEvent() { + binding.buttonSignOut.setOnClickListener { + with(SoptUserAuthStorage.getInstance(this)) { + clearAuthStorage() + } + val intent = Intent(this@HomeActivity, SignInActivity::class.java) + startActivity(intent) + finish() + } + } + ``` + +▪ SoptUserAuthStorage + ```kotlin + ... + fun clearAuthStorage() { + sharedPreferences.edit() + .clear() + .apply() + } + ... + ``` + +☑ 위와 같은 플로우로 마치 자동 로그인 처럼 할 수 있다. + +### Level1:안린이 탈출하기 - 2 + +☑ 자신만의 방식으로 코드를 정리하고, SharedPreference 구현 + + ```kotlin +class SoptUserAuthStorage private constructor(context: Context) { + + private val sharedPreferences = context.getSharedPreferences( + "${context.packageName}.$STORAGE_KEY", + Context.MODE_PRIVATE + ) + + private val editor = sharedPreferences.edit() + + fun saveUserData(userData: SoptUserInfo) { + editor.putString(USER_ID_KEY, userData.id) + .putString(USER_PW_KEY, userData.password) + .apply() + } + + fun getUserData(): SoptUserInfo = SoptUserInfo( + id = sharedPreferences.getString(USER_ID_KEY, "") ?: "", + password = sharedPreferences.getString(USER_PW_KEY, "") ?: "" + ) + + fun hasUserData(): Boolean { + with(getUserData()) { + return id.isNotEmpty() && password.isNotEmpty() + } + } + + fun clearAuthStorage() { + sharedPreferences.edit() + .clear() + .apply() + } + + companion object { + private const val STORAGE_KEY = "user_auth" + private const val USER_ID_KEY = "user_id" + private const val USER_PW_KEY = "user_pw" + + @Volatile + private var instance: SoptUserAuthStorage? = null + + @JvmStatic + fun getInstance(context: Context) = instance ?: synchronized(this) { + instance ?: SoptUserAuthStorage(context).apply { + instance = this + } + } + } +} + ``` + +☑ 확장함수 + +▪ RetrofitEnqueueUtil + + ```kotlin +fun Call.enqueueUtil( + onSuccess: (ResponseType) -> Unit, + onError: ((stateCode: Int) -> Unit)? = null +) { + this.enqueue(object : Callback { + override fun onResponse(call: Call, response: Response) { + if (response.isSuccessful) { + onSuccess.invoke(response.body() ?: return) + } else { + onError?.invoke(response.code()) + } + } + + override fun onFailure(call: Call, t: Throwable) { + Log.d("NetworkTest", "error:$t") + } + }) +} + ``` + +▪ ToastUtil - ```kotlin - private fun inputLoginInformation(){ - - // 서버로 보낼 id(email), password를 dataClass로 묶어준다 - val requestLoginData = RequestLoginData( - id = binding.editTextSignInIdInput.text.toString(), - password = binding.editTextSignInPasswordInput.text.toString() - ) - - // 현재 사용자의 정보를 받아올 것을 암시 - // 서버 통신은 I/O 작업 -> 비동적으로 받아올 Callback 내부 코드는 나중에 - // 데이터를 받아오고 실행된다 - val call: Call = ServiceCreator.soptService.postLogin(requestLoginData) - - // enqueue 함수 -> Call이 비동기 작업 이후, 동작할 Callback을 등록할 수 있다 - // 해당 함수 호출은 Callback을 등록만하고 - // 실제 서버 통신을 요청한 이후, 통신 결과가 나왔을 때 실행된다 - // object 키워드로 Callback을 구현할 익명 클래스 생성 - call.enqueue(object : Callback { - - // 네트워크 통신 Response가 있는 경우, 해당 함수를 retrofit이 호출 - override fun onResponse( - call: Call, - response: Response - ) { - // 네트워크 통신에 성공한 경우, status 코드가 200~300일 때, 실행 - if (response.isSuccessful) { - // response body 자체가 nullable 데이터 - // 서버에서 오는 data도 nullable - val data = response.body()?.data - // 통신 성공시 유저 닉네임을 보여준다 - Toast.makeText(this@SignInActivity, data?.user_nickname, Toast.LENGTH_SHORT).show() - // 홈 화면으로 넘어감 - startHomeActivity() - } else { - // 네트워크 통신에 실패한 경우, status 코드가 200~300이 아닌 경우 - Toast.makeText( - this@SignInActivity, - "네트워크 통신 실패", - Toast.LENGTH_SHORT - ).show() - } - } - - // 네트워크 통신 자체가 실패한 경우, 해당 함수를 retrofit이 실행 - override fun onFailure(call: Call, t: Throwable) { - Log.d("NetworkTest", "error:$t") - } - }) - }``` - - - - SignUp - - ```kotlin - private fun inputSignUpInformation() { - val requestSignUpData = RequestSignUpData( - email = binding.editTextSignUpIdInput.toString(), - password = binding.editTextSignUpPasswordInput.toString(), - sex = if (binding.radioButtonMan.isChecked()) "0" else "1", - nickname = binding.editTextSignUpNicknameInput.toString(), - phone = binding.editTextSignUpPhoneInput.toString(), - birth = binding.textViewSignUpBirthInput.toString() - ) - - val call: Call = - ServiceCreator.soptService.postSignUp(requestSignUpData) - - call.enqueue(object : Callback { - override fun onResponse( - call: Call, - response: Response - ) { - if (response.isSuccessful) { - - Toast.makeText( - this@SignUpActivity, - "회원가입 완료", - Toast.LENGTH_SHORT - ).show() - - val intent = Intent() - intent.putExtra( - "SignUpNameExtra", - binding.editTextSignUpNameInput.text.toString() - ) - intent.putExtra("SignUpIDExtra", binding.editTextSignUpIdInput.text.toString()) - intent.putExtra( - "SignUpPasswordExtra", - binding.editTextSignUpPasswordInput.text.toString() - ) - setResult(Activity.RESULT_OK, intent) - finish() - - } else { - Toast.makeText( - this@SignUpActivity, - "네트워크 통신 실패", - Toast.LENGTH_SHORT - ).show() - } - } - override fun onFailure(call: Call, t: Throwable) { - Log.d("NetworkTest", "error:$t") - } - }) - }``` + ```kotlin +fun Context.showToast(msg: String) { + Toast.makeText(this, msg, Toast.LENGTH_SHORT) + .show() +} + ``` ### 과제를 통해 배우고 성장한 내용 -안드로이드 스튜디오에서 벗어나(?) 서버와 통신해볼 수 있어서 정말 신기하고 재밌었습니다. 하지만 완벽히 이해하고 선택 과제에서 안내해주신 것과 같은 다양한 api를 활용하기 위해서는 많은 노력이 필요할 것 같습니다. 이번에 수행한 필수 과제에 대한 이해가 더 깊어지면, '서버 연결은 여러번해서 익숙해지는 것 말고는 답이 없다.'라고 말씀하신 것처럼 여러번 시도해보고 실패해보고 에러도 마주쳐보겠습니다. 그리고 이 과정에서 에러를 해결하는 방법을 숙지하면, 서버 통신에 대해 조금 더 알아갈 수 있을 것 같습니다. +이번 세미나에서 SharedPreference에 대해 배우고, 과제에서는 직접 자동 로그인과 로그아웃을 구현해보았습니다. 앱을 실행할 때마다 아이디와 비밀번호를 입력하는 과정이 불편했는데, SharedPreference를 통해 이런 불편함을 해소할 수 있어서 매우 신기하고 유익한 시간이었습니다. 하지만 SharedPreference를 다루는 것에 아직 서툴고 어려워서 제 자신만의 코드를 구성한 것이 아니라 안드로이드파트장님의 코드를 많이 참고했다는 것이 아쉬웠습니다. 지금까지 배워온 것을 복습하고, 아직 서툰 것은 스스로 공부해나가고, 앞으로 있을 행사에 참여하면서 더 큰 성장을 할 수 있도록 노력하겠습니다. 감사합니다. 😊 diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..24ab246 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,65 @@ +plugins { + id 'com.android.application' + id 'kotlin-android' +} + +android { + compileSdkVersion 30 + buildToolsVersion "30.0.3" + + defaultConfig { + applicationId "com.example.newsecondassignment" + minSdkVersion 23 + targetSdkVersion 30 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = '1.8' + } + + buildFeatures { + viewBinding = true + } +} + +dependencies { + + implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" + implementation 'androidx.core:core-ktx:1.3.2' + implementation 'androidx.appcompat:appcompat:1.2.0' + implementation 'com.google.android.material:material:1.3.0' + implementation 'androidx.constraintlayout:constraintlayout:2.0.4' + implementation 'androidx.legacy:legacy-support-v4:1.0.0' + testImplementation 'junit:junit:4.+' + androidTestImplementation 'androidx.test.ext:junit:1.1.2' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' + + // IllegalArgumentException: Can only use lower 16 bits for requestCode Error 해결 + // ActivityResult API 사용하면서 "Can only use lower 16 bits for requestCode" 오류를 피하기 위해선 + // 위와 같이 1.2.0 버전 이상의 androidx.activity 종속성과 1.3.0-alpha05 버전 이상의 androidx.fragment 종속성 모두 사용해야 합니다. + implementation "androidx.activity:activity-ktx:1.3.0-alpha05" + implementation 'androidx.fragment:fragment-ktx:1.3.0-rc01' + + // 서버 연결을 위한 Retrofit2 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + // gson + implementation 'com.google.code.gson:gson:2.8.6' + // retrofit2에서 gson 사용을 위한 컨버터 + implementation 'com.squareup.retrofit2:converter-gson:2.9.0' + + implementation 'de.hdodenhof:circleimageview:3.1.0' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/androidTest/java/com/example/newsecondassignment/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/example/newsecondassignment/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..1058622 --- /dev/null +++ b/app/src/androidTest/java/com/example/newsecondassignment/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.example.newsecondassignment + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.example.newsecondassignment", appContext.packageName) + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..508a96c --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/example/newsecondassignment/FollowingUserInfo.kt b/app/src/main/java/com/example/newsecondassignment/FollowingUserInfo.kt new file mode 100644 index 0000000..5bcb8a4 --- /dev/null +++ b/app/src/main/java/com/example/newsecondassignment/FollowingUserInfo.kt @@ -0,0 +1,6 @@ +package com.example.newsecondassignment + +data class FollowingUserInfo ( + val userImage:String, + val userName:String +) \ No newline at end of file diff --git a/app/src/main/java/com/example/newsecondassignment/RepoInfo.kt b/app/src/main/java/com/example/newsecondassignment/RepoInfo.kt new file mode 100644 index 0000000..49c487b --- /dev/null +++ b/app/src/main/java/com/example/newsecondassignment/RepoInfo.kt @@ -0,0 +1,7 @@ +package com.example.newsecondassignment + +data class RepoInfo ( + val userRepoName:String, + val userRepoDescription:String, + val userRepoLanguage:String +) \ No newline at end of file diff --git a/app/src/main/java/com/example/newsecondassignment/SoptUserAuthStorage.kt b/app/src/main/java/com/example/newsecondassignment/SoptUserAuthStorage.kt new file mode 100644 index 0000000..a71143c --- /dev/null +++ b/app/src/main/java/com/example/newsecondassignment/SoptUserAuthStorage.kt @@ -0,0 +1,52 @@ +package com.example.newsecondassignment + +import android.content.Context + +class SoptUserAuthStorage private constructor(context: Context) { + + private val sharedPreferences = context.getSharedPreferences( + "${context.packageName}.$STORAGE_KEY", + Context.MODE_PRIVATE + ) + + private val editor = sharedPreferences.edit() + + fun getUserData(): SoptUserInfo = SoptUserInfo( + id = sharedPreferences.getString(USER_ID_KEY, "") ?: "", + password = sharedPreferences.getString(USER_PW_KEY, "") ?: "" + ) + + fun saveUserData(userData: SoptUserInfo) { + editor.putString(USER_ID_KEY, userData.id) + .putString(USER_PW_KEY, userData.password) + .apply() + } + + fun hasUserData(): Boolean { + with(getUserData()) { + return id.isNotEmpty() && password.isNotEmpty() + } + } + + fun clearAuthStorage() { + sharedPreferences.edit() + .clear() + .apply() + } + + companion object { + private const val STORAGE_KEY = "user_auth" + private const val USER_ID_KEY = "user_id" + private const val USER_PW_KEY = "user_pw" + + @Volatile + private var instance: SoptUserAuthStorage? = null + + @JvmStatic + fun getInstance(context: Context) = instance ?: synchronized(this) { + instance ?: SoptUserAuthStorage(context).apply { + instance = this + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/newsecondassignment/SoptUserInfo.kt b/app/src/main/java/com/example/newsecondassignment/SoptUserInfo.kt new file mode 100644 index 0000000..0e6d3f9 --- /dev/null +++ b/app/src/main/java/com/example/newsecondassignment/SoptUserInfo.kt @@ -0,0 +1,6 @@ +package com.example.newsecondassignment + +data class SoptUserInfo( + val id: String, + val password: String +) diff --git a/app/src/main/java/com/example/newsecondassignment/activity/HomeActivity.kt b/app/src/main/java/com/example/newsecondassignment/activity/HomeActivity.kt new file mode 100644 index 0000000..f775ab5 --- /dev/null +++ b/app/src/main/java/com/example/newsecondassignment/activity/HomeActivity.kt @@ -0,0 +1,88 @@ +package com.example.newsecondassignment.activity + +import android.content.Intent +import androidx.appcompat.app.AppCompatActivity +import android.os.Bundle +import com.example.newsecondassignment.RepoInfo +import com.example.newsecondassignment.SoptUserAuthStorage +import com.example.newsecondassignment.SoptUserInfo +import com.example.newsecondassignment.adapter.RepoListAdapter +import com.example.newsecondassignment.databinding.ActivityHomeBinding + +class HomeActivity : AppCompatActivity() { + + private lateinit var binding:ActivityHomeBinding + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityHomeBinding.inflate(layoutInflater) + setContentView(binding.root) + + moreButtonClickEvent() + logoutButtonClickEvent() + + val repoListAdapter = RepoListAdapter() + + binding.recyclerviewRepo.adapter = repoListAdapter + + repoListAdapter.repoList.addAll( + listOf( + RepoInfo( + userRepoName = "안드로이드 과제 1", + userRepoDescription = "킹갓제너럴엠페러마제스티골져스프레셔스뷰리풀하이클래스엘레강스럭셔리클래식지니어스원더풀러블리월드탑클래스코드", + userRepoLanguage = "kotlin" + ), + RepoInfo( + userRepoName = "안드로이드 과제 2", + userRepoDescription = "킹갓제너럴엠페러마제스티골져스프레셔스뷰리풀하이클래스엘레강스럭셔리클래식지니어스원더풀러블리월드탑클래스코드", + userRepoLanguage = "kotlin" + ), + RepoInfo( + userRepoName = "안드로이드 과제 3", + userRepoDescription = "킹갓제너럴엠페러마제스티골져스프레셔스뷰리풀하이클래스엘레강스럭셔리클래식지니어스원더풀러블리월드탑클래스코드", + userRepoLanguage = "kotlin" + ), + RepoInfo( + userRepoName = "안드로이드 과제 4", + userRepoDescription = "킹갓제너럴엠페러마제스티골져스프레셔스뷰리풀하이클래스엘레강스럭셔리클래식지니어스원더풀러블리월드탑클래스코드", + userRepoLanguage = "kotlin" + ), + RepoInfo( + userRepoName = "안드로이드 과제 5", + userRepoDescription = "킹갓제너럴엠페러마제스티골져스프레셔스뷰리풀하이클래스엘레강스럭셔리클래식지니어스원더풀러블리월드탑클래스코드", + userRepoLanguage = "kotlin" + ), + RepoInfo( + userRepoName = "안드로이드 과제 6", + userRepoDescription = "킹갓제너럴엠페러마제스티골져스프레셔스뷰리풀하이클래스엘레강스럭셔리클래식지니어스원더풀러블리월드탑클래스코드", + userRepoLanguage = "kotlin" + ), + RepoInfo( + userRepoName = "안드로이드 과제 7", + userRepoDescription = "킹갓제너럴엠페러마제스티골져스프레셔스뷰리풀하이클래스엘레강스럭셔리클래식지니어스원더풀러블리월드탑클래스코드", + userRepoLanguage = "kotlin" + ) + ) + ) + repoListAdapter.notifyDataSetChanged() + } + + private fun logoutButtonClickEvent() { + binding.buttonSignOut.setOnClickListener { + with(SoptUserAuthStorage.getInstance(this)) { + clearAuthStorage() + } + val intent = Intent(this@HomeActivity, SignInActivity::class.java) + startActivity(intent) + finish() + } + } + + private fun moreButtonClickEvent() { + binding.buttonHomeMore.setOnClickListener{ + val intent = Intent(this@HomeActivity, UserInfoActivity::class.java) + startActivity(intent) + finish() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/newsecondassignment/activity/RepoInfoActivity.kt b/app/src/main/java/com/example/newsecondassignment/activity/RepoInfoActivity.kt new file mode 100644 index 0000000..9ffc228 --- /dev/null +++ b/app/src/main/java/com/example/newsecondassignment/activity/RepoInfoActivity.kt @@ -0,0 +1,24 @@ +package com.example.newsecondassignment.activity + +import androidx.appcompat.app.AppCompatActivity +import android.os.Bundle +import com.example.newsecondassignment.R +import com.example.newsecondassignment.fragment.RepoListFragment +import com.example.newsecondassignment.databinding.ActivityRepoInfoBinding + +class RepoInfoActivity : AppCompatActivity() { + + private lateinit var binding:ActivityRepoInfoBinding + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityRepoInfoBinding.inflate(layoutInflater) + setContentView(binding.root) + + val repoListFragment = RepoListFragment() + + val transaction = supportFragmentManager.beginTransaction() + transaction.add(R.id.repo_info_fragment, repoListFragment) + transaction.commit() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/newsecondassignment/activity/SignInActivity.kt b/app/src/main/java/com/example/newsecondassignment/activity/SignInActivity.kt new file mode 100644 index 0000000..15628eb --- /dev/null +++ b/app/src/main/java/com/example/newsecondassignment/activity/SignInActivity.kt @@ -0,0 +1,116 @@ +package com.example.newsecondassignment.activity + +import android.app.Activity +import android.content.Intent +import androidx.appcompat.app.AppCompatActivity +import android.os.Bundle +import android.util.Log +import android.widget.Toast +import androidx.activity.result.ActivityResult +import androidx.activity.result.contract.ActivityResultContracts +import com.example.newsecondassignment.SoptUserAuthStorage +import com.example.newsecondassignment.SoptUserInfo +import com.example.newsecondassignment.activity.HomeActivity +import com.example.newsecondassignment.activity.SignUpActivity +import com.example.newsecondassignment.api.ServiceCreator +import com.example.newsecondassignment.databinding.ActivitySignInBinding +import com.example.newsecondassignment.request.RequestLoginData +import com.example.newsecondassignment.response.ResponseLoginData +import com.example.newsecondassignment.utils.enqueueUtil +import com.example.newsecondassignment.utils.showToast +import retrofit2.Call +import retrofit2.Callback +import retrofit2.Response + +class SignInActivity : AppCompatActivity() { + + private lateinit var binding: ActivitySignInBinding + + override fun onCreate(savedInstanceState: Bundle?) { + + Log.d("TAG", "onCreate") + + super.onCreate(savedInstanceState) + binding = ActivitySignInBinding.inflate(layoutInflater) + setContentView(binding.root) + + searchUserAuthStorage() + setButtonEvent() + } + + private fun setButtonEvent() { + with(binding) { + buttonSignIn.setOnClickListener { loginButtonClickEvent() } + buttonSignUpStart.setOnClickListener { signUpButtonStartClickEvent() } + } + } + + private fun searchUserAuthStorage() { + with(SoptUserAuthStorage.getInstance(this)) { + if (hasUserData()) { + requestLogin(getUserData().let { RequestLoginData(it.id, it.password) }) + } + } + } + + private fun loginButtonClickEvent() { + val requestLoginData = RequestLoginData( + id = binding.editTextSignInIdInput.text.toString(), + password = binding.editTextSignInPasswordInput.text.toString() + ) + requestLogin(requestLoginData) + } + + private fun requestLogin(requestLoginData: RequestLoginData) { + val call: Call = ServiceCreator.soptService + .postLogin(requestLoginData) + call.enqueueUtil( + onSuccess = { response -> + val data = response.data + showToast(data?.user_nickname.toString()) + with(SoptUserAuthStorage.getInstance(this)) { + saveUserData(requestLoginData.let { SoptUserInfo(it.id, it.password) }) + } + startHomeActivity() + } + ) + } + + // registerForActivityResult 함수를 사용해서 Callback을 등록 + // 인자로 들어가는 것은 ActivityResultContracts 클래스의 Static 함수들 -> Result를 받기 위해서 Activity를 실행하는 StartActivityForResult() 함수를 넣어줌 + val signUpActivityLauncher = + registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { + if (it.resultCode == Activity.RESULT_OK) { + val userName = it.data?.getStringExtra("SignUpNameExtra") + val userID = it.data?.getStringExtra("SignUpIDExtra") + val userPassword = it.data?.getStringExtra("SignUpPasswordExtra") + + binding.editTextSignInIdInput.setText(userID) + binding.editTextSignInPasswordInput.setText(userPassword) + } else { + Toast.makeText( + this@SignInActivity, + "회원가입에 실패했습니다!", + Toast.LENGTH_SHORT + ).show() + } + } + + private fun signUpButtonStartClickEvent() { + binding.buttonSignUpStart.setOnClickListener { + val intent = Intent(this@SignInActivity, SignUpActivity::class.java) + signUpActivityLauncher.launch(intent) + } + } + + private fun startHomeActivity() { + Toast.makeText( + this@SignInActivity, + "로그인 성공", + Toast.LENGTH_SHORT + ).show() + val intent = Intent(this@SignInActivity, HomeActivity::class.java) + startActivity(intent) + finish() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/newsecondassignment/activity/SignUpActivity.kt b/app/src/main/java/com/example/newsecondassignment/activity/SignUpActivity.kt new file mode 100644 index 0000000..f77ac47 --- /dev/null +++ b/app/src/main/java/com/example/newsecondassignment/activity/SignUpActivity.kt @@ -0,0 +1,113 @@ +package com.example.newsecondassignment.activity + +import android.app.Activity +import android.app.DatePickerDialog +import android.content.Intent +import androidx.appcompat.app.AppCompatActivity +import android.os.Bundle +import android.util.Log +import android.widget.Toast +import com.example.newsecondassignment.api.ServiceCreator +import com.example.newsecondassignment.databinding.ActivitySignUpBinding +import com.example.newsecondassignment.request.RequestSignUpData +import com.example.newsecondassignment.response.ResponseSignUpData +import retrofit2.Call +import retrofit2.Callback +import retrofit2.Response +import java.util.* + +class SignUpActivity : AppCompatActivity() { + + private lateinit var binding: ActivitySignUpBinding + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivitySignUpBinding.inflate(layoutInflater) + setContentView(binding.root) + + signUpEvent() + showDatePicker() + } + + private fun signUpEvent() { + var signUpName = binding.editTextSignUpNameInput.text + var signUpID = binding.editTextSignUpIdInput.text + var signUpPassword = binding.editTextSignUpPasswordInput.text + + binding.buttonSignUpFinish.setOnClickListener { + if (signUpName.isNullOrBlank() || signUpID.isNullOrBlank() || signUpPassword.isNullOrBlank()) { + Toast.makeText( + this@SignUpActivity, + "빈 칸이 있는지 확인해주세요!", + Toast.LENGTH_SHORT + ).show() + } else { + + inputSignUpInformation() + } + } + } + + private fun showDatePicker() { + val calendar = Calendar.getInstance() + val year = calendar.get(Calendar.YEAR) + val month = calendar.get(Calendar.MONTH) + val day = calendar.get(Calendar.DAY_OF_MONTH) + + binding.textViewSignUpBirthInput.setOnClickListener { + val listener = + DatePickerDialog(this, DatePickerDialog.OnDateSetListener { view, y, m, d -> + binding.textViewSignUpBirthInput.setText("" + y + "-" + m + "-" + d) + }, year, month, day) + + listener.show() + + } + } + + private fun inputSignUpInformation() { + val requestSignUpData = RequestSignUpData( + email = binding.editTextSignUpIdInput.toString(), + password = binding.editTextSignUpPasswordInput.toString(), + sex = if (binding.radioButtonMan.isChecked()) "0" else "1", + nickname = binding.editTextSignUpNicknameInput.toString(), + phone = binding.editTextSignUpPhoneInput.toString(), + birth = binding.textViewSignUpBirthInput.toString() + ) + + val call: Call = ServiceCreator.soptService.postSignUp(requestSignUpData) + + call.enqueue(object : Callback { + override fun onResponse( + call: Call, + response: Response + ) { + if (response.isSuccessful) { + + Toast.makeText( + this@SignUpActivity, + "회원가입 완료", + Toast.LENGTH_SHORT + ).show() + + val intent = Intent() + intent.putExtra("SignUpNameExtra", binding.editTextSignUpNameInput.text.toString()) + intent.putExtra("SignUpIDExtra", binding.editTextSignUpIdInput.text.toString()) + intent.putExtra("SignUpPasswordExtra", binding.editTextSignUpPasswordInput.text.toString()) + setResult(Activity.RESULT_OK, intent) + finish() + + } else { + Toast.makeText( + this@SignUpActivity, + "네트워크 통신 실패", + Toast.LENGTH_SHORT + ).show() + } + } + override fun onFailure(call: Call, t: Throwable) { + Log.d("NetworkTest", "error:$t") + } + }) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/newsecondassignment/activity/UserInfoActivity.kt b/app/src/main/java/com/example/newsecondassignment/activity/UserInfoActivity.kt new file mode 100644 index 0000000..d1ea651 --- /dev/null +++ b/app/src/main/java/com/example/newsecondassignment/activity/UserInfoActivity.kt @@ -0,0 +1,29 @@ +package com.example.newsecondassignment.activity + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import com.example.newsecondassignment.fragment.FollowingListFragment +import com.example.newsecondassignment.R +import com.example.newsecondassignment.databinding.ActivityUserInfoBinding + +class UserInfoActivity:AppCompatActivity() { + + private lateinit var binding: ActivityUserInfoBinding + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityUserInfoBinding.inflate(layoutInflater) + setContentView(binding.root) + + // 2. UserInfoActivity에 FollowingListFragment 보여주기 + // 2-1. UserInfoActivity에서 보여줄 FollowingListFragment 생성 + val followingListFragment = FollowingListFragment() + + // 2-2. UserInfoActivity에서 FragmentManager가 Fragment를 관리할 transaction(작업 단위) 만들어주기 + val transaction = supportFragmentManager.beginTransaction() + // 2-3. 해당 작업 단위에서 어떤 View(id 참조)에 어떤 Fragment를 보여줄 것인가 + // 2-4. 그것이 해당 뷰에 추가하는 일이라고 선언 + transaction.add(R.id.user_info_fragment, followingListFragment) + transaction.commit() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/newsecondassignment/adapter/FollowingListAdapter.kt b/app/src/main/java/com/example/newsecondassignment/adapter/FollowingListAdapter.kt new file mode 100644 index 0000000..d1cac75 --- /dev/null +++ b/app/src/main/java/com/example/newsecondassignment/adapter/FollowingListAdapter.kt @@ -0,0 +1,48 @@ +package com.example.newsecondassignment.adapter + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.example.newsecondassignment.FollowingUserInfo +import com.example.newsecondassignment.databinding.ItemFollowUserBinding + +// 1. Adapter를 만들기 위해서 RecyclerView의 Adapter를 상속 -> <> 안에는 데이터를 어떤 뷰로 바꿀지에 해당하는 뷰홀더가 들어감 -> 일단 비워둠 +// 3. <> 안에 만든 뷰홀더 넣어주기 +// 4. FollowingListAdapter -> Implement Methods +class FollowingListAdapter : RecyclerView.Adapter() { + + // 5. 아까 만든 데이터 클래스 타입의 리스트 생성 + val userList = mutableListOf() + + // Adapter는 아이템마다 ViewHolder를 만드는 방법 정의 필요 + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FollowingUserViewHolder { + + // 6. 우리가 만들 뷰홀더에서 뷰를 참조하고 관리하기 위해서 ViewBinding 객체를 만들어주는 부분 + val binding = ItemFollowUserBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + return FollowingUserViewHolder(binding) + } + + // Adapter는 전체 아이템의 수를 알아야 함 + // 7. 모든 리스트는 해당 리스트의 전체 아이템 수를 size라는 친구로 반환 + override fun getItemCount(): Int = userList.size + + // Adapter는 ViewHolder에 Data를 전달하는 방법을 정의해야 함 + override fun onBindViewHolder(holder: FollowingUserViewHolder, position: Int) { + // 8. Data를 전달하며 뷰홀더가 알아서 data를 뷰에 묶어줄 수 있도록 시키기 -> 뷰홀더에게 리스트에서 지금 보여줘야 할 부분에 대한 위치의 데이터를 보내줌 + holder.onBind(userList[position]) + } + + // 2. 뷰홀더 만들기 + class FollowingUserViewHolder( + private val binding: ItemFollowUserBinding + ) : RecyclerView.ViewHolder(binding.root) { + // 9. 위의 onBind() 정의 -> 뷰홀더가 받은 데이터를 어떻게 묶어줄지 정의 + fun onBind(followingUserInfo: FollowingUserInfo) { + binding.followUserName.text = followingUserInfo.userName + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/newsecondassignment/adapter/RepoListAdapter.kt b/app/src/main/java/com/example/newsecondassignment/adapter/RepoListAdapter.kt new file mode 100644 index 0000000..fdc9cd1 --- /dev/null +++ b/app/src/main/java/com/example/newsecondassignment/adapter/RepoListAdapter.kt @@ -0,0 +1,39 @@ +package com.example.newsecondassignment.adapter + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.example.newsecondassignment.RepoInfo +import com.example.newsecondassignment.databinding.ItemRepoBinding + +class RepoListAdapter:RecyclerView.Adapter() { + + val repoList = mutableListOf() + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RepoViewHolder { + val binding = ItemRepoBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + return RepoViewHolder(binding) + } + + override fun getItemCount(): Int = repoList.size + + override fun onBindViewHolder(holder: RepoViewHolder, position: Int) { + holder.onBind(repoList[position]) + } + + class RepoViewHolder( + private val binding:ItemRepoBinding + ): RecyclerView.ViewHolder(binding.root) { + fun onBind(repoInfo: RepoInfo) { + binding.repoName.text = repoInfo.userRepoName + binding.repoDescription.text = repoInfo.userRepoDescription + binding.repoLanguage.text = repoInfo.userRepoLanguage + + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/newsecondassignment/api/ServiceCreator.kt b/app/src/main/java/com/example/newsecondassignment/api/ServiceCreator.kt new file mode 100644 index 0000000..3e0aff9 --- /dev/null +++ b/app/src/main/java/com/example/newsecondassignment/api/ServiceCreator.kt @@ -0,0 +1,15 @@ +package com.example.newsecondassignment.api + +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory + +object ServiceCreator { + private const val BASE_URL = "http://cherishserver.com" + + private val retrofit:Retrofit = Retrofit.Builder() + .baseUrl(BASE_URL) + .addConverterFactory(GsonConverterFactory.create()) + .build() + + val soptService:SoptService = retrofit.create(SoptService::class.java) +} \ No newline at end of file diff --git a/app/src/main/java/com/example/newsecondassignment/api/SoptService.kt b/app/src/main/java/com/example/newsecondassignment/api/SoptService.kt new file mode 100644 index 0000000..4129cb4 --- /dev/null +++ b/app/src/main/java/com/example/newsecondassignment/api/SoptService.kt @@ -0,0 +1,24 @@ +package com.example.newsecondassignment.api + +import android.app.DownloadManager +import com.example.newsecondassignment.request.RequestLoginData +import com.example.newsecondassignment.request.RequestSignUpData +import com.example.newsecondassignment.response.ResponseSignUpData +import com.example.newsecondassignment.response.ResponseLoginData +import retrofit2.Call +import retrofit2.http.Body +import retrofit2.http.POST + +interface SoptService { + @POST("/login/signin") + fun postLogin( + @Body body:RequestLoginData + ): Call + + // 서버에 POST라는 행위를 요청 + // /login/signup이란 식별자에 해당하는 데이터를 body에 담아 보낸다 + @POST("/login/signup") + fun postSignUp( + @Body body:RequestSignUpData + ):Call +} \ No newline at end of file diff --git a/app/src/main/java/com/example/newsecondassignment/fragment/FollowingListFragment.kt b/app/src/main/java/com/example/newsecondassignment/fragment/FollowingListFragment.kt new file mode 100644 index 0000000..dc03adb --- /dev/null +++ b/app/src/main/java/com/example/newsecondassignment/fragment/FollowingListFragment.kt @@ -0,0 +1,77 @@ +package com.example.newsecondassignment.fragment + +import android.os.Bundle +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import com.example.newsecondassignment.FollowingUserInfo +import com.example.newsecondassignment.adapter.FollowingListAdapter +import com.example.newsecondassignment.databinding.FragmentFollowingListBinding + + +class FollowingListFragment : Fragment() { + + // _binding -> 뷰가 만들어질 때 초기화하고, 뷰가 죽으면 참조를 삭제해줌 + private var _binding: FragmentFollowingListBinding? = null + private val binding get() = _binding ?: error("View를 참조하기 위해 binding이 초기화되지 않았습니다.") + private lateinit var followingListAdapter: FollowingListAdapter + + override fun onCreateView( // fragment의 뷰를 그리는 시점 + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + Log.d("TAG", "fragment - onCreateView") + + // 1. ViewBinding을 이용해 binding 객체 만들어주기 -> 2. UserInfoActivity에 FollowingListFragment 보여주기 + _binding = FragmentFollowingListBinding.inflate( + inflater, + container, + false + ) + return binding.root + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + // 사용할 어댑터의 초기 값 넣어주기 + followingListAdapter = FollowingListAdapter() + + // RecyclerView에 어댑터를 우리가 만든 어댑터로 만들어주기 + binding.userList.adapter = followingListAdapter + + followingListAdapter.userList.addAll( + listOf( + FollowingUserInfo( + userImage = "지금은 빈 칸", + userName = "cjsjizzu" + ), + FollowingUserInfo( + userImage = "지금은 빈 칸", + userName = "cjsjizzu" + ), + FollowingUserInfo( + userImage = "지금은 빈 칸", + userName = "cjsjizzu" + ), + FollowingUserInfo( + userImage = "지금은 빈 칸", + userName = "cjsjizzu" + ), + FollowingUserInfo( + userImage = "지금은 빈 칸", + userName = "cjsjizzu" + ) + ) + ) + followingListAdapter.notifyDataSetChanged() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/newsecondassignment/fragment/RepoListFragment.kt b/app/src/main/java/com/example/newsecondassignment/fragment/RepoListFragment.kt new file mode 100644 index 0000000..7f6922e --- /dev/null +++ b/app/src/main/java/com/example/newsecondassignment/fragment/RepoListFragment.kt @@ -0,0 +1,90 @@ +package com.example.newsecondassignment.fragment + +import android.os.Bundle +import androidx.fragment.app.Fragment +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.example.newsecondassignment.adapter.RepoListAdapter +import com.example.newsecondassignment.databinding.FragmentRepoListBinding + +class RepoListFragment : Fragment() { + + private var _binding:FragmentRepoListBinding? = null + private val binding get() = _binding?:error("View를 참조하기 위해 binding이 초기화되지 않았습니다.") + private lateinit var repoListAdapter: RepoListAdapter + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + + _binding = FragmentRepoListBinding.inflate( + inflater, + container, + false + ) + // Inflate the layout for this fragment + return binding.root + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + +// repoListAdapter = RepoListAdapter() +// +// binding.repoList.adapter = repoListAdapter +// +// repoListAdapter.repoList.addAll( +// listOf( +// RepoInfo( +// userRepoName = "qqqqq", +// userRepoDescription = "awesome", +// userRepoLanguage = "kt" +// ), +// RepoInfo( +// userRepoName = "qqqqq", +// userRepoDescription = "awesome", +// userRepoLanguage = "kt" +// ), +// RepoInfo( +// userRepoName = "qqqqq", +// userRepoDescription = "awesome", +// userRepoLanguage = "kt" +// ), +// RepoInfo( +// userRepoName = "qqqqq", +// userRepoDescription = "awesome", +// userRepoLanguage = "kt" +// ), +// RepoInfo( +// userRepoName = "qqqqq", +// userRepoDescription = "awesome", +// userRepoLanguage = "kt" +// ), +// RepoInfo( +// userRepoName = "qqqqq", +// userRepoDescription = "awesome", +// userRepoLanguage = "kt" +// ), +// RepoInfo( +// userRepoName = "qqqqq", +// userRepoDescription = "awesome", +// userRepoLanguage = "kt" +// ), +// RepoInfo( +// userRepoName = "qqqqq", +// userRepoDescription = "awesome", +// userRepoLanguage = "kt" +// ) +// ) +// ) +// repoListAdapter.notifyDataSetChanged() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/newsecondassignment/request/RequestLoginData.kt b/app/src/main/java/com/example/newsecondassignment/request/RequestLoginData.kt new file mode 100644 index 0000000..9c5eab7 --- /dev/null +++ b/app/src/main/java/com/example/newsecondassignment/request/RequestLoginData.kt @@ -0,0 +1,14 @@ +package com.example.newsecondassignment.request + +import com.google.gson.annotations.SerializedName + +//data class RequestLoginData( +// val email: String, +// val password: String +//) + +data class RequestLoginData( + @SerializedName("email") + val id: String, + val password: String +) \ No newline at end of file diff --git a/app/src/main/java/com/example/newsecondassignment/request/RequestSignUpData.kt b/app/src/main/java/com/example/newsecondassignment/request/RequestSignUpData.kt new file mode 100644 index 0000000..09a5d7f --- /dev/null +++ b/app/src/main/java/com/example/newsecondassignment/request/RequestSignUpData.kt @@ -0,0 +1,12 @@ +package com.example.newsecondassignment.request + +import com.google.gson.annotations.SerializedName + +data class RequestSignUpData( + val email: String, + val password: String, + val sex:String, + val nickname:String, + val phone:String, + val birth:String +) \ No newline at end of file diff --git a/app/src/main/java/com/example/newsecondassignment/response/ResponseLoginData.kt b/app/src/main/java/com/example/newsecondassignment/response/ResponseLoginData.kt new file mode 100644 index 0000000..d2c6e8b --- /dev/null +++ b/app/src/main/java/com/example/newsecondassignment/response/ResponseLoginData.kt @@ -0,0 +1,19 @@ +package com.example.newsecondassignment.response + +import com.google.gson.annotations.SerializedName + +data class ResponseLoginData( + val success: Boolean, + val message: String, + val data: LoginData? +) { + data class LoginData( + @SerializedName("UserId") + val userId: Int, + val user_nickname: String, + val token: String + ) +} + + + diff --git a/app/src/main/java/com/example/newsecondassignment/response/ResponseSignUpData.kt b/app/src/main/java/com/example/newsecondassignment/response/ResponseSignUpData.kt new file mode 100644 index 0000000..7c27f71 --- /dev/null +++ b/app/src/main/java/com/example/newsecondassignment/response/ResponseSignUpData.kt @@ -0,0 +1,15 @@ +package com.example.newsecondassignment.response + +import com.google.gson.annotations.SerializedName + +data class ResponseSignUpData( + val success: Boolean, + val message: String, + val data: SignUpData? +) { + data class SignUpData( + val user_nickname: String + ) +} + + diff --git a/app/src/main/java/com/example/newsecondassignment/utils/RetrofitEnqueueUtil.kt b/app/src/main/java/com/example/newsecondassignment/utils/RetrofitEnqueueUtil.kt new file mode 100644 index 0000000..4bb211d --- /dev/null +++ b/app/src/main/java/com/example/newsecondassignment/utils/RetrofitEnqueueUtil.kt @@ -0,0 +1,25 @@ +package com.example.newsecondassignment.utils + +import android.util.Log +import retrofit2.Call +import retrofit2.Callback +import retrofit2.Response + +fun Call.enqueueUtil( + onSuccess: (ResponseType) -> Unit, + onError: ((stateCode: Int) -> Unit)? = null +) { + this.enqueue(object : Callback { + override fun onResponse(call: Call, response: Response) { + if (response.isSuccessful) { + onSuccess.invoke(response.body() ?: return) + } else { + onError?.invoke(response.code()) + } + } + + override fun onFailure(call: Call, t: Throwable) { + Log.d("NetworkTest", "error:$t") + } + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/example/newsecondassignment/utils/ToastUtil.kt b/app/src/main/java/com/example/newsecondassignment/utils/ToastUtil.kt new file mode 100644 index 0000000..b61a4e6 --- /dev/null +++ b/app/src/main/java/com/example/newsecondassignment/utils/ToastUtil.kt @@ -0,0 +1,9 @@ +package com.example.newsecondassignment.utils + +import android.content.Context +import android.widget.Toast + +fun Context.showToast(msg: String) { + Toast.makeText(this, msg, Toast.LENGTH_SHORT) + .show() +} \ No newline at end of file diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/button_background.xml b/app/src/main/res/drawable/button_background.xml new file mode 100644 index 0000000..374592c --- /dev/null +++ b/app/src/main/res/drawable/button_background.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/soda.jpg b/app/src/main/res/drawable/soda.jpg new file mode 100644 index 0000000..1a8cb5c Binary files /dev/null and b/app/src/main/res/drawable/soda.jpg differ diff --git a/app/src/main/res/layout/activity_home.xml b/app/src/main/res/layout/activity_home.xml new file mode 100644 index 0000000..4138402 --- /dev/null +++ b/app/src/main/res/layout/activity_home.xml @@ -0,0 +1,146 @@ + + + + + + + + + + + + + + + + + + + + + +