diff --git a/README.md b/README.md index cc0acc5a..0c5c61ce 100644 --- a/README.md +++ b/README.md @@ -1 +1,62 @@ # android-contacts + +### 간단한 연락처 애플리케이션 +## 개요 +이 프로젝트는 간단한 연락처 애플리케이션을 구현합니다. 사용자는 이름과 전화번호를 입력하여 연락처를 추가할 수 있으며, 생일, 성별, 메모 등의 추가 정보를 입력할 수 있습니다. + +--- +### STEP 1 +STEP 1에서는 연락처를 추가하는 간단한 UI를 작성합니다. + +## 기능 + +1. **UI 구성**: + - **이름** (필수): TextField + - **전화번호** (필수): TextField + - **이메일** (선택): TextField + - **더보기 버튼** (클릭 시 확장. 생일, 성별, 메모 폼 등장): Button + - **생일** (선택): DatePickerDialog + - **성별** (선택): Radio Button + - **메모** (선택): TextField + +## 사용 방법 + +1. **연락처 추가하기**: + - 애플리케이션 실행 후, 연락처 추가 버튼을 클릭합니다. + - 이름과 전화번호를 입력합니다. + - 필요 시 '더보기' 버튼을 눌러 생일, 성별, 메모를 입력합니다. + - 저장 버튼을 클릭하여 연락처를 저장합니다. 저장이 완료되면 토스트 메시지가 표시됩니다. + - 입력을 취소하려면 취소 버튼을 클릭합니다. 취소 토스트 메시지가 표시됩니다. + +--- + +### STEP 2 +STEP2에서는 간단한 연락처 앱의 주요 구성 요소를 구현합니다. STEP 1에서 제작했던 UI로부터 데이터를 입력받고 RecyclerView를 이용해 연락처 아이템을 생성합니다. + +## 기능 +- **연락처 등록 화면**: + - 사용자는 이름, 전화번호, 이메일을 입력하여 연락처를 등록할 수 있습니다. + - 선택적으로 생일, 성별, 메모를 추가할 수 있습니다. + - "저장" 버튼을 클릭하여 연락처를 저장하고, "취소" 버튼을 클릭하여 저장을 취소할 수 있습니다. + +- **연락처 목록**: + - 저장된 연락처는 리스트에 추가됩니다. 이 때 아이템이 많으면 __스크롤할 수 있어야 합니다__. + - 각 연락처 항목에는 이름과 전화번호가 표시됩니다. 사용자는 이 항목을 클릭하여 연락처의 상세 정보를 볼 수 있습니다. + +- **상세 화면**: + - 사용자는 연락처 목록에서 연락처를 선택하면 상세 정보를 볼 수 있습니다. + - 상세 정보에는 이름, 전화번호, 이메일, 생일, 성별, 메모 등이 표시됩니다. + +- **뒤로가기 확인**: + - 사용자가 연락처 작성 중에 뒤로가기 버튼을 누르면 작성을 취소할 지 확인합니다. + +- **연락처 초기화**: + - 앱을 다시 시작하면 저장된 연락처는 __초기화 되어야 합니다__. + +### 프로젝트 구조 + +프로젝트는 다음과 같은 구조로 이루어집니다: + +- **데이터 클래스**: `Contact.kt` +- **어댑터**: `ContactAdapter.kt` +- **UI**: `MainActivity.kt`, `AddContactActivity.kt`, `ContactDetailActivity.kt` \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 89dc9d8b..88ed19fb 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -13,14 +13,15 @@ android:theme="@style/Theme.Contacts" tools:targetApi="31"> - + + diff --git a/app/src/main/java/campus/tech/kakao/contacts/AddContactActivity.kt b/app/src/main/java/campus/tech/kakao/contacts/AddContactActivity.kt new file mode 100644 index 00000000..b9505519 --- /dev/null +++ b/app/src/main/java/campus/tech/kakao/contacts/AddContactActivity.kt @@ -0,0 +1,102 @@ +package campus.tech.kakao.contacts + +import android.app.DatePickerDialog +import android.content.Intent +import android.os.Bundle +import android.view.View +import android.widget.Button +import android.widget.EditText +import android.widget.LinearLayout +import android.widget.RadioGroup +import android.widget.Toast +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AppCompatActivity +import java.util.Calendar + +class AddContactActivity : AppCompatActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_add_contact) + + val nameField: EditText = findViewById(R.id.name_field) + val phoneField: EditText = findViewById(R.id.phone_field) + val emailField: EditText = findViewById(R.id.email_field) + val saveButton: Button = findViewById(R.id.save_button) + val cancelButton: Button = findViewById(R.id.cancel_button) + val moreButton: LinearLayout = findViewById(R.id.more_button) + val additionalFields: LinearLayout = findViewById(R.id.additional_fields) + val birthdayField: EditText = findViewById(R.id.birthday_field) + val genderGroup: RadioGroup = findViewById(R.id.gender_group) + val memoField: EditText = findViewById(R.id.memo_field) + + moreButton.setOnClickListener { + moreButton.visibility = View.GONE + additionalFields.visibility = View.VISIBLE + } + + saveButton.setOnClickListener { + val name = nameField.text.toString().trim() + val phone = phoneField.text.toString().trim() + val email = emailField.text.toString().trim() + val birthday = birthdayField.text.toString().trim() + val genderId = genderGroup.checkedRadioButtonId + val memo = memoField.text.toString().trim() + if (name.isNotEmpty() && phone.isNotEmpty()) { + val resultIntent = Intent() + resultIntent.putExtra("name", name) + resultIntent.putExtra("phone", phone) + resultIntent.putExtra("email", email) + resultIntent.putExtra("birthday", birthday) + resultIntent.putExtra("genderId", genderId) + resultIntent.putExtra("memo", memo) + setResult(RESULT_OK, resultIntent) + finish() + } else { + Toast.makeText( + this@AddContactActivity, getString(R.string.toast_name_and_phone_is_essential), + Toast.LENGTH_SHORT + ).show() + } + } + + cancelButton.setOnClickListener { + showExitConfirmationDialog() + } + + birthdayField.setOnClickListener { + showDatePickerDialog() + } + } + + override fun onBackPressed() { + showExitConfirmationDialog() + } + + private fun showExitConfirmationDialog() { + AlertDialog.Builder(this) + .setMessage(getString(R.string.dialog_confirm_exit)) + .setPositiveButton(getString(R.string.confirm_label)) { _, _ -> + finish() + } + .setNegativeButton(getString(R.string.cancel_label)) { dialog, _ -> + dialog.dismiss() + } + .create() + .show() + } + + private fun showDatePickerDialog() { + 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 datePickerDialog = DatePickerDialog(this, { _, selectedYear, selectedMonth, selectedDay -> + val selectedDate = "$selectedYear-${selectedMonth + 1}-$selectedDay" + findViewById(R.id.birthday_field).setText(selectedDate) + }, year, month, day) + + datePickerDialog.show() + } +} 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..3ac1237b --- /dev/null +++ b/app/src/main/java/campus/tech/kakao/contacts/Contact.kt @@ -0,0 +1,10 @@ +package campus.tech.kakao.contacts + +data class Contact( + val name: String, + val phone: String, + val email: String, + val birthday: String?, + val genderId: Int?, + val memo: String? +) \ No newline at end of file diff --git a/app/src/main/java/campus/tech/kakao/contacts/ContactAdapter.kt b/app/src/main/java/campus/tech/kakao/contacts/ContactAdapter.kt new file mode 100644 index 00000000..8bb78adf --- /dev/null +++ b/app/src/main/java/campus/tech/kakao/contacts/ContactAdapter.kt @@ -0,0 +1,39 @@ +package campus.tech.kakao.contacts + +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView + +class ContactAdapter(private val context: Context, private val contactList: List) : + RecyclerView.Adapter() { + + interface OnItemClickListener { + fun onItemClick(contact: Contact) + } + + var itemClickListener: OnItemClickListener? = null + + inner class ContactViewHolder(view: View) : RecyclerView.ViewHolder(view) { + val nameTextView: TextView = view.findViewById(R.id.contact_name) + val profileInitialTextView: TextView = view.findViewById(R.id.profile_initial) + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ContactViewHolder { + val view = LayoutInflater.from(parent.context).inflate(R.layout.contact_item, parent, false) + return ContactViewHolder(view) + } + + override fun onBindViewHolder(holder: ContactViewHolder, position: Int) { + val contact = contactList[position] + holder.nameTextView.text = contact.name + holder.profileInitialTextView.text = contact.name.substring(0, 1).toUpperCase() + holder.itemView.setOnClickListener { + itemClickListener?.onItemClick(contact) + } + } + + override fun getItemCount() = contactList.size +} diff --git a/app/src/main/java/campus/tech/kakao/contacts/ContactDetailActivity.kt b/app/src/main/java/campus/tech/kakao/contacts/ContactDetailActivity.kt new file mode 100644 index 00000000..126f01c5 --- /dev/null +++ b/app/src/main/java/campus/tech/kakao/contacts/ContactDetailActivity.kt @@ -0,0 +1,67 @@ +package campus.tech.kakao.contacts + +import android.app.Activity +import android.os.Bundle +import android.view.View +import android.widget.TextView + +class ContactDetailActivity : Activity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_contact_detail) + + val nameTextView = findViewById(R.id.contact_name_content) + val phoneTextView = findViewById(R.id.contact_phone_content) + val emailTextView = findViewById(R.id.contact_mail_content) + val birthdayTextView = findViewById(R.id.contact_birthday_content) + val genderTextView = findViewById(R.id.contact_gender_content) + val memoTextView = findViewById(R.id.contact_memo_content) + + val nameSection = findViewById(R.id.name_section) + val phoneSection = findViewById(R.id.phone_section) + val emailSection = findViewById(R.id.email_section) + val birthdaySection = findViewById(R.id.birthday_section) + val genderSection = findViewById(R.id.gender_section) + val memoSection = findViewById(R.id.memo_section) + + val intent = intent + val name = intent.getStringExtra("name") + val phone = intent.getStringExtra("phone") + val email = intent.getStringExtra("email") + val birthday = intent.getStringExtra("birthday") + val genderId = intent.getIntExtra("genderId", -1) + val memo = intent.getStringExtra("memo") + + if (!name.isNullOrEmpty()) { + nameSection.visibility = View.VISIBLE + nameTextView.text = name + } + + if (!phone.isNullOrEmpty()) { + phoneSection.visibility = View.VISIBLE + phoneTextView.text = phone + } + + if (!email.isNullOrEmpty()) { + emailSection.visibility = View.VISIBLE + emailTextView.text = email + } + + if (!birthday.isNullOrEmpty()) { + birthdaySection.visibility = View.VISIBLE + birthdayTextView.text = birthday + } + + if (genderId != -1) { + genderSection.visibility = View.VISIBLE + val genderText = if (genderId == R.id.gender_male) getString(R.string.gender_male) else getString(R.string.gender_female) + genderTextView.text = genderText + } + + if (!memo.isNullOrEmpty()) { + memoSection.visibility = View.VISIBLE + memoTextView.text = memo + } + } +} diff --git a/app/src/main/java/campus/tech/kakao/contacts/ContactListActivity.kt b/app/src/main/java/campus/tech/kakao/contacts/ContactListActivity.kt new file mode 100644 index 00000000..21f0fabd --- /dev/null +++ b/app/src/main/java/campus/tech/kakao/contacts/ContactListActivity.kt @@ -0,0 +1,76 @@ +package campus.tech.kakao.contacts + +import android.app.Activity +import android.content.Intent +import android.os.Bundle +import android.view.View +import android.widget.TextView +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.google.android.material.floatingactionbutton.FloatingActionButton + +class ContactListActivity : Activity() { + + private val contacts = mutableListOf() + private lateinit var contactAdapter: ContactAdapter + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_contact_list) + + contactAdapter = ContactAdapter(this, contacts) + val recyclerViewContacts = findViewById(R.id.recycler_view_contacts) + recyclerViewContacts.layoutManager = LinearLayoutManager(this) + recyclerViewContacts.adapter = contactAdapter + + contactAdapter.itemClickListener = object : ContactAdapter.OnItemClickListener { + override fun onItemClick(contact: Contact) { + val intent = Intent(this@ContactListActivity, ContactDetailActivity::class.java).apply { + putExtra("name", contact.name) + putExtra("phone", contact.phone) + putExtra("email", contact.email) + putExtra("birthday", contact.birthday) + putExtra("genderId", contact.genderId) + putExtra("memo", contact.memo) + } + startActivity(intent) + } + } + + val fabAddContact = findViewById(R.id.fab_add_contact) + fabAddContact.setOnClickListener { + val intent = Intent(this, AddContactActivity::class.java) + startActivityForResult(intent, ADD_CONTACT_REQUEST) + } + + checkEmptyState() + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + if (requestCode == ADD_CONTACT_REQUEST && resultCode == RESULT_OK) { + data?.let { + val name = it.getStringExtra("name") ?: return + val phone = it.getStringExtra("phone") ?: return + val email = it.getStringExtra("email") ?: "" + val birthday = it.getStringExtra("birthday") ?: "" + val genderId = it.getIntExtra("genderId", -1) + val memo = it.getStringExtra("memo") ?: "" + + val newContact = Contact(name, phone, email, birthday, genderId, memo) + contacts.add(newContact) + contactAdapter.notifyItemInserted(contacts.size - 1) + checkEmptyState() + } + } + } + + private fun checkEmptyState() { + val emptyText = findViewById(R.id.empty_text) + emptyText.visibility = if (contacts.isEmpty()) View.VISIBLE else View.GONE + } + + companion object { + private const val ADD_CONTACT_REQUEST = 1 + } +} diff --git a/app/src/main/java/campus/tech/kakao/contacts/MainActivity.kt b/app/src/main/java/campus/tech/kakao/contacts/MainActivity.kt deleted file mode 100644 index 7aae79fe..00000000 --- a/app/src/main/java/campus/tech/kakao/contacts/MainActivity.kt +++ /dev/null @@ -1,11 +0,0 @@ -package campus.tech.kakao.contacts - -import android.os.Bundle -import androidx.appcompat.app.AppCompatActivity - -class MainActivity : AppCompatActivity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.activity_main) - } -} 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..1f5a4bf9 --- /dev/null +++ b/app/src/main/res/drawable/arrow_down.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/circle_background.xml b/app/src/main/res/drawable/circle_background.xml new file mode 100644 index 00000000..467e068f --- /dev/null +++ b/app/src/main/res/drawable/circle_background.xml @@ -0,0 +1,7 @@ + + + + diff --git a/app/src/main/res/drawable/face.xml b/app/src/main/res/drawable/face.xml new file mode 100644 index 00000000..881617e6 --- /dev/null +++ b/app/src/main/res/drawable/face.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/activity_add_contact.xml b/app/src/main/res/layout/activity_add_contact.xml new file mode 100644 index 00000000..8a0f66e5 --- /dev/null +++ b/app/src/main/res/layout/activity_add_contact.xml @@ -0,0 +1,178 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +