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..c4e4683 --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +BCSD_Android_2025-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/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..97626ba --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,6 @@ + + + + + + \ 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/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..74dd639 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,10 @@ + + + + + + + + + \ 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 5791375..8b7827d 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,6 +1,7 @@ plugins { alias(libs.plugins.android.application) alias(libs.plugins.kotlin.android) + id("org.jetbrains.kotlin.kapt") } android { @@ -16,6 +17,10 @@ android { testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } + buildFeatures { + dataBinding = true + viewBinding = true + } buildTypes { release { @@ -37,6 +42,10 @@ android { dependencies { + implementation(libs.androidx.room.runtime) + implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.0") + implementation(libs.androidx.room.ktx) + kapt(libs.androidx.room.compiler) implementation(libs.androidx.core.ktx) implementation(libs.androidx.appcompat) implementation(libs.material) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4c80941..13fa638 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -21,6 +21,7 @@ + \ No newline at end of file diff --git a/app/src/main/java/com/example/bcsd_android_2025_1/AddEditWordActivity.kt b/app/src/main/java/com/example/bcsd_android_2025_1/AddEditWordActivity.kt new file mode 100644 index 0000000..fd84cc0 --- /dev/null +++ b/app/src/main/java/com/example/bcsd_android_2025_1/AddEditWordActivity.kt @@ -0,0 +1,39 @@ +package com.example.bcsd_android_2025_1 +import android.content.Intent +import android.os.Bundle +import android.widget.Toast +import androidx.appcompat.app.AppCompatActivity +import com.example.bcsd_android_2025_1.databinding.ActivityAddEditWordBinding + +class AddEditWordActivity : AppCompatActivity() { + private lateinit var binding: ActivityAddEditWordBinding + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityAddEditWordBinding.inflate(layoutInflater) + setContentView(binding.root) + + val existingWord = intent.getSerializableExtra("word") as? Word + + if (existingWord != null) { + binding.etWord.setText(existingWord.word) + binding.etMeaning.setText(existingWord.meaning) + } + + binding.btnSave.setOnClickListener { + val word = binding.etWord.text.toString() + val meaning = binding.etMeaning.text.toString() + if (word.isNotBlank() && meaning.isNotBlank()) { + val result = Word( + id = existingWord?.id ?: 0, + word = word, + meaning = meaning + ) + setResult(RESULT_OK, Intent().putExtra("result", result)) + finish() + } else { + Toast.makeText(this, R.string.toast_msg, Toast.LENGTH_SHORT).show() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/bcsd_android_2025_1/MainActivity.kt b/app/src/main/java/com/example/bcsd_android_2025_1/MainActivity.kt index 3ffa0eb..96d5605 100644 --- a/app/src/main/java/com/example/bcsd_android_2025_1/MainActivity.kt +++ b/app/src/main/java/com/example/bcsd_android_2025_1/MainActivity.kt @@ -1,14 +1,56 @@ package com.example.bcsd_android_2025_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.core.view.ViewCompat -import androidx.core.view.WindowInsetsCompat +import androidx.lifecycle.ViewModelProvider +import androidx.recyclerview.widget.LinearLayoutManager +import com.example.bcsd_android_2025_1.databinding.ActivityMainBinding class MainActivity : AppCompatActivity() { + private lateinit var viewModel: WordViewModel + private lateinit var adapter: WordAdapter + + private val launcher = + registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> + if (result.resultCode == RESULT_OK) { + val word = result.data?.getSerializableExtra("result") as? Word + word?.let { + if (it.id == 0) viewModel.insert(it) else viewModel.update(it) + } + } + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContentView(R.layout.activity_main) + val binding = ActivityMainBinding.inflate(layoutInflater) + setContentView(binding.root) + + viewModel = ViewModelProvider(this)[WordViewModel::class.java] + binding.viewModel = viewModel + binding.lifecycleOwner = this + + adapter = WordAdapter( + onItemClick = { word -> viewModel.selectWord(word) }, + onEditClick = { word -> + val intent = Intent(this, AddEditWordActivity::class.java) + intent.putExtra("word", word) + launcher.launch(intent) + }, + onDeleteClick = { word -> viewModel.delete(word) } + ) + + binding.recyclerView.layoutManager = LinearLayoutManager(this) + binding.recyclerView.adapter = adapter + + viewModel.allWords.observe(this) { + adapter.submitList(it) + } + + binding.fab.setOnClickListener { + val intent = Intent(this, AddEditWordActivity::class.java) + launcher.launch(intent) + } } } \ No newline at end of file diff --git a/app/src/main/java/com/example/bcsd_android_2025_1/Word.kt b/app/src/main/java/com/example/bcsd_android_2025_1/Word.kt new file mode 100644 index 0000000..9e2484c --- /dev/null +++ b/app/src/main/java/com/example/bcsd_android_2025_1/Word.kt @@ -0,0 +1,13 @@ +package com.example.bcsd_android_2025_1 + +import androidx.room.Entity +import androidx.room.PrimaryKey +import java.io.Serializable + +@Entity(tableName = "word_table") + +data class Word( + @PrimaryKey(autoGenerate = true) val id : Int = 0, + val word: String, + val meaning: String +) : Serializable \ No newline at end of file diff --git a/app/src/main/java/com/example/bcsd_android_2025_1/WordAdapter.kt b/app/src/main/java/com/example/bcsd_android_2025_1/WordAdapter.kt new file mode 100644 index 0000000..962d66f --- /dev/null +++ b/app/src/main/java/com/example/bcsd_android_2025_1/WordAdapter.kt @@ -0,0 +1,41 @@ +package com.example.bcsd_android_2025_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.bcsd_android_2025_1.databinding.ItemWordBinding + +class WordAdapter( + private val onItemClick: (Word) -> Unit, + private val onEditClick: (Word) -> Unit, + private val onDeleteClick: (Word) -> Unit +) : ListAdapter(DiffCallback()){ + + inner class WordViewHolder(private val binding: ItemWordBinding) : + RecyclerView.ViewHolder(binding.root) { + fun bind(word: Word) { + binding.word = word + binding.root.setOnClickListener { onItemClick(word) } + binding.btnEdit.setOnClickListener { onEditClick(word) } + binding.btnDelete.setOnClickListener { onDeleteClick(word) } + binding.executePendingBindings() + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WordViewHolder { + val inflater = LayoutInflater.from(parent.context) + val binding = ItemWordBinding.inflate(inflater, parent, false) + return WordViewHolder(binding) + } + + override fun onBindViewHolder(holder: WordViewHolder, position: Int) { + holder.bind(getItem(position)) + } + + class DiffCallback : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: Word, newItem: Word) = oldItem.id == newItem.id + override fun areContentsTheSame(oldItem: Word, newItem: Word) = oldItem == newItem + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/bcsd_android_2025_1/WordDao.kt b/app/src/main/java/com/example/bcsd_android_2025_1/WordDao.kt new file mode 100644 index 0000000..3e08961 --- /dev/null +++ b/app/src/main/java/com/example/bcsd_android_2025_1/WordDao.kt @@ -0,0 +1,19 @@ +package com.example.bcsd_android_2025_1 + +import androidx.lifecycle.LiveData +import androidx.room.* + +@Dao +interface WordDao { + @Query("SELECT * FROM word_table ORDER BY id DESC") + fun getAllWords() : LiveData> + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insert(word: Word) + + @Update + suspend fun update(word: Word) + + @Delete + suspend fun delete(word: Word) +} \ No newline at end of file diff --git a/app/src/main/java/com/example/bcsd_android_2025_1/WordDatabase.kt b/app/src/main/java/com/example/bcsd_android_2025_1/WordDatabase.kt new file mode 100644 index 0000000..dec457e --- /dev/null +++ b/app/src/main/java/com/example/bcsd_android_2025_1/WordDatabase.kt @@ -0,0 +1,28 @@ +package com.example.bcsd_android_2025_1 + +import android.content.Context +import androidx.room.Database +import androidx.room.Room +import androidx.room.RoomDatabase + +@Database(entities = [Word::class], version = 1) +abstract class WordDatabase : RoomDatabase() { + abstract fun wordDao(): WordDao + + companion object { + @Volatile + private var INSTANCE: WordDatabase? = null + + fun getDatabase(context: Context): WordDatabase { + return INSTANCE ?: synchronized(this) { + val instance = Room.databaseBuilder( + context.applicationContext, + WordDatabase::class.java, + "word_database" + ).build() + INSTANCE = instance + instance + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/bcsd_android_2025_1/WordRepository.kt b/app/src/main/java/com/example/bcsd_android_2025_1/WordRepository.kt new file mode 100644 index 0000000..847169e --- /dev/null +++ b/app/src/main/java/com/example/bcsd_android_2025_1/WordRepository.kt @@ -0,0 +1,11 @@ +package com.example.bcsd_android_2025_1 + +import androidx.lifecycle.LiveData + +class WordRepository(private val dao: WordDao) { + val allWords : LiveData> = dao.getAllWords() + + suspend fun insert(word: Word) = dao.insert(word) + suspend fun update(word: Word) = dao.update(word) + suspend fun delete(word: Word) = dao.delete(word) +} \ No newline at end of file diff --git a/app/src/main/java/com/example/bcsd_android_2025_1/WordViewModel.kt b/app/src/main/java/com/example/bcsd_android_2025_1/WordViewModel.kt new file mode 100644 index 0000000..127afaf --- /dev/null +++ b/app/src/main/java/com/example/bcsd_android_2025_1/WordViewModel.kt @@ -0,0 +1,24 @@ +package com.example.bcsd_android_2025_1 + +import android.app.Application +import androidx.lifecycle.* +import kotlinx.coroutines.launch + +class WordViewModel(application: Application) : AndroidViewModel(application) { + private val repository: WordRepository + val allWords: LiveData> + private val _selectedWord = MutableLiveData() + val selectedWord: LiveData get() = _selectedWord + + init { + val dao = WordDatabase.getDatabase(application).wordDao() + repository = WordRepository(dao) + allWords = repository.allWords + } + fun insert(word: Word) = viewModelScope.launch { repository.insert(word) } + fun update(word: Word) = viewModelScope.launch { repository.update(word) } + fun delete(word: Word) = viewModelScope.launch { repository.delete(word) } + fun selectWord(word: Word) { + _selectedWord.value = word + } +} diff --git a/app/src/main/res/drawable/add_icon.xml b/app/src/main/res/drawable/add_icon.xml new file mode 100644 index 0000000..9f83b8f --- /dev/null +++ b/app/src/main/res/drawable/add_icon.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/delete_icon.xml b/app/src/main/res/drawable/delete_icon.xml new file mode 100644 index 0000000..883bcaa --- /dev/null +++ b/app/src/main/res/drawable/delete_icon.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/edit_icon.xml b/app/src/main/res/drawable/edit_icon.xml new file mode 100644 index 0000000..3c53db7 --- /dev/null +++ b/app/src/main/res/drawable/edit_icon.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/layout/activity_add_edit__word.xml b/app/src/main/res/layout/activity_add_edit__word.xml new file mode 100644 index 0000000..726b467 --- /dev/null +++ b/app/src/main/res/layout/activity_add_edit__word.xml @@ -0,0 +1,43 @@ + + + + + + + + + + +