diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..26d3352
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,3 @@
+# Default ignored files
+/shelf/
+/workspace.xml
diff --git a/.idea/.name b/.idea/.name
new file mode 100644
index 0000000..cec0517
--- /dev/null
+++ b/.idea/.name
@@ -0,0 +1 @@
+android_2026_1
\ No newline at end of file
diff --git a/.idea/AndroidProjectSystem.xml b/.idea/AndroidProjectSystem.xml
new file mode 100644
index 0000000..4a53bee
--- /dev/null
+++ b/.idea/AndroidProjectSystem.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
new file mode 100644
index 0000000..b86273d
--- /dev/null
+++ b/.idea/compiler.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml
new file mode 100644
index 0000000..b268ef3
--- /dev/null
+++ b/.idea/deploymentTargetSelector.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/deviceManager.xml b/.idea/deviceManager.xml
new file mode 100644
index 0000000..91f9558
--- /dev/null
+++ b/.idea/deviceManager.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
new file mode 100644
index 0000000..639c779
--- /dev/null
+++ b/.idea/gradle.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/markdown.xml b/.idea/markdown.xml
new file mode 100644
index 0000000..c61ea33
--- /dev/null
+++ b/.idea/markdown.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/migrations.xml b/.idea/migrations.xml
new file mode 100644
index 0000000..f8051a6
--- /dev/null
+++ b/.idea/migrations.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..b2c751a
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml
new file mode 100644
index 0000000..16660f1
--- /dev/null
+++ b/.idea/runConfigurations.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1dd
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index cc8ee59..b15966f 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -1,15 +1,17 @@
plugins {
alias(libs.plugins.android.application)
+ id("kotlin-parcelize")
+ alias(libs.plugins.android.legacy.kapt)
}
android {
namespace = "com.example.android_2026_1"
- compileSdk = 36
+ compileSdk = 37
defaultConfig {
applicationId = "com.example.android_2026_1"
minSdk = 26
- targetSdk = 36
+ targetSdk = 37
versionCode = 1
versionName = "1.0"
@@ -29,6 +31,10 @@ android {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
+
+ buildFeatures {
+ dataBinding = true
+ }
}
dependencies {
@@ -37,7 +43,9 @@ dependencies {
implementation(libs.material)
implementation(libs.androidx.activity)
implementation(libs.androidx.constraintlayout)
+ implementation(libs.androidx.recyclerview)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
+ implementation(libs.coil)
}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index e58d838..5ac5522 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -19,6 +19,14 @@
+
+
+
+
+
+
diff --git a/app/src/main/java/com/example/android_2026_1/BindingAdapters.kt b/app/src/main/java/com/example/android_2026_1/BindingAdapters.kt
new file mode 100644
index 0000000..6c9fd70
--- /dev/null
+++ b/app/src/main/java/com/example/android_2026_1/BindingAdapters.kt
@@ -0,0 +1,16 @@
+package com.example.android_2026_1
+
+import android.widget.ImageView
+import androidx.databinding.BindingAdapter
+import coil.dispose
+import coil.load
+
+@BindingAdapter("imageUri")
+fun ImageView.setImageUri(uri: String?) {
+ if (uri.isNullOrEmpty()) {
+ dispose()
+ setImageDrawable(null)
+ } else {
+ load(uri)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/android_2026_1/MainActivity.kt b/app/src/main/java/com/example/android_2026_1/MainActivity.kt
index ad2a6ca..93e2bc6 100644
--- a/app/src/main/java/com/example/android_2026_1/MainActivity.kt
+++ b/app/src/main/java/com/example/android_2026_1/MainActivity.kt
@@ -1,20 +1,87 @@
package com.example.android_2026_1
+import android.content.Intent
import android.os.Bundle
import androidx.activity.enableEdgeToEdge
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
+import androidx.databinding.DataBindingUtil
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.example.android_2026_1.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
+
+ private val viewModel: MainViewModel by viewModels()
+ private lateinit var adapter: WordAdapter
+ private lateinit var binding: ActivityMainBinding
+
+ private val editLauncher = registerForActivityResult(
+ ActivityResultContracts.StartActivityForResult()
+ ) { result ->
+ val data = result.data
+ if (data != null) {
+ val mode = data.getStringExtra("mode")
+ val word = data.getStringExtra("word").orEmpty()
+ val meaning = data.getStringExtra("meaning").orEmpty()
+ val uri = data.getStringExtra("imageUri")
+
+ when (mode) {
+ MODE_ADD -> viewModel.addWord(word, meaning, uri)
+ MODE_EDIT -> {
+ val id = data.getIntExtra("id", -1)
+ viewModel.editWord(id, word, meaning, uri)
+ }
+ }
+ }
+ }
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
- setContentView(R.layout.activity_main)
- ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
+ binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
+ binding.viewModel = viewModel
+ binding.lifecycleOwner = this
+
+ ViewCompat.setOnApplyWindowInsetsListener(binding.main) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
}
+
+ adapter = WordAdapter { clickedItem ->
+ viewModel.selectItem(clickedItem)
+ }
+
+ binding.recyclerView.layoutManager = LinearLayoutManager(this)
+ binding.recyclerView.adapter = adapter
+
+ binding.btnAdd.setOnClickListener {
+ val intent = Intent(this, WordEditActivity::class.java).apply {
+ putExtra("mode", MODE_ADD)
+ }
+ editLauncher.launch(intent)
+ }
+
+ binding.btnEdit.setOnClickListener {
+ viewModel.selectedWord.value?.let { item ->
+ val intent = Intent(this, WordEditActivity::class.java).apply {
+ putExtra("mode", MODE_EDIT)
+ putExtra("item", item)
+ }
+ editLauncher.launch(intent)
+ }
+ }
+
+ viewModel.wordList.observe(this) { newList ->
+ adapter.submitList(newList)
+ }
+ }
+
+ companion object {
+ const val MODE_ADD = "ADD"
+ const val MODE_EDIT = "EDIT"
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/android_2026_1/MainViewModel.kt b/app/src/main/java/com/example/android_2026_1/MainViewModel.kt
new file mode 100644
index 0000000..83dd5f7
--- /dev/null
+++ b/app/src/main/java/com/example/android_2026_1/MainViewModel.kt
@@ -0,0 +1,59 @@
+package com.example.android_2026_1
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+
+class MainViewModel : ViewModel() {
+
+ private val _wordList = MutableLiveData>(emptyList())
+ val wordList: LiveData> = _wordList
+
+ private val _selectedWord = MutableLiveData(null)
+ val selectedWord: LiveData = _selectedWord
+
+ private var nextId = 0
+
+ private fun getCurrentList(): MutableList {
+ return _wordList.value.orEmpty().toMutableList()
+ }
+
+ fun addWord(word: String, meaning: String, uri: String?) {
+ val currentList = getCurrentList()
+ val newItem = WordItem(nextId++, word, meaning, uri)
+ currentList.add(newItem)
+ _wordList.value = currentList
+ }
+
+ fun editWord(id: Int, word: String, meaning: String, uri: String?) {
+ val currentList = getCurrentList()
+ val index = currentList.indexOfFirst { it.id == id }
+ if (index != -1) {
+ val updated = WordItem(id, word, meaning, uri)
+ currentList[index] = updated
+ _wordList.value = currentList
+ if (_selectedWord.value?.id == id) {
+ _selectedWord.value = updated
+ }
+ }
+ }
+
+ fun deleteWord() {
+ val item = _selectedWord.value ?: return
+ val currentList = getCurrentList()
+ val index = currentList.indexOfFirst { it.id == item.id }
+ if (index != -1) {
+ currentList.removeAt(index)
+ _wordList.value = currentList
+ clearSelection()
+ }
+ }
+
+ fun selectItem(item: WordItem) {
+ _selectedWord.value = item
+ }
+
+ fun clearSelection() {
+ _selectedWord.value = null
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/android_2026_1/WordAdapter.kt b/app/src/main/java/com/example/android_2026_1/WordAdapter.kt
new file mode 100644
index 0000000..e57ef3a
--- /dev/null
+++ b/app/src/main/java/com/example/android_2026_1/WordAdapter.kt
@@ -0,0 +1,38 @@
+package com.example.android_2026_1
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.recyclerview.widget.DiffUtil
+import androidx.recyclerview.widget.ListAdapter
+import androidx.recyclerview.widget.RecyclerView
+import com.example.android_2026_1.databinding.ItemWordBinding
+
+class WordAdapter(
+ private val onItemClick: (WordItem) -> Unit
+) : ListAdapter(WordDiffCallback) {
+
+ class ViewHolder(val binding: ItemWordBinding) : RecyclerView.ViewHolder(binding.root)
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+ val layoutInflater = LayoutInflater.from(parent.context)
+ val binding = ItemWordBinding.inflate(layoutInflater, parent, false)
+ return ViewHolder(binding)
+ }
+
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ val item = getItem(position)
+ holder.binding.item = item
+ holder.binding.root.setOnClickListener { onItemClick(item) }
+ holder.binding.executePendingBindings()
+ }
+
+ companion object WordDiffCallback : DiffUtil.ItemCallback() {
+ override fun areItemsTheSame(oldItem: WordItem, newItem: WordItem): Boolean {
+ return oldItem.id == newItem.id
+ }
+
+ override fun areContentsTheSame(oldItem: WordItem, newItem: WordItem): Boolean {
+ return oldItem == newItem
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/android_2026_1/WordEditActivity.kt b/app/src/main/java/com/example/android_2026_1/WordEditActivity.kt
new file mode 100644
index 0000000..13cb6ea
--- /dev/null
+++ b/app/src/main/java/com/example/android_2026_1/WordEditActivity.kt
@@ -0,0 +1,84 @@
+package com.example.android_2026_1
+
+import android.content.Intent
+import android.os.Bundle
+import androidx.activity.enableEdgeToEdge
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.appcompat.app.AppCompatActivity
+import androidx.databinding.DataBindingUtil
+import coil.load
+import coil.dispose
+import com.example.android_2026_1.databinding.ActivityWordEditBinding
+
+class WordEditActivity : AppCompatActivity() {
+
+ private var imageUri: String? = null
+ private lateinit var binding: ActivityWordEditBinding
+
+ private val pickLauncher = registerForActivityResult(
+ ActivityResultContracts.GetContent()
+ ) { uri ->
+ uri?.let {
+ imageUri = it.toString()
+ binding.btnImage.load(it)
+ }
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ enableEdgeToEdge()
+ binding = DataBindingUtil.setContentView(this, R.layout.activity_word_edit)
+
+ val mode = intent.getStringExtra("mode") ?: "ADD"
+ var editId = -1
+ val isEdit = mode == "EDIT"
+
+ binding.isEdit = isEdit
+
+ if (isEdit) {
+ val item = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) {
+ intent.getParcelableExtra("item", WordItem::class.java)
+ } else {
+ @Suppress("DEPRECATION")
+ intent.getParcelableExtra("item")
+ }
+
+ item?.let { item ->
+ editId = item.id
+ imageUri = item.imageUri
+ binding.item = item
+ if (item.imageUri == null) {
+ binding.btnImage.dispose()
+ binding.btnImage.setImageResource(android.R.drawable.ic_input_add)
+ }
+ }
+ } else {
+ imageUri = null
+ binding.btnImage.dispose()
+ binding.btnImage.setImageResource(android.R.drawable.ic_input_add)
+ }
+
+ binding.btnImage.setOnClickListener {
+ pickLauncher.launch("image/*")
+ }
+
+ binding.btnSubmit.setOnClickListener {
+ val wordText = binding.etWord.text.toString().trim()
+ val meaningText = binding.etMeaning.text.toString().trim()
+
+ if (wordText.isEmpty() || meaningText.isEmpty()) {
+ return@setOnClickListener
+ }
+
+ val resultIntent = Intent().apply {
+ putExtra("mode", mode)
+ putExtra("id", editId)
+ putExtra("word", wordText)
+ putExtra("meaning", meaningText)
+ putExtra("imageUri", imageUri)
+ }
+ setResult(RESULT_OK, resultIntent)
+ finish()
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/android_2026_1/WordItem.kt b/app/src/main/java/com/example/android_2026_1/WordItem.kt
new file mode 100644
index 0000000..411c707
--- /dev/null
+++ b/app/src/main/java/com/example/android_2026_1/WordItem.kt
@@ -0,0 +1,12 @@
+package com.example.android_2026_1
+
+import android.os.Parcelable
+import kotlinx.parcelize.Parcelize
+
+@Parcelize
+data class WordItem(
+ val id: Int,
+ val word: String,
+ val meaning: String,
+ val imageUri: String? = null
+) : Parcelable
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index 9affce0..e2d43a5 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -1,10 +1,103 @@
-
+ xmlns:tools="http://schemas.android.com/tools">
-
\ No newline at end of file
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_word_edit.xml b/app/src/main/res/layout/activity_word_edit.xml
new file mode 100644
index 0000000..05b2a51
--- /dev/null
+++ b/app/src/main/res/layout/activity_word_edit.xml
@@ -0,0 +1,97 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_word.xml b/app/src/main/res/layout/item_word.xml
new file mode 100644
index 0000000..b78bf44
--- /dev/null
+++ b/app/src/main/res/layout/item_word.xml
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index c8524cd..5256fd6 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -2,4 +2,8 @@
#FF000000
#FFFFFFFF
+
+ #673AB7
+ #FFC107
+ #4CAF50
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index c4a5053..9696cb7 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1,3 +1,17 @@
- android_2026_1
+ 단어장
+
+ 단어 추가
+ 단어 수정
+
+ 단어
+ 뜻
+
+ 추가
+ 수정
+
+ 단어 이미지
+ 단어 삭제
+ 단어 수정
+ 새 단어 추가
\ No newline at end of file
diff --git a/build.gradle.kts b/build.gradle.kts
index 922f551..06e4514 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,4 +1,3 @@
-// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.kotlin.android) apply false
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index dd29a05..7126ec5 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,25 +1,32 @@
[versions]
-kotlin = "2.3.21"
+agp = "9.1.1"
+databindingCompiler = "9.3.0"
+kotlin = "2.4.10"
+coil = "2.7.0"
activity = "1.13.0"
-agp = "9.0.1"
constraintlayout = "2.2.1"
-coreKtx = "1.18.0"
+coreKtx = "1.19.0"
junit = "4.13.2"
junitVersion = "1.3.0"
espressoCore = "3.7.0"
appcompat = "1.7.1"
material = "1.14.0"
+recyclerview = "1.4.0"
[libraries]
androidx-constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version.ref = "constraintlayout" }
androidx-activity = { module = "androidx.activity:activity", version.ref = "activity" }
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
+androidx-databinding-compiler = { module = "androidx.databinding:databinding-compiler", version.ref = "databindingCompiler" }
+coil = { module = "io.coil-kt:coil", version.ref = "coil" }
junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
material = { module = "com.google.android.material:material", version.ref = "material" }
+androidx-recyclerview = { group = "androidx.recyclerview", name = "recyclerview", version.ref = "recyclerview" }
[plugins]
-android-application = { id = "com.android.application", version.ref = "agp" }
-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
\ No newline at end of file
+android-application = { id="com.android.application", version.ref="agp" }
+kotlin-android = { id="org.jetbrains.kotlin.android", version.ref="kotlin" }
+android-legacy-kapt = { id = "com.android.legacy-kapt", version.ref = "agp" }
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 986dfe1..36bee10 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,8 +1,8 @@
-#Fri May 15 03:28:10 KST 2026
+#Sat Jul 18 21:35:54 KST 2026
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionSha256Sum=a17ddd85a26b6a7f5ddb71ff8b05fc5104c0202c6e64782429790c933686c806
-distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip
+distributionSha256Sum=b266d5ff6b90eada6dc3b20cb090e3731302e553a27c5d3e4df1f0d76beaff06
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME