From c5f48f687ceb3c7e2b1e510f84295f1326231d6b Mon Sep 17 00:00:00 2001 From: JooYae Date: Mon, 17 May 2021 16:24:07 +0900 Subject: [PATCH 1/4] feat: Fourth assignment --- app/.gitignore | 1 + app/build.gradle | 63 +++++ app/proguard-rules.pro | 21 ++ .../ExampleInstrumentedTest.kt | 24 ++ app/src/main/AndroidManifest.xml | 32 +++ .../FollowingListAdapter.kt | 47 +++ .../FollowingListFragment.kt | 76 +++++ .../newsecondassignment/FollowingUserInfo.kt | 6 + .../newsecondassignment/HomeActivity.kt | 77 +++++ .../example/newsecondassignment/RepoInfo.kt | 7 + .../newsecondassignment/RepoInfoActivity.kt | 23 ++ .../newsecondassignment/RepoListAdapter.kt | 38 +++ .../newsecondassignment/RepoListFragment.kt | 89 ++++++ .../newsecondassignment/SignInActivity.kt | 143 ++++++++++ .../newsecondassignment/SignUpActivity.kt | 113 ++++++++ .../newsecondassignment/UserInfoActivity.kt | 28 ++ .../newsecondassignment/api/ServiceCreator.kt | 15 + .../newsecondassignment/api/SoptService.kt | 24 ++ .../request/RequestLoginData.kt | 14 + .../request/RequestSignUpData.kt | 12 + .../response/ResponseLoginData.kt | 19 ++ .../response/ResponseSignUpData.kt | 15 + .../drawable-v24/ic_launcher_foreground.xml | 30 ++ .../res/drawable/ic_launcher_background.xml | 170 +++++++++++ app/src/main/res/layout/activity_home.xml | 85 ++++++ .../main/res/layout/activity_repo_info.xml | 16 ++ app/src/main/res/layout/activity_sign_in.xml | 105 +++++++ app/src/main/res/layout/activity_sign_up.xml | 267 ++++++++++++++++++ .../main/res/layout/activity_user_info.xml | 14 + .../res/layout/fragment_following_list.xml | 39 +++ .../main/res/layout/fragment_repo_list.xml | 18 ++ app/src/main/res/layout/item_follow_user.xml | 31 ++ app/src/main/res/layout/item_repo.xml | 45 +++ .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 + app/src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 3593 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 5339 bytes app/src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2636 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 3388 bytes app/src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 4926 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 7472 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 7909 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 11873 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 10652 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 16570 bytes app/src/main/res/values-night/themes.xml | 16 ++ app/src/main/res/values/colors.xml | 10 + app/src/main/res/values/strings.xml | 5 + app/src/main/res/values/themes.xml | 16 ++ .../newsecondassignment/ExampleUnitTest.kt | 17 ++ 50 files changed, 1781 insertions(+) create mode 100644 app/.gitignore create mode 100644 app/build.gradle create mode 100644 app/proguard-rules.pro create mode 100644 app/src/androidTest/java/com/example/newsecondassignment/ExampleInstrumentedTest.kt create mode 100644 app/src/main/AndroidManifest.xml create mode 100644 app/src/main/java/com/example/newsecondassignment/FollowingListAdapter.kt create mode 100644 app/src/main/java/com/example/newsecondassignment/FollowingListFragment.kt create mode 100644 app/src/main/java/com/example/newsecondassignment/FollowingUserInfo.kt create mode 100644 app/src/main/java/com/example/newsecondassignment/HomeActivity.kt create mode 100644 app/src/main/java/com/example/newsecondassignment/RepoInfo.kt create mode 100644 app/src/main/java/com/example/newsecondassignment/RepoInfoActivity.kt create mode 100644 app/src/main/java/com/example/newsecondassignment/RepoListAdapter.kt create mode 100644 app/src/main/java/com/example/newsecondassignment/RepoListFragment.kt create mode 100644 app/src/main/java/com/example/newsecondassignment/SignInActivity.kt create mode 100644 app/src/main/java/com/example/newsecondassignment/SignUpActivity.kt create mode 100644 app/src/main/java/com/example/newsecondassignment/UserInfoActivity.kt create mode 100644 app/src/main/java/com/example/newsecondassignment/api/ServiceCreator.kt create mode 100644 app/src/main/java/com/example/newsecondassignment/api/SoptService.kt create mode 100644 app/src/main/java/com/example/newsecondassignment/request/RequestLoginData.kt create mode 100644 app/src/main/java/com/example/newsecondassignment/request/RequestSignUpData.kt create mode 100644 app/src/main/java/com/example/newsecondassignment/response/ResponseLoginData.kt create mode 100644 app/src/main/java/com/example/newsecondassignment/response/ResponseSignUpData.kt create mode 100644 app/src/main/res/drawable-v24/ic_launcher_foreground.xml create mode 100644 app/src/main/res/drawable/ic_launcher_background.xml create mode 100644 app/src/main/res/layout/activity_home.xml create mode 100644 app/src/main/res/layout/activity_repo_info.xml create mode 100644 app/src/main/res/layout/activity_sign_in.xml create mode 100644 app/src/main/res/layout/activity_sign_up.xml create mode 100644 app/src/main/res/layout/activity_user_info.xml create mode 100644 app/src/main/res/layout/fragment_following_list.xml create mode 100644 app/src/main/res/layout/fragment_repo_list.xml create mode 100644 app/src/main/res/layout/item_follow_user.xml create mode 100644 app/src/main/res/layout/item_repo.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/values-night/themes.xml create mode 100644 app/src/main/res/values/colors.xml create mode 100644 app/src/main/res/values/strings.xml create mode 100644 app/src/main/res/values/themes.xml create mode 100644 app/src/test/java/com/example/newsecondassignment/ExampleUnitTest.kt 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..576d64d --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,63 @@ +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' +} \ 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..492ec56 --- /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/FollowingListAdapter.kt b/app/src/main/java/com/example/newsecondassignment/FollowingListAdapter.kt new file mode 100644 index 0000000..99eceda --- /dev/null +++ b/app/src/main/java/com/example/newsecondassignment/FollowingListAdapter.kt @@ -0,0 +1,47 @@ +package com.example.newsecondassignment + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +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/FollowingListFragment.kt b/app/src/main/java/com/example/newsecondassignment/FollowingListFragment.kt new file mode 100644 index 0000000..ddad8e0 --- /dev/null +++ b/app/src/main/java/com/example/newsecondassignment/FollowingListFragment.kt @@ -0,0 +1,76 @@ +package com.example.newsecondassignment + +import android.os.Binder +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.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/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/HomeActivity.kt b/app/src/main/java/com/example/newsecondassignment/HomeActivity.kt new file mode 100644 index 0000000..e35a21f --- /dev/null +++ b/app/src/main/java/com/example/newsecondassignment/HomeActivity.kt @@ -0,0 +1,77 @@ +package com.example.newsecondassignment + +import android.content.Intent +import androidx.appcompat.app.AppCompatActivity +import android.os.Bundle +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() + + val repoListAdapter = RepoListAdapter() + + binding.recyclerviewRepo.adapter = repoListAdapter + + repoListAdapter.repoList.addAll( + listOf( + RepoInfo( + userRepoName = "이름이 너무 길다이름이 너무 길다이름이 너무 길다이름이 너무 길다이름이 너무 길다이름이 너무 길다이름이 너무 길다", + userRepoDescription = "awesomeawesomeawesomeawesomeawesomeawesomeawesomeawesomeawesomeawesomeawesomeawesomeawesomeawesome", + userRepoLanguage = "kt" + ), + RepoInfo( + userRepoName = "qqqqq", + userRepoDescription = "awesomeawesomeawesomeawesomeawesomeawesomeawesomeawesomeawesomeawesomeawesomeawesomeawesome", + userRepoLanguage = "kt" + ), + RepoInfo( + userRepoName = "qqqqq", + userRepoDescription = "awesome", + userRepoLanguage = "kt" + ), + RepoInfo( + userRepoName = "qqqqq", + userRepoDescription = "awesomeawesomeawesomeawesomeawesomeawesomeawesomeawesomeawesomeawesome", + 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() + } + + 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/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/RepoInfoActivity.kt b/app/src/main/java/com/example/newsecondassignment/RepoInfoActivity.kt new file mode 100644 index 0000000..da1d86e --- /dev/null +++ b/app/src/main/java/com/example/newsecondassignment/RepoInfoActivity.kt @@ -0,0 +1,23 @@ +package com.example.newsecondassignment + +import androidx.appcompat.app.AppCompatActivity +import android.os.Bundle +import androidx.fragment.app.add +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/RepoListAdapter.kt b/app/src/main/java/com/example/newsecondassignment/RepoListAdapter.kt new file mode 100644 index 0000000..fbafd5f --- /dev/null +++ b/app/src/main/java/com/example/newsecondassignment/RepoListAdapter.kt @@ -0,0 +1,38 @@ +package com.example.newsecondassignment + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +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/RepoListFragment.kt b/app/src/main/java/com/example/newsecondassignment/RepoListFragment.kt new file mode 100644 index 0000000..03f773a --- /dev/null +++ b/app/src/main/java/com/example/newsecondassignment/RepoListFragment.kt @@ -0,0 +1,89 @@ +package com.example.newsecondassignment + +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.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/SignInActivity.kt b/app/src/main/java/com/example/newsecondassignment/SignInActivity.kt new file mode 100644 index 0000000..a7420c2 --- /dev/null +++ b/app/src/main/java/com/example/newsecondassignment/SignInActivity.kt @@ -0,0 +1,143 @@ +package com.example.newsecondassignment + +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.api.ServiceCreator +import com.example.newsecondassignment.databinding.ActivitySignInBinding +import com.example.newsecondassignment.request.RequestLoginData +import com.example.newsecondassignment.response.ResponseLoginData +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) + + signInButtonClickEvent() + signUpButtonStartClickEvent() + } + + private fun signInButtonClickEvent() { + binding.buttonSignIn.setOnClickListener { + var signInID = binding.editTextSignInIdInput.text + var signInPassword = binding.editTextSignInPasswordInput.text + + if (signInID.isNullOrBlank() || signInPassword.isNullOrBlank()) { + Toast.makeText( + this@SignInActivity, + "아이디/비밀번호를 확인해주세요!", + Toast.LENGTH_SHORT + ).show() + } else { + inputLoginInformation() + } + } + + } + + // 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 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") + } + }) + } + + // 홈 화면으로 넘어감 + 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/SignUpActivity.kt b/app/src/main/java/com/example/newsecondassignment/SignUpActivity.kt new file mode 100644 index 0000000..eb8ee64 --- /dev/null +++ b/app/src/main/java/com/example/newsecondassignment/SignUpActivity.kt @@ -0,0 +1,113 @@ +package com.example.newsecondassignment + +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/UserInfoActivity.kt b/app/src/main/java/com/example/newsecondassignment/UserInfoActivity.kt new file mode 100644 index 0000000..22e4651 --- /dev/null +++ b/app/src/main/java/com/example/newsecondassignment/UserInfoActivity.kt @@ -0,0 +1,28 @@ +package com.example.newsecondassignment + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import androidx.fragment.app.add +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/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/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/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/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/layout/activity_home.xml b/app/src/main/res/layout/activity_home.xml new file mode 100644 index 0000000..5597257 --- /dev/null +++ b/app/src/main/res/layout/activity_home.xml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + +