-
Notifications
You must be signed in to change notification settings - Fork 33
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
base: main
Are you sure you want to change the base?
Changes from all commits
9eb591b
40b5c8c
af2d16b
1414971
50b0457
507b044
ab05e17
7f4d3d1
8a13cd0
3a5a90a
4e402bd
c815037
d653dd5
1ac2e2a
ddb03d1
595a23c
a588470
30146cc
c0779a3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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` |
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() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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() | ||
} | ||
} |
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? | ||
) |
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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. SAM(Single Abstract Method)를 구현하셔서 사용하셨군요! |
||
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") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
} | ||
|
||
companion object { | ||
private const val ADD_CONTACT_REQUEST = 1 | ||
} | ||
} |
This file was deleted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
README.md를 잘 작성해주셨군요 🎉
하지만 코드리뷰 요청을 해주실땐 '변경사항' 단위로 리뷰를 하다보니
PR 본문에 어떤작업이고 어떤 내용이 있는지를 잘 설명 해주시면 리뷰어가 리뷰하기 더 편할거에요!