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주차 과제 #37

Open
wants to merge 24 commits into
base: jsh00325
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
62588b0
docs: Add Program Description & Feature
jsh00325 Jun 24, 2024
037366f
feat: Add Contact Input Layout
jsh00325 Jun 25, 2024
fd20c6d
feat: Add a Button for Input More Information
jsh00325 Jun 25, 2024
9cca3c5
feat: Add a Feature for Cancel Button
jsh00325 Jun 25, 2024
9946f38
feat: Add a Feature for Save Contact
jsh00325 Jun 26, 2024
7ea2ec6
docs: Update Step2 Program Features
jsh00325 Jun 28, 2024
763bf16
feat: Add Contact Item Layout
jsh00325 Jun 28, 2024
4cfbaa7
feat: Add Contact List Layout w. RecyclerView
jsh00325 Jun 28, 2024
7c70c0c
feat: Add Contact Info Layout
jsh00325 Jun 28, 2024
2eafb0f
feat: Create Adapter for ContactsList & Connect with RecyclerView
jsh00325 Jun 28, 2024
4e22289
feat: Connect With Register Activity using FloatingActionButton
jsh00325 Jun 28, 2024
e10a86e
feat: Add Cancle Button's Feature On Register Activity
jsh00325 Jun 28, 2024
c60d8dd
feat: Add AlertDialog When User Press Back Button
jsh00325 Jun 28, 2024
7066116
feat: Save Contact and Update RecyclerView with New Item
jsh00325 Jun 28, 2024
347235d
feat: Add Click Lister at ContactItem
jsh00325 Jun 28, 2024
0e2ad0a
feat: Fill Contact Info At ContactInfoActivity
jsh00325 Jun 28, 2024
39e966c
style: Reformat code
jsh00325 Jul 1, 2024
3b98422
refactor: Change When to If
jsh00325 Jul 1, 2024
7df2bd2
refactor: Add Constant Value
jsh00325 Jul 1, 2024
8637994
refactor: Add Constant Value 2
jsh00325 Jul 1, 2024
08a428a
refactor: Change Method Name
jsh00325 Jul 1, 2024
b45cc13
refactor: Change Linear Layout To Constraint Layout
jsh00325 Jul 2, 2024
2033f07
refactor: use isVisible instead of let
jsh00325 Jul 2, 2024
a4cecd4
refactor: Reduce Layout Depth
jsh00325 Jul 2, 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
46 changes: 46 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,47 @@
# android-contacts

## 📄 프로그램 설명

연락처를 관리하는 안드로이드 앱입니다.

간단하게 연락처를 추가하는 기능이 있습니다.

## 🎯 1단계(연락처 추가) 구현할 기능

- [X] 연락처와 이름, 전화번호를 입력 받는 레이아웃 제작

- [X] 전화번호는 숫자만 입력 가능하도록 구현한다.

- [X] 더보기 버튼을 통해 생일, 성별, 메모를 입력 받는 레이아웃 제작

- [X] 버튼을 누르면 입력 폼이 확장되도록 구현한다.

- [X] 성별은 라디오 버튼을 통해 남성, 여성을 선택할 수 있도록 구현한다.

- [X] 저장 버튼을 누르면 입력한 정보를 저장

- [X] 이름과 전화번호가 입력되지 않았을 때, 토스트 메시지를 띄워 경고한다.

- [X] 저장이 완료되면 '저장이 완료 되었습니다'라는 토스트 메시지를 띄워 저장되었음을 알린다.

- [X] 취소 버튼을 누르면 입력한 정보를 초기화

- [X] '취소 되었습니다'라는 토스트 메시지를 띄워 초기화 되었음을 알린다.

## 🎯 2단계(연락처 목록) 구현할 기능

- [X] RecyclerView를 활용하여 연락처 목록을 보여주는 레이아웃 제작

- [X] 연락처의 목록을 구성하는 item 레이아웃 제작

- [X] 연락처의 상세 정보를 확인하는 레이아웃 제작

- [X] 연락처 목록에서 연락처를 클릭하면 상세 정보를 확인할 수 있도록 구현

- [X] 추가하기 버튼 클릭 시 기존의 등록 페이지가 나타나는 기능 구현

- [X] 등록 페이지에서 취소 버튼 클릭 시 목록으로 돌아가는 기능 구현

- [X] 등록 페이지에서 저장 버튼 클릭 시 목록에 추가되는 기능 구현

- [X] 뒤로가기 버튼 클릭 시 확인 팝업 띄우기
3 changes: 2 additions & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ dependencies {
implementation("androidx.appcompat:appcompat:1.6.1")
implementation("com.google.android.material:material:1.11.0")
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
testImplementation("junit:junit:4.13.2")
implementation("androidx.activity:activity:1.8.0")
testImplementation("junit:junit:4.13.2")
JSpiner marked this conversation as resolved.
Show resolved Hide resolved
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
}
13 changes: 11 additions & 2 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,23 @@
android:theme="@style/Theme.Contacts"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:name=".ContactInfoActivity"
android:exported="true" />
<activity
android:name=".ContactsListActivity"
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=".ContactRegisterActivity"
android:exported="true" />
<activity
android:name=".MainActivity"
android:exported="true" />
</application>

</manifest>
</manifest>
12 changes: 12 additions & 0 deletions app/src/main/java/campus/tech/kakao/contacts/ContactData.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package campus.tech.kakao.contacts

import java.io.Serializable

data class ContactData (
val name: String,
val phone: String,
var email: String? = null,
var birthday: String? = null,
var isFemale: Boolean? = null,
var memo: String? = null
): Serializable
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package campus.tech.kakao.contacts

import android.os.Build
import android.os.Bundle
import android.view.View
import android.widget.LinearLayout
import android.widget.TextView
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat

class ContactInfoActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_contact_info)

when (Build.VERSION.SDK_INT) {
in 33..Int.MAX_VALUE ->
JSpiner marked this conversation as resolved.
Show resolved Hide resolved
intent.getSerializableExtra("contactData", ContactData::class.java)
JSpiner marked this conversation as resolved.
Show resolved Hide resolved
else ->
intent.getSerializableExtra("contactData") as ContactData?
}?.let {
findViewById<TextView>(R.id.contactName).text = it.name
findViewById<TextView>(R.id.contactPhone).text = it.phone

it.email?.let { email ->
findViewById<LinearLayout>(R.id.contact_mail_layout).visibility = View.VISIBLE
Copy link

Choose a reason for hiding this comment

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

인텐트간 정보를 주고 받을 때, null 확인을 위해서 let 구문을 사용하였는데, 코드에서 저의 사용 방법이 올바른 방법인지 확인해 주셨으면 감사하겠습니다.

이 부분을 말씀 해주신거 같은데 나쁘지 않은 방법으로 보입니다.
다만 요구사항이 더 복잡해져서 visibility가 여러군데에서 바뀐다면
it.email이 null일때 visibility=GONE 처리가 누락될 가능성도 있을거 같아요
항상 visibility 처리를 하도록 이렇게 바꿔보는 방법도 있을거 같습니다.

// it.email?.let { email ->  // let은 더이상 사용하지 않습니다.
findViewById<LinearLayout>(R.id.contact_mail_layout).isVisible = it.email != null

Copy link
Author

Choose a reason for hiding this comment

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

2033f07 / 리뷰 참고하여 다음과 같이 수정하였습니다!

Copy link

Choose a reason for hiding this comment

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

오 좋네요 👍
제가 보기엔 깔끔해진거 같은데 어떠세요?

Copy link
Author

Choose a reason for hiding this comment

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

가독성도 더 좋아진 것 같고, 항상 조건에 따라 상태를 지정할 수 있어 좋은 것 같습니다!!

findViewById<TextView>(R.id.contactMail).text = email
}

it.birthday?.let { birthday ->
findViewById<LinearLayout>(R.id.contact_birthday_layout).visibility = View.VISIBLE
findViewById<TextView>(R.id.contactBirthday).text = birthday
}

it.isFemale?.let { isFemale ->
findViewById<LinearLayout>(R.id.contact_Sex_layout).visibility = View.VISIBLE
findViewById<TextView>(R.id.contactSex).text = when (isFemale) {
true -> "여성"
false -> "남성"
}
}

it.memo?.let { memo ->
findViewById<LinearLayout>(R.id.contact_memo_layout).visibility = View.VISIBLE
findViewById<TextView>(R.id.contactMemo).text = memo
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package campus.tech.kakao.contacts

import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.Button
import android.widget.EditText
import android.widget.LinearLayout
import android.widget.RadioButton
import android.widget.Toast
import androidx.activity.OnBackPressedCallback
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat

class ContactRegisterActivity : AppCompatActivity() {
private lateinit var nameInputView: EditText
private lateinit var phoneInputView: EditText
private lateinit var emailInputView: EditText
private lateinit var birthdayInputView: EditText
private lateinit var femaleRadioButton: RadioButton
private lateinit var maleRadioButton: RadioButton
private lateinit var memoInputView: EditText

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_contact_register)

setMoreInfoVisibility()

nameInputView = findViewById(R.id.contactName)
phoneInputView = findViewById(R.id.contactPhone)
emailInputView = findViewById(R.id.contactMail)
birthdayInputView = findViewById(R.id.contactBirthday)
femaleRadioButton = findViewById(R.id.femaleRadioButton)
maleRadioButton = findViewById(R.id.maleRadioButton)
memoInputView = findViewById(R.id.contactMemo)

findViewById<Button>(R.id.cancelButton).setOnClickListener {
Toast.makeText(this@ContactRegisterActivity, "취소되었습니다.", Toast.LENGTH_SHORT).show()
finish()
}

findViewById<Button>(R.id.saveButton).setOnClickListener {
if (checkRequiredFields()) {
val intent = Intent()
intent.putExtra("contactData", saveContact())
setResult(RESULT_OK, intent)
Toast.makeText(this@ContactRegisterActivity, "저장되었습니다.", Toast.LENGTH_SHORT).show()
finish()
}
}

onBackPressedDispatcher.addCallback(backPressedCallback)
}

private val backPressedCallback = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
val dialogBuilder = AlertDialog.Builder(this@ContactRegisterActivity)
dialogBuilder.setMessage("작성중인 내용이 있습니다. 정말 나가시겠습니까?")
dialogBuilder.setNegativeButton("작성하기", null)
dialogBuilder.setPositiveButton("나가기") { dialog, which ->
Toast.makeText(this@ContactRegisterActivity, "취소되었습니다.", Toast.LENGTH_SHORT).show()
finish()
}
dialogBuilder.show()
}
}

private fun setMoreInfoVisibility() {
findViewById<LinearLayout>(R.id.moreInfoButton).setOnClickListener { moreInfoButton ->
findViewById<LinearLayout>(R.id.moreInfoLayout).visibility = View.VISIBLE
moreInfoButton.visibility = View.GONE
}
}

private fun checkRequiredFields(): Boolean {
return if (nameInputView.text.isEmpty()) {
Toast.makeText(this@ContactRegisterActivity, "이름은 필수 입력사항입니다.", Toast.LENGTH_SHORT).show()
false
} else if (phoneInputView.text.isEmpty()) {
Toast.makeText(this@ContactRegisterActivity, "전화번호는 필수 입력사항입니다.", Toast.LENGTH_SHORT).show()
false
} else true
}

private fun saveContact(): ContactData = ContactData(
JSpiner marked this conversation as resolved.
Show resolved Hide resolved
name = nameInputView.text.toString(),
phone = phoneInputView.text.toString(),
email = when {
emailInputView.text.isEmpty() -> null
else -> emailInputView.text.toString()
},
birthday = when {
birthdayInputView.text.isEmpty() -> null
else -> birthdayInputView.text.toString()
},
isFemale = when {
femaleRadioButton.isChecked -> true
maleRadioButton.isChecked -> false
else -> null
},
memo = when {
memoInputView.text.isEmpty() -> null
else -> memoInputView.text.toString()
}
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package campus.tech.kakao.contacts

import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.widget.TextView
import androidx.activity.enableEdgeToEdge
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.LayoutManager
import com.google.android.material.floatingactionbutton.FloatingActionButton

class ContactsListActivity : AppCompatActivity() {
lateinit var contactsList: RecyclerView
lateinit var emptyInfoView: TextView
lateinit var addButton: FloatingActionButton

val contactsDataList = mutableListOf<ContactData>()

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_contacts_list)

contactsList = findViewById(R.id.contacts_list_recyclerview)
emptyInfoView = findViewById(R.id.contact_list_empty_textview)
addButton = findViewById(R.id.contact_add_button)

contactsList.adapter = ContactsListAdapter(contactsDataList, LayoutInflater.from(this), this)
contactsList.layoutManager = LinearLayoutManager(this)

val startActivityLauncher: ActivityResultLauncher<Intent> =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
when (result.resultCode) {
RESULT_OK -> {
val contactData = when (Build.VERSION.SDK_INT) {
in 33..Int.MAX_VALUE ->
result.data?.getSerializableExtra("contactData", ContactData::class.java)
else ->
result.data?.getSerializableExtra("contactData") as ContactData?
}?.let { data ->
contactsDataList.add(data)
contactsList.adapter?.notifyItemInserted(contactsDataList.size - 1)
emptyInfoView.visibility = View.GONE
}
}
}
}

addButton.setOnClickListener {
val intent = Intent(this, ContactRegisterActivity::class.java)
startActivityLauncher.launch(intent)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package campus.tech.kakao.contacts

import android.content.Context
import android.content.Intent
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView

class ContactsListAdapter(
val contactsList: MutableList<ContactData>,
val inflater: LayoutInflater,
val context: Context
): RecyclerView.Adapter<ContactsListAdapter.MyViewHolder>() {

inner class MyViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) {
val thumbnail: TextView = itemView.findViewById(R.id.contact_item_thumbnail)
val name: TextView = itemView.findViewById(R.id.contact_item_name)

init {
itemView.setOnClickListener {
val intent = Intent(context, ContactInfoActivity::class.java)
intent.putExtra("contactData", contactsList[adapterPosition])
context.startActivity(intent)
}
}
}

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val view = inflater.inflate(R.layout.item_contact, parent, false)
return MyViewHolder(view)
}

override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.thumbnail.text = contactsList[position].name[0].toString()
holder.name.text = contactsList[position].name
}

override fun getItemCount(): Int = contactsList.size
}
13 changes: 13 additions & 0 deletions app/src/main/res/drawable/backgound_input_form.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">

<solid android:color="#DCF2F5" />
<corners android:radius="10dp" />
<padding
android:left="10dp"
android:right="10dp"
android:top="10dp"
android:bottom="10dp" />

</shape>
11 changes: 11 additions & 0 deletions app/src/main/res/drawable/background_circle.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">

<gradient
android:startColor="#EAD4AD"
android:endColor="#FFA400"
android:type="linear"
android:angle="315" />

</shape>
Loading