Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[부산대 Android_김민혁] 1주차 미션 제출합니다. #16

Open
wants to merge 19 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
9eb591b
docs: Add initial README.md file
kyleidea1 Jun 27, 2024
40b5c8c
docs: Add basic layout skeleton and bottom cancel/save buttons
kyleidea1 Jun 27, 2024
af2d16b
feat: Add face.xml resource for logo and implement name input form
kyleidea1 Jun 27, 2024
1414971
feat: Add phone number and email input forms
kyleidea1 Jun 27, 2024
50b0457
refactor: Manage previously hardcoded strings in string resources
kyleidea1 Jun 27, 2024
507b044
feat: Add introduced email input form and 'More' button.
kyleidea1 Jun 27, 2024
ab05e17
feat: Introduce additional fields expanded by 'More' button and added…
kyleidea1 Jun 27, 2024
7f4d3d1
Implemented basic functionality in MainActivity, excluding additional…
kyleidea1 Jun 27, 2024
8a13cd0
feat: Implement birthday input form with DatePickerDialog in MainActi…
kyleidea1 Jun 27, 2024
3a5a90a
feat: Add gender selection and memo input forms in MainActivity
kyleidea1 Jun 27, 2024
4e402bd
feat: Log contact information on save button click in MainActivity
kyleidea1 Jun 27, 2024
c815037
Refactor: Rename MainActivity to AddContactActivity and added some lo…
kyleidea1 Jun 28, 2024
d653dd5
Update README.md for STEP 1 and add README.md for STEP 2
kyleidea1 Jun 28, 2024
1ac2e2a
feat: Add ContactActivity with RecyclerView for contact list
kyleidea1 Jun 28, 2024
ddb03d1
feat: Add ContactAdapter for ContactList
kyleidea1 Jun 28, 2024
595a23c
feat: Add Contact.kt, contact_item.xml, and circle_background.xml
kyleidea1 Jun 28, 2024
a588470
feat: Add ContactDetailActivity and activity_contact_detail for detai…
kyleidea1 Jun 28, 2024
30146cc
feat: Add batch string extraction functionality
kyleidea1 Jun 28, 2024
c0779a3
chore: modified message requires essential fields
kyleidea1 Jun 28, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,62 @@
# android-contacts

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

README.md를 잘 작성해주셨군요 🎉
하지만 코드리뷰 요청을 해주실땐 '변경사항' 단위로 리뷰를 하다보니
PR 본문에 어떤작업이고 어떤 내용이 있는지를 잘 설명 해주시면 리뷰어가 리뷰하기 더 편할거에요!

### 간단한 연락처 애플리케이션
## 개요
이 프로젝트는 간단한 연락처 애플리케이션을 구현합니다. 사용자는 이름과 전화번호를 입력하여 연락처를 추가할 수 있으며, 생일, 성별, 메모 등의 추가 정보를 입력할 수 있습니다.

---
### 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`
5 changes: 3 additions & 2 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,15 @@
android:theme="@style/Theme.Contacts"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:name=".ContactListActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".AddContactActivity" />
<activity android:name=".ContactDetailActivity" />
</application>

</manifest>
102 changes: 102 additions & 0 deletions app/src/main/java/campus/tech/kakao/contacts/AddContactActivity.kt
Original file line number Diff line number Diff line change
@@ -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() {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

onBackPressed 에 대해서 warning이 나오실거에요 한번 확인 해보시겠어요?

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"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

string template에 연산이 들어가있으니 결과물을 예측하기 어려워진거 같아요
연산이 안들어가게 할순 없을까요?

findViewById<EditText>(R.id.birthday_field).setText(selectedDate)
}, year, month, day)

datePickerDialog.show()
}
}
10 changes: 10 additions & 0 deletions app/src/main/java/campus/tech/kakao/contacts/Contact.kt
Original file line number Diff line number Diff line change
@@ -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?
)
39 changes: 39 additions & 0 deletions app/src/main/java/campus/tech/kakao/contacts/ContactAdapter.kt
Original file line number Diff line number Diff line change
@@ -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<Contact>) :
RecyclerView.Adapter<ContactAdapter.ContactViewHolder>() {

interface OnItemClickListener {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SAM(Single Abstract Method)를 구현하셔서 사용하셨군요!
이렇게써도 좋지만 kotlin에서는 람다식을 이용하면 좀 더 간단하게 구현할수도 있습니다.

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
}
Original file line number Diff line number Diff line change
@@ -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<TextView>(R.id.contact_name_content)
val phoneTextView = findViewById<TextView>(R.id.contact_phone_content)
val emailTextView = findViewById<TextView>(R.id.contact_mail_content)
val birthdayTextView = findViewById<TextView>(R.id.contact_birthday_content)
val genderTextView = findViewById<TextView>(R.id.contact_gender_content)
val memoTextView = findViewById<TextView>(R.id.contact_memo_content)

val nameSection = findViewById<View>(R.id.name_section)
val phoneSection = findViewById<View>(R.id.phone_section)
val emailSection = findViewById<View>(R.id.email_section)
val birthdaySection = findViewById<View>(R.id.birthday_section)
val genderSection = findViewById<View>(R.id.gender_section)
val memoSection = findViewById<View>(R.id.memo_section)

val intent = intent
val name = intent.getStringExtra("name")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

intent.getXXX()로 받는 key들이 여러곳에서 사용되고 있네요
상수로 빼내는건 어떨까요?

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
}
}
}
Original file line number Diff line number Diff line change
@@ -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<Contact>()
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<RecyclerView>(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<FloatingActionButton>(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<TextView>(R.id.empty_text)
emptyText.visibility = if (contacts.isEmpty()) View.VISIBLE else View.GONE
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isVisible 익스텐션을 이용하면 좀 더 간단하게 구현할수 있을거에요

}

companion object {
private const val ADD_CONTACT_REQUEST = 1
}
}
11 changes: 0 additions & 11 deletions app/src/main/java/campus/tech/kakao/contacts/MainActivity.kt

This file was deleted.

Loading