diff --git a/README.md b/README.md index cc0acc5a..a7f20b8c 100644 --- a/README.md +++ b/README.md @@ -1 +1,21 @@ # android-contacts + +### 구현할 기능 목록 +- 얼굴 사진 ✅ +- 입력란 + - 이름(필수값) ✅ + - 전화번호(필수, 숫자만 입력) ✅ + - 메일 ✅ + - 더보기 ✅ + - 생일(캘린더로 선택 가능하게끔) ✅ + - 성별(라디오 버튼) ✅ + - 메모 ✅ +- 저장 -> 완료시 토스트 전송 ✅ +- 취소 -> 토스트 전송 ✅ + +- 스크롤뷰 ✅ +- 저장, 취소 버튼 키보드와 함께 올리기 ✅ + +- 저장 버튼 누르면 Info 액티비티로 데이터 보내기 ✅ +- 데이터 받아서 화면 띄우기 ✅ +- 데이터 아이템을 누르면 디테일 화면으로 이동시키기 ✅ \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts index c5add08f..bc29bd5f 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,6 +1,7 @@ plugins { id("com.android.application") id("org.jetbrains.kotlin.android") + id("kotlin-parcelize") } android { @@ -41,6 +42,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.9.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..758e1649 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -13,14 +13,21 @@ android:theme="@style/Theme.Contacts" tools:targetApi="31"> + android:name=".ContactDetail" + android:exported="false" /> + + - + \ No newline at end of file diff --git a/app/src/main/java/campus/tech/kakao/contacts/AddInfoActivity.kt b/app/src/main/java/campus/tech/kakao/contacts/AddInfoActivity.kt new file mode 100644 index 00000000..e8dea234 --- /dev/null +++ b/app/src/main/java/campus/tech/kakao/contacts/AddInfoActivity.kt @@ -0,0 +1,123 @@ +package campus.tech.kakao.contacts + +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.os.Bundle +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Button +import android.widget.ImageView +import android.widget.TextView +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts +import androidx.appcompat.app.AppCompatActivity +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import kotlin.collections.ArrayList + +class AddInfoActivity : AppCompatActivity() { + private lateinit var addBtn: Button + private lateinit var recyclerView: RecyclerView + private lateinit var infoText: TextView + private val contactList = ArrayList() + + private lateinit var resultLauncher: ActivityResultLauncher + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_add_info) + + initialize() + setUpListeners() + setRecyclerView() + setupResultLauncher() + + } + + private fun initialize() { + addBtn = findViewById(R.id.addBtn) + recyclerView = findViewById(R.id.recyclerView) + infoText = findViewById(R.id.infoText) + + } + + private fun setUpListeners() { + addBtn.setOnClickListener { + val intent = Intent(this@AddInfoActivity, MainActivity::class.java) + resultLauncher.launch(intent) + } + } + + + private fun setRecyclerView() { + recyclerView.adapter = RecyclerViewAdapter(contactList, LayoutInflater.from(this),this) + recyclerView.layoutManager = LinearLayoutManager(this) + } + + private fun setupResultLauncher() { + resultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> + if (result.resultCode == Activity.RESULT_OK) { + val data: Intent? = result.data + val contact = data?.getParcelableExtra("CONTACT_RESULT") + contact?.let { + contactList.add(it) + recyclerView.adapter?.notifyItemInserted(contactList.size - 1) + Log.d("AddInfo", "New contact added: ${it.name}") + isShowText() + } + } + } + } + + private fun isShowText() { + if (contactList.isEmpty()) { + infoText.visibility = View.VISIBLE + } + else { + infoText.visibility = View.GONE + } + } + + + class RecyclerViewAdapter( + var contactList: ArrayList, + var inflater: LayoutInflater, + private val context: Context + ): RecyclerView.Adapter() { + + class ViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) { + val faceImg: ImageView + val nameTextView: TextView + + init { + faceImg = itemView.findViewById(R.id.faceImg) + nameTextView = itemView.findViewById(R.id.name_text_view) + } + + } + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val view = inflater.inflate(R.layout.contact_item, parent, false) + return ViewHolder(view) + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val contact = contactList[position] + holder.nameTextView.text = contact.name + + holder.itemView.setOnClickListener { + val intent = Intent(context, ContactDetail::class.java).apply { + putExtra("CONTACT", contact) + } + context.startActivity(intent) + } + } + + override fun getItemCount(): Int { + return contactList.size + } + } +} + 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..8be339e4 --- /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 tel: String, + val mail: String, + val birth: String, + val gender: String, + val memo: String +): Parcelable \ No newline at end of file diff --git a/app/src/main/java/campus/tech/kakao/contacts/ContactDetail.kt b/app/src/main/java/campus/tech/kakao/contacts/ContactDetail.kt new file mode 100644 index 00000000..cee2102f --- /dev/null +++ b/app/src/main/java/campus/tech/kakao/contacts/ContactDetail.kt @@ -0,0 +1,73 @@ +package campus.tech.kakao.contacts + +import android.os.Build +import android.os.Bundle +import android.view.View +import android.widget.TextView +import androidx.appcompat.app.AppCompatActivity +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.view.isVisible + +class ContactDetail : AppCompatActivity() { + lateinit var detailName: TextView + lateinit var detailTel: TextView + lateinit var detailMail: TextView + lateinit var detailBirth: TextView + lateinit var detailGender: TextView + lateinit var detailMemo: TextView + + lateinit var detailMailLayout: ConstraintLayout + lateinit var detailBirthLayout: ConstraintLayout + lateinit var detailGenderLayout: ConstraintLayout + lateinit var detailMemoLayout: ConstraintLayout + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_contact_detail) + + initialize() + setContactDetail() + } + private fun initialize() { + detailName = findViewById(R.id.detailName) + detailTel = findViewById(R.id.detailTel) + detailMail = findViewById(R.id.detailMail) + detailBirth = findViewById(R.id.detailBirth) + detailGender = findViewById(R.id.detailGender) + detailMemo = findViewById(R.id.detailMemo) + + detailMailLayout = findViewById(R.id.detailMail_layout) + detailBirthLayout = findViewById(R.id.detailBirth_layout) + detailGenderLayout = findViewById(R.id.detailGender_layout) + detailMemoLayout = findViewById(R.id.detailMemo_layout) + } + + private fun setContactDetail() { + val contact: Contact? = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + intent.getParcelableExtra("CONTACT", Contact::class.java) + } else { + intent.getParcelableExtra("CONTACT") + } + + contact?.let { + setDetail(it) + setDetailLayoutVisibility(it) + } + } + private fun setDetail(contact: Contact) { + detailName.text = contact.name + detailTel.text = contact.tel + detailMail.text = contact.mail ?: "" + detailBirth.text = contact.birth ?: "" + detailGender.text = contact.gender + detailMemo.text = contact.memo ?: "" + } + + private fun setDetailLayoutVisibility(contact: Contact) { + detailMailLayout.isVisible = contact.mail.isNotEmpty() + detailBirthLayout.isVisible = contact.birth.isNotEmpty() + detailGenderLayout.isVisible = contact.gender != "null" + detailMemoLayout.isVisible = contact.memo.isNotEmpty() + } +} 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..ca8ac0e9 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,149 @@ package campus.tech.kakao.contacts +import android.app.AlertDialog +import android.app.DatePickerDialog +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.TextView +import android.widget.Toast import androidx.appcompat.app.AppCompatActivity +import java.util.Calendar class MainActivity : AppCompatActivity() { + private lateinit var name: EditText + private lateinit var tel: EditText + private lateinit var mail: EditText + private lateinit var birth: EditText + private lateinit var gender: EditText + private lateinit var genderRadio: LinearLayout + private lateinit var memo: EditText + private lateinit var viewMore: TextView + private lateinit var cancel: Button + private lateinit var save: Button + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) + + initializeView() + setUpListeners() + } + + private fun initializeView() { + name = findViewById(R.id.name) + tel = findViewById(R.id.tel) + mail = findViewById(R.id.mail) + birth = findViewById(R.id.birth) + gender = findViewById(R.id.gender) + genderRadio = findViewById(R.id.genderRadio) + memo = findViewById(R.id.memo) + viewMore = findViewById(R.id.viewMore) + cancel = findViewById(R.id.cancel) + save = findViewById(R.id.save) + } + + + private fun setUpListeners() { + viewMore.setOnClickListener { + expandView() + } + cancel.setOnClickListener { + showCancelMsg() + } + save.setOnClickListener { + if (checkValide()) { + saveData() + Toast.makeText(this, "저장되었습니다.", Toast.LENGTH_SHORT).show() + } + } + birth.setOnClickListener { + showCalendar() + } + } + + private fun expandView() { + birth.visibility = View.VISIBLE + gender.visibility = View.VISIBLE + genderRadio.visibility = View.VISIBLE + memo.visibility = View.VISIBLE + viewMore.visibility = View.INVISIBLE + } + + private fun checkValide(): Boolean { + return when { + name.text.isEmpty() -> { + Toast.makeText(this, "이름은 필수 입력값입니다.", Toast.LENGTH_SHORT).show() + false + } + tel.text.isEmpty() -> { + Toast.makeText(this, "전화번호는 필수 입력값입니다.", Toast.LENGTH_SHORT).show() + false + } + else -> true + } + } + private fun showCalendar() { + var dateString = "" + val cal = Calendar.getInstance() + val dateSetListener = DatePickerDialog.OnDateSetListener { view, year, month, dayOfMonth -> + dateString = "${year}.${month+1}.${dayOfMonth}" + birth.setText(dateString) + + } + DatePickerDialog(this, dateSetListener, cal.get(Calendar.YEAR), + cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH)).show() } -} + + private fun saveData() { + val contact = createContact() + val resultIntent = Intent().apply { + putExtra("CONTACT_RESULT", contact) + Log.d("Gender", "${gender}") + } + setResult(RESULT_OK, resultIntent) + finish() + } + + private fun createContact(): Contact { + var genderData: String? + var male = findViewById(R.id.male) + var female = findViewById(R.id.female) + + if (male.isChecked) genderData = "male" + else if (female.isChecked) genderData = "female" + else genderData = "null" + + val contact: Contact + contact = Contact( + name.text.toString(), + tel.text.toString(), + mail.text.toString(), + birth.text.toString(), + genderData, + memo.text.toString() + ) + + return contact + } + + private fun showCancelMsg() { + AlertDialog.Builder(this).apply { + setMessage("작성 중인 내용이 있습니다. 정말 나가시겠습니까?") + setPositiveButton("나가기") { _, _ -> + finish() + } + setNegativeButton("작성하기", null) + create() + show() + } + } + + + +} \ No newline at end of file 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..a5936707 --- /dev/null +++ b/app/src/main/res/drawable/arrow_down.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/face.xml b/app/src/main/res/drawable/face.xml new file mode 100644 index 00000000..b61d61ce --- /dev/null +++ b/app/src/main/res/drawable/face.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/rectangled.xml b/app/src/main/res/drawable/rectangled.xml new file mode 100644 index 00000000..cead1e45 --- /dev/null +++ b/app/src/main/res/drawable/rectangled.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/rectangled_mint.xml b/app/src/main/res/drawable/rectangled_mint.xml new file mode 100644 index 00000000..6325bd6d --- /dev/null +++ b/app/src/main/res/drawable/rectangled_mint.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_add_info.xml b/app/src/main/res/layout/activity_add_info.xml new file mode 100644 index 00000000..2a9de002 --- /dev/null +++ b/app/src/main/res/layout/activity_add_info.xml @@ -0,0 +1,42 @@ + + + + + + + +