diff --git a/README.md b/README.md
index cc0acc5a..9947da23 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,56 @@
# android-contacts
+
+- 카카오 테크 캠퍼스 과제(연락처 앱) 수행을 위한 저장소입니다.
+
+## *Contents*
+1. 홈 화면 2. 연락처 추가 화면 3. 취소 / 뒤로가기 팝업
+
+

+
+4. 연락처 목록 화면 5. 연락처 목록 화면(회전)
+
+
+
+6. 상세화면1 7. 상세 화면2
+
+
+
+## feature
+
+### 1단계 - 연락처 추가
+
+1. 이름과 전화번호 입력 기능
+ - 이름과 전화번호는 필수 값으로 입력하지 않은 경우 토스트 메시지 보여준다.
+
+2. 전화번호 입력 기능
+ - 숫자만 입력 가능
+
+3. 더보기 기능
+ - 더보기를 눌러 입력 폼을 확장할 수 있다.
+ - 추가되는 입력 폼 : 생일, 성별, 메모
+
+4. 성별 입력 기능
+ - 성별을 둘 중 하나를 선택할 수 있다.
+
+5. 저장 기능
+ - 저장 버튼을 누르면 '저장이 완료 되었습니다' 라는 토스트 메시지를 보여준다.
+
+6. 취소 기능
+ - 취소 버튼을 누르면 '취소 되었습니다' 라는 토스트 메시지를 보여준다.
+
+7. 생일 입력 기능
+ - 생일을 달력에서 선택해서 입력할 수 있다.
+
+
+### 2단계 - 연락처 목록
+
+1. 연락처 등록 화면
+ - 연락처 추가 버튼을 누르면 연락처 추가 화면으로 넘어감
+
+2. 연락처를 저장하면 목록에 추가
+ - 앱을 다시 실행하면 목록은 비어있음
+ - 저장된 연락처가 많을 경우 목록 스크롤 가능
+
+3. 연락처 목록의 연락처를 선택하면 상세 화면 출력
+
+4. 연락처 작성 중 뒤로가기 / 취소 버튼을 누르면 확인 팝업 출력
\ No newline at end of file
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index c5add08f..77d242ff 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -1,6 +1,8 @@
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
+ id("kotlin-parcelize")
+ id("org.jlleitschuh.gradle.ktlint")
}
android {
@@ -41,6 +43,7 @@ dependencies {
implementation("androidx.appcompat:appcompat:1.6.1")
implementation("com.google.android.material:material:1.11.0")
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
+ implementation("androidx.activity:activity:1.8.0")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 89dc9d8b..96dc0f90 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -13,7 +13,10 @@
android:theme="@style/Theme.Contacts"
tools:targetApi="31">
+
@@ -21,6 +24,9 @@
+
-
+
\ No newline at end of file
diff --git a/app/src/main/java/campus/tech/kakao/contacts/Contact.kt b/app/src/main/java/campus/tech/kakao/contacts/Contact.kt
new file mode 100644
index 00000000..bdf33ab6
--- /dev/null
+++ b/app/src/main/java/campus/tech/kakao/contacts/Contact.kt
@@ -0,0 +1,14 @@
+package campus.tech.kakao.contacts
+
+import android.os.Parcelable
+import kotlinx.parcelize.Parcelize
+
+@Parcelize
+data class Contact(
+ val name: String,
+ val phoneNum: String,
+ val email: String,
+ val birthday: String,
+ val gender: Gender? = null,
+ val memo: String,
+) : Parcelable
diff --git a/app/src/main/java/campus/tech/kakao/contacts/DetailActivity.kt b/app/src/main/java/campus/tech/kakao/contacts/DetailActivity.kt
new file mode 100644
index 00000000..e0773296
--- /dev/null
+++ b/app/src/main/java/campus/tech/kakao/contacts/DetailActivity.kt
@@ -0,0 +1,107 @@
+package campus.tech.kakao.contacts
+
+import android.os.Build
+import android.os.Bundle
+import android.widget.TextView
+import androidx.appcompat.app.AppCompatActivity
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.core.view.isVisible
+
+class DetailActivity : AppCompatActivity() {
+ private lateinit var detailNameTextView: TextView
+ private lateinit var detailPhoneNumTextView: TextView
+ private lateinit var detailEmailTextView: TextView
+ private lateinit var detailBirthdayTextView: TextView
+ private lateinit var detailGenderTextView: TextView
+ private lateinit var detailMemoTextView: TextView
+ private lateinit var detailEmailLayout: ConstraintLayout
+ private lateinit var detailBirthdayLayout: ConstraintLayout
+ private lateinit var detailGenderLayout: ConstraintLayout
+ private lateinit var detailMemoLayout: ConstraintLayout
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_detail)
+
+ initViews()
+ setContactDetails()
+ }
+
+ /**
+ * 사용할 view들을 초기화하는 함수
+ *
+ * - `detailNameTextView` : 이름을 나타내는 TextView
+ * - `detailPhoneNumTextView` : 전화번호를 나타내는 TextView
+ * - `detailEmailTextView` : 메일을 나타내는 TextView
+ * - `detailBirthdayTextView` : 생일을 나타내는 TextView
+ * - `detailGenderTextView` : 성별을 나타내는 TextView
+ * - `detailMemoTextView` : 메모를 나타내는 TextView
+ * - `detailEmailLayout` : 메일 ConstraintLayout
+ * - `detailBirthdayLayout` : 생일 ConstraintLayout
+ * - `detailGenderLayout` : 성별 ConstraintLayout
+ * - `detailMemoLayout` : 메모 ConstraintLayout
+ */
+ private fun initViews() {
+ detailNameTextView = findViewById(R.id.detail_name_text_view)
+ detailPhoneNumTextView = findViewById(R.id.detail_phone_num_text_view)
+ detailEmailTextView = findViewById(R.id.detail_email_text_view)
+ detailBirthdayTextView = findViewById(R.id.detail_birthday_text_view)
+ detailGenderTextView = findViewById(R.id.detail_gender_text_view)
+ detailMemoTextView = findViewById(R.id.detail_memo_text_view)
+
+ detailEmailLayout = findViewById(R.id.detail_email_layout)
+ detailBirthdayLayout = findViewById(R.id.detail_birthday_layout)
+ detailGenderLayout = findViewById(R.id.detail_gender_layout)
+ detailMemoLayout = findViewById(R.id.detail_memo_layout)
+ }
+
+ /**
+ * 전달된 contact 정보를 view와 layout에 설정하는 함수.
+ */
+ private fun setContactDetails() {
+ val contact: Contact? =
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ intent.getParcelableExtra("contact", Contact::class.java)
+ } else {
+ intent.getParcelableExtra("contact")
+ }
+
+ contact?.let {
+ setDetailTextViews(it)
+ setDetailLayoutVisibility(it)
+ }
+ }
+
+ /**
+ * contact 정보로 TextView의 text를 설정하는 함수.
+ *
+ * @param contact RegisterActivity로 부터 받은 Contact 객체.
+ */
+ private fun setDetailTextViews(contact: Contact) {
+ detailNameTextView.text = contact.name
+ detailPhoneNumTextView.text = contact.phoneNum
+ detailEmailTextView.text = contact.email ?: ""
+ detailBirthdayTextView.text = contact.birthday ?: ""
+ detailGenderTextView.text =
+ when (contact.gender) {
+ Gender.FEMALE -> "여성"
+ Gender.MALE -> "남성"
+ else -> ""
+ }
+ detailMemoTextView.text = contact.memo ?: ""
+ }
+
+ /**
+ * contact 정보로 layout의 visibility를 설정하는 함수.
+ *
+ * 빈 정보는 보이지 않도록 설정.
+ *
+ * @param contact RegisterActivity로 부터 받은 Contact 객체.
+ */
+ private fun setDetailLayoutVisibility(contact: Contact) {
+ detailEmailLayout.isVisible = contact.email.isNotEmpty()
+ detailBirthdayLayout.isVisible = contact.birthday.isNotEmpty()
+ detailGenderLayout.isVisible = contact.gender != null
+ detailMemoLayout.isVisible = contact.memo.isNotEmpty()
+ }
+}
diff --git a/app/src/main/java/campus/tech/kakao/contacts/Gender.kt b/app/src/main/java/campus/tech/kakao/contacts/Gender.kt
new file mode 100644
index 00000000..878598e6
--- /dev/null
+++ b/app/src/main/java/campus/tech/kakao/contacts/Gender.kt
@@ -0,0 +1,6 @@
+package campus.tech.kakao.contacts
+
+enum class Gender {
+ MALE,
+ FEMALE,
+}
diff --git a/app/src/main/java/campus/tech/kakao/contacts/MainActivity.kt b/app/src/main/java/campus/tech/kakao/contacts/MainActivity.kt
index 7aae79fe..a0e2a26a 100644
--- a/app/src/main/java/campus/tech/kakao/contacts/MainActivity.kt
+++ b/app/src/main/java/campus/tech/kakao/contacts/MainActivity.kt
@@ -1,11 +1,220 @@
package campus.tech.kakao.contacts
+import android.app.DatePickerDialog
+import android.icu.util.Calendar
import android.os.Bundle
+import android.view.View
+import android.widget.Button
+import android.widget.EditText
+import android.widget.RadioGroup
+import android.widget.TextView
+import android.widget.Toast
+import androidx.activity.OnBackPressedCallback
+import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
+import androidx.appcompat.widget.LinearLayoutCompat
+import androidx.constraintlayout.widget.ConstraintLayout
class MainActivity : AppCompatActivity() {
+ private lateinit var nameEditText: EditText
+ private lateinit var phoneNumEditText: EditText
+ private lateinit var emailEditText: EditText
+ private lateinit var birthdayTextView: TextView
+ private lateinit var genderLayout: ConstraintLayout
+ private lateinit var memoEditText: EditText
+ private lateinit var cancelBtn: Button
+ private lateinit var saveBtn: Button
+ private lateinit var seeMoreLayout: LinearLayoutCompat
+ private lateinit var genderRadioGroup: RadioGroup
+ private var gender: Gender? = null
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
+
+ initViews()
+ setOnClickListeners()
+ registerOnBackPressedCallback()
+ }
+
+ /**
+ * 사용할 view들을 초기화하는 함수.
+ * - `nameEditText` : 이름 입력폼을 나타내는 EditText
+ * - `phoneNumEditText` : 전화번호 입력폼을 나타내는 EditText
+ * - `emailEditText` : 이메일 입력폼을 나타내는 EditText
+ * - `birthdayTextView` : 생일 입력폼을 나타내는 TextView
+ * - `genderLayout` : 성별 입력폼을 나타내는 ConstraintLayout
+ * - `memoEditText` : 메모 력폼을 나타내는 EditText
+ * - `cancelBtn` : 취소 버튼을 나타내는 Button
+ * - `saveBtn` : 저장 버튼을 나타내는 Button
+ * - `seeMoreLayout` : 더보기를 나타내는 LinearLayoutCompat
+ * - `genderRadioGroup` : 성별 선택을 위한 RadioGroup
+ */
+ private fun initViews() {
+ nameEditText = findViewById(R.id.name_edit_text)
+ phoneNumEditText = findViewById(R.id.phone_num_edit_text)
+ emailEditText = findViewById(R.id.email_edit_text)
+ birthdayTextView = findViewById(R.id.birthday_text_view)
+ genderLayout = findViewById(R.id.gender_layout)
+ memoEditText = findViewById(R.id.memo_edit_text)
+ cancelBtn = findViewById(R.id.cancel_btn)
+ saveBtn = findViewById(R.id.save_btn)
+ seeMoreLayout = findViewById(R.id.see_more_layout)
+ genderRadioGroup = findViewById(R.id.gender_radio_group)
+ }
+
+ /**
+ * 사용할 클릭 리스너들을 설정하는 함수
+ */
+ private fun setOnClickListeners() {
+ setOnClickListenerOfSaveBtn()
+ setOnClickListenerOfCancelBtn()
+ setOnClickListenerOfSeeMoreLayout()
+ setOnClickListenerOfGenderRadioGroup()
+ setOnClickListenerOfBirthdayEditText()
+ }
+
+ /**
+ * 취소 버튼에 대한 클릭 리스너를 설정하는 함수.
+ *
+ * 취소 버튼을 누르면 확인 팝업 보여줌.
+ */
+ private fun setOnClickListenerOfCancelBtn() {
+ cancelBtn.setOnClickListener {
+ showAlertDialog()
+ }
+ }
+
+ /**
+ * 저장 버튼에 대한 클릭 리스너를 설정하는 함수
+ *
+ * 이름 또는 전화번호 입력 폼이 비어있을 경우 Toast 메시지 출력.
+ * 아닌 경우 입력한 정보를 contact 객체로 담아 결과를 보내고 finish.
+ *
+ * - `contact` : 입력한 연락처 정보를 담고 있는 Contact 객체
+ * - `name` : 작성한 이름 String
+ * - `phoneNum` : 작성한 전화 번호 String
+ */
+ private fun setOnClickListenerOfSaveBtn() {
+ saveBtn.setOnClickListener {
+ val name = nameEditText.text.toString().trim()
+ val phoneNum = phoneNumEditText.text.toString().trim()
+
+ when {
+ name.isEmpty() -> {
+ Toast.makeText(this, "이름은 필수 값 입니다.", Toast.LENGTH_LONG).show()
+ }
+ phoneNum.isEmpty() -> {
+ Toast.makeText(this, "전화 번호는 필수 값 입니다.", Toast.LENGTH_LONG).show()
+ }
+ !phoneNum.matches(Regex("^\\d{10,11}\$")) -> {
+ Toast.makeText(this, "전화 번호는 10자리 또는 11자리의 숫자만 입력 가능합니다.", Toast.LENGTH_LONG).show()
+ }
+ else -> {
+ val contact = createContact()
+ intent.putExtra("CONTACT_RESULT", contact)
+ setResult(RESULT_OK, intent)
+ finish()
+ }
+ }
+ }
+ }
+
+ /**
+ * Contact 객체를 만드는 함수
+ *
+ * @return 입력 폼의 정보를 바탕으로 한 Contact 객체
+ */
+ private fun createContact(): Contact {
+ return Contact(
+ nameEditText.text.toString(),
+ phoneNumEditText.text.toString(),
+ emailEditText.text.toString(),
+ birthdayTextView.text.toString(),
+ gender,
+ memoEditText.text.toString(),
+ )
+ }
+
+ /**
+ * 더보기 layout에 대한 클릭 리스너를 설정하는 함수
+ *
+ * layout을 누르면 더보기 layout은 감추고 추가 입력 폼을 보이게 함.
+ */
+ private fun setOnClickListenerOfSeeMoreLayout() {
+ seeMoreLayout.setOnClickListener {
+ seeMoreLayout.visibility = View.GONE
+ birthdayTextView.visibility = View.VISIBLE
+ genderLayout.visibility = View.VISIBLE
+ memoEditText.visibility = View.VISIBLE
+ }
+ }
+
+ /**
+ * genderRadioGroup에 대한 클릭 리스너를 설정하는 함수
+ *
+ * 선택한 성별이 여성이면 1을, 남성이면 2를 gender에 저장.
+ */
+ private fun setOnClickListenerOfGenderRadioGroup() {
+ genderRadioGroup.setOnCheckedChangeListener { group, checkId ->
+ gender =
+ when (checkId) {
+ R.id.woman_radio_btn -> Gender.FEMALE
+ R.id.man_radio_btn -> Gender.MALE
+ else -> null
+ }
+ }
+ }
+
+ /**
+ * 생일 입력폼에 대한 클릭 리스너를 설정하는 함수
+ *
+ * 클릭 시 캘린더 dialog가 나오고 선택한 날짜가 입력폼에 입력됨.
+ */
+ private fun setOnClickListenerOfBirthdayEditText() {
+ birthdayTextView.setOnClickListener {
+ val calendar = Calendar.getInstance()
+ val year = calendar.get(Calendar.YEAR)
+ val month = calendar.get(Calendar.MONTH)
+ val day = calendar.get(Calendar.DAY_OF_MONTH)
+ val dateListener =
+ DatePickerDialog.OnDateSetListener { view, year, month, dayOfMonth ->
+ val formattedDate = "$year.$month.$dayOfMonth"
+ birthdayTextView.text = formattedDate
+ }
+ DatePickerDialog(this, dateListener, year, month, day).show()
+ }
+ }
+
+ /**
+ * 뒤로 가기 버튼을 누르면 확인 팝업을 보여주도록 설정하는 함수
+ *
+ */
+ private fun registerOnBackPressedCallback() {
+ onBackPressedDispatcher.addCallback(
+ this,
+ object : OnBackPressedCallback(true) {
+ override fun handleOnBackPressed() {
+ showAlertDialog()
+ }
+ },
+ )
+ }
+
+ /**
+ * 작성 내용 취소 / 뒤로가기 시 확인 팝업을 보여주는 함수.
+ *
+ * 나가기를 누르면 액티비티가 종료되고 작성하기를 누르면 다시 작성 가능함.
+ */
+ private fun showAlertDialog() {
+ AlertDialog.Builder(this).apply {
+ setMessage("작성 중인 내용이 있습니다. 정말 나가시겠습니까?")
+ setPositiveButton("나가기") { _, _ ->
+ finish()
+ }
+ setNegativeButton("작성하기", null)
+ create()
+ show()
+ }
}
}
diff --git a/app/src/main/java/campus/tech/kakao/contacts/RegisterActivity.kt b/app/src/main/java/campus/tech/kakao/contacts/RegisterActivity.kt
new file mode 100644
index 00000000..a00360a6
--- /dev/null
+++ b/app/src/main/java/campus/tech/kakao/contacts/RegisterActivity.kt
@@ -0,0 +1,218 @@
+package campus.tech.kakao.contacts
+
+import android.annotation.SuppressLint
+import android.content.Intent
+import android.os.Build
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import android.widget.TextView
+import androidx.activity.result.ActivityResultLauncher
+import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
+import androidx.appcompat.app.AppCompatActivity
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+
+class RegisterActivity : AppCompatActivity() {
+ lateinit var registerBtnLayout: FrameLayout
+ lateinit var contactRecyclerView: RecyclerView
+ lateinit var howToRegisterTextView: TextView
+ private lateinit var startActivityLauncher: ActivityResultLauncher
+ private var contactList = ArrayList()
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_register)
+
+ initViews()
+ setOnClickListeners()
+ setContactRecyclerView()
+ setStartActivityLauncher()
+ restoreInstanceState(savedInstanceState)
+ }
+
+ /**
+ * 사용할 view들을 초기화하는 함수
+ *
+ * - `registerBtnLayout` : 연락처 등록 버튼을 나타내는 FrameLayout
+ * - `contactRecyclerView` : 연락처 목록을 나타내는 RecyclerView
+ * - `howToRegisterTextView` : 연락처 등록 방법 메시지를 나타내는 TextView
+ */
+ private fun initViews() {
+ registerBtnLayout = findViewById(R.id.register_btn_layout)
+ contactRecyclerView = findViewById(R.id.contact_recycler_view)
+ howToRegisterTextView = findViewById(R.id.how_to_register_text_view)
+ }
+
+ /**
+ * 사용할 클릭 리스너들을 설정하는 함수
+ */
+ private fun setOnClickListeners() {
+ setOnClickListenerOfRegisterBtnLayout()
+ }
+
+ /**
+ * 연락처 추가 버튼에 대한 클릭 리스너를 설정하는 함수
+ *
+ * 클릭 시 MainActivity로 넘어가고 Contact 객체를 결과로 받는 것을 기다림.
+ */
+ private fun setOnClickListenerOfRegisterBtnLayout() {
+ registerBtnLayout.setOnClickListener {
+ val intent = Intent(this@RegisterActivity, MainActivity::class.java)
+ startActivityLauncher.launch(intent)
+ }
+ }
+
+ /**
+ * 연락처 리사이클러뷰를 설정하는 함수
+ *
+ * - `contactClickListener` : RecyclerView의 아이템이 클릭될 때 실행될 코드를 포함하는 OnContactClickListener 구현 객체
+ *
+ */
+ private fun setContactRecyclerView() {
+ val contactClickListener =
+ object : OnContactClickListener {
+ override fun onContactClicked(position: Int) {
+ val contact = contactList[position]
+ val intent =
+ Intent(this@RegisterActivity, DetailActivity::class.java).apply {
+ putExtra("contact", contact)
+ }
+ startActivity(intent)
+ }
+ }
+ contactRecyclerView.adapter =
+ ContactRecyclerViewAdapter(contactList, contactClickListener)
+ contactRecyclerView.layoutManager = LinearLayoutManager(this)
+ }
+
+ /**
+ * startActivityLauncher를 설정하는 함수
+ *
+ * result로 contact 객체를 받아와 list에 추가.
+ * 하나 이상의 contact 객체가 들어오면 등록 안내 textview의 visibility를 gone으로 설정.
+ */
+ @SuppressLint("NotifyDataSetChanged")
+ private fun setStartActivityLauncher() {
+ startActivityLauncher =
+ registerForActivityResult(StartActivityForResult()) { result ->
+ if (result.resultCode == RESULT_OK) {
+ val contact: Contact? =
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ result.data?.getParcelableExtra("CONTACT_RESULT", Contact::class.java)
+ } else {
+ result.data?.getParcelableExtra("CONTACT_RESULT")
+ }
+ contact?.let {
+ contactList.add(it)
+ if (howToRegisterTextView.visibility != View.GONE) {
+ howToRegisterTextView.visibility = View.GONE
+ }
+ contactRecyclerView.adapter?.notifyDataSetChanged()
+ }
+ }
+ }
+ }
+
+ interface OnContactClickListener {
+ fun onContactClicked(position: Int)
+ }
+
+ class ContactRecyclerViewAdapter(
+ private val contactList: ArrayList,
+ private val contactClickListener: OnContactClickListener,
+ ) : RecyclerView.Adapter() {
+ class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
+ private val lastNameTextView: TextView = itemView.findViewById(R.id.last_name_text_view)
+ private val nameTextView: TextView = itemView.findViewById(R.id.name_text_view)
+
+ fun bind(
+ contact: Contact,
+ clickListener: OnContactClickListener,
+ ) {
+ setLastNameText(contact.name[0].toString())
+ setNameText(contact.name)
+ itemView.setOnClickListener {
+ clickListener.onContactClicked(adapterPosition)
+ }
+ }
+
+ private fun setLastNameText(lastName: String) {
+ lastNameTextView.text = lastName
+ }
+
+ private fun setNameText(name: String) {
+ nameTextView.text = name
+ }
+ }
+
+ @Override
+ override fun onCreateViewHolder(
+ parent: ViewGroup,
+ viewType: Int,
+ ): ViewHolder {
+ val itemView =
+ LayoutInflater.from(parent.context)
+ .inflate(R.layout.contact_item, parent, false)
+ return ViewHolder(itemView)
+ }
+
+ @Override
+ override fun onBindViewHolder(
+ holder: ViewHolder,
+ position: Int,
+ ) {
+ val contact = contactList[position]
+ holder.bind(contact, contactClickListener)
+ }
+
+ @Override
+ override fun getItemCount(): Int {
+ return contactList.size
+ }
+ }
+
+ /**
+ * 화면 방향 전환 등으로 인해 Activity의 정보가 사라지지 않도록 저장하는 함수
+ *
+ * @param outState Activity의 현재 상태를 저장하는 Bundle 객체.
+ */
+ @Override
+ override fun onSaveInstanceState(outState: Bundle) {
+ super.onSaveInstanceState(outState)
+ outState.putParcelableArrayList("contact_list", ArrayList(contactList))
+ }
+
+ /**
+ * 화면 방향 전환 등으로 인해 Activity의 정보가 사라지지 않도록 복원하는 함수
+ *
+ * @param savedInstanceState onSaveInstanceState에서 저장된 데이터를 포함하는 Bundle 객체.
+ */
+ @Override
+ override fun onRestoreInstanceState(savedInstanceState: Bundle) {
+ super.onRestoreInstanceState(savedInstanceState)
+ contactList = savedInstanceState.getParcelableArrayList("contact_list")!!
+ }
+
+ companion object {
+ const val KEY_CONTACT_LIST = "contact_list"
+ }
+
+ /**
+ * 화면 방향 전환 등으로 인해 Activity의 정보가 사라지지 않도록 복원하는 함수
+ *
+ * @param savedInstanceState onSaveInstanceState에서 저장된 데이터를 포함하는 Bundle 객체.
+ */
+ @Override
+ private fun restoreInstanceState(savedInstanceState: Bundle?) {
+ if (savedInstanceState != null) {
+ contactList = savedInstanceState.getParcelableArrayList(KEY_CONTACT_LIST) ?: ArrayList()
+ setContactRecyclerView()
+ if (contactList.isNotEmpty()) {
+ howToRegisterTextView.visibility = View.GONE
+ }
+ }
+ }
+}
diff --git a/app/src/main/res/drawable/add.xml b/app/src/main/res/drawable/add.xml
new file mode 100644
index 00000000..9f83b8fb
--- /dev/null
+++ b/app/src/main/res/drawable/add.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/arrow_down.xml b/app/src/main/res/drawable/arrow_down.xml
new file mode 100644
index 00000000..1a69b23f
--- /dev/null
+++ b/app/src/main/res/drawable/arrow_down.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/base_profile_image.xml b/app/src/main/res/drawable/base_profile_image.xml
new file mode 100644
index 00000000..f4cc4181
--- /dev/null
+++ b/app/src/main/res/drawable/base_profile_image.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/circle_orange.xml b/app/src/main/res/drawable/circle_orange.xml
new file mode 100644
index 00000000..8b4e72cc
--- /dev/null
+++ b/app/src/main/res/drawable/circle_orange.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/round_box_add.xml b/app/src/main/res/drawable/round_box_add.xml
new file mode 100644
index 00000000..912b211d
--- /dev/null
+++ b/app/src/main/res/drawable/round_box_add.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/round_box_contract_item.xml b/app/src/main/res/drawable/round_box_contract_item.xml
new file mode 100644
index 00000000..e01cd52c
--- /dev/null
+++ b/app/src/main/res/drawable/round_box_contract_item.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_detail.xml b/app/src/main/res/layout/activity_detail.xml
new file mode 100644
index 00000000..bee10ef4
--- /dev/null
+++ b/app/src/main/res/layout/activity_detail.xml
@@ -0,0 +1,204 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index 24d17df2..0fc9eaef 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -7,13 +7,199 @@
android:layout_height="match_parent"
tools:context=".MainActivity">
-
+ app:layout_constraintTop_toTopOf="parent">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_register.xml b/app/src/main/res/layout/activity_register.xml
new file mode 100644
index 00000000..ece77b63
--- /dev/null
+++ b/app/src/main/res/layout/activity_register.xml
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/contact_item.xml b/app/src/main/res/layout/contact_item.xml
new file mode 100644
index 00000000..3bbecdde
--- /dev/null
+++ b/app/src/main/res/layout/contact_item.xml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index 768b058a..bd15f047 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -2,4 +2,9 @@
#FF000000
#FFFFFFFF
+ #E6F3F6
+ #EEC346
+ #E9A23C
+
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index a805abf4..3dfdb1ed 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1,3 +1,16 @@
Contacts
+ 이름
+ 전화번호
+ 취소
+ 저장
+ 생일
+ 메일
+ 성별
+ 메모
+ 더보기
+ 여성
+ 남성
+ 오른쪽 + 버튼을 클릭하여\n연락처를 등록해보세요.
+
\ No newline at end of file
diff --git a/img/cancelEvent.png b/img/cancelEvent.png
new file mode 100644
index 00000000..47bfa594
Binary files /dev/null and b/img/cancelEvent.png differ
diff --git a/img/contactList.png b/img/contactList.png
new file mode 100644
index 00000000..7549dd1c
Binary files /dev/null and b/img/contactList.png differ
diff --git a/img/detailActivity.png b/img/detailActivity.png
new file mode 100644
index 00000000..33f525d0
Binary files /dev/null and b/img/detailActivity.png differ
diff --git a/img/detailActivity2.png b/img/detailActivity2.png
new file mode 100644
index 00000000..6e77106c
Binary files /dev/null and b/img/detailActivity2.png differ
diff --git a/img/listRotation.png b/img/listRotation.png
new file mode 100644
index 00000000..85796859
Binary files /dev/null and b/img/listRotation.png differ
diff --git a/img/mainActivity.png b/img/mainActivity.png
new file mode 100644
index 00000000..2f6bb794
Binary files /dev/null and b/img/mainActivity.png differ
diff --git a/img/registerActivity.png b/img/registerActivity.png
new file mode 100644
index 00000000..f3289906
Binary files /dev/null and b/img/registerActivity.png differ