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 @@ + + + + + + + + + + + + + + + + + + + + + + +