diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml index 148fdd2..bb44937 100644 --- a/.idea/kotlinc.xml +++ b/.idea/kotlinc.xml @@ -1,6 +1,6 @@ - \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 2a4bbd8..61d8a23 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("kotlin-kapt") } android { @@ -17,6 +18,11 @@ android { testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } + buildFeatures { + viewBinding = true + dataBinding = true + } + buildTypes { release { isMinifyEnabled = false @@ -42,7 +48,14 @@ dependencies { implementation(libs.material) implementation(libs.androidx.activity) implementation(libs.androidx.constraintlayout) + implementation(libs.androidx.room.common.jvm) + implementation(libs.androidx.room.runtime.android) testImplementation(libs.junit) androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) + implementation(libs.androidx.room.runtime) + implementation(libs.androidx.room.ktx) + implementation(libs.androidx.lifecycle.viewmodel.ktx) + implementation(libs.androidx.lifecycle.livedata.ktx) + kapt(libs.androidx.room.compiler) } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 398b755..e018814 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -20,6 +20,9 @@ + \ No newline at end of file diff --git a/app/src/main/java/com/example/android_25_2/MainActivity.kt b/app/src/main/java/com/example/android_25_2/MainActivity.kt index aed359b..f0f684f 100644 --- a/app/src/main/java/com/example/android_25_2/MainActivity.kt +++ b/app/src/main/java/com/example/android_25_2/MainActivity.kt @@ -1,20 +1,70 @@ package com.example.android_25_2 +import android.content.Intent import android.os.Bundle import androidx.activity.enableEdgeToEdge 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.android_25_2.databinding.ActivityMainBinding class MainActivity : AppCompatActivity() { + + private lateinit var activityBinding: ActivityMainBinding + private lateinit var wordViewModel: WordViewModel + private lateinit var adapter: WordAdapter + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() - setContentView(R.layout.activity_main) + activityBinding = ActivityMainBinding.inflate(layoutInflater) + setContentView(activityBinding.root) ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets -> val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()) v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom) insets } + + wordViewModel = ViewModelProvider(this)[WordViewModel::class.java] + adapter = WordAdapter() + activityBinding.recyclerView.adapter = adapter + activityBinding.recyclerView.layoutManager = LinearLayoutManager(this) + + activityBinding.buttonAdd.setOnClickListener { + startActivity(Intent(this, SecondActivity::class.java)) + } + + wordViewModel.allWords.observe(this) { words -> + adapter.setWords(words) + } + + adapter.setOnWordItemClickListener(object : WordAdapter.OnWordItemClickListener{ + override fun onWordItemClick(position: Int, word: WordEntity) { + adapter.moveToTop(position) + activityBinding.recyclerView.scrollToPosition(0) + } + + override fun onEditButtonClick(word: WordEntity) { + val intent = Intent(this@MainActivity, SecondActivity::class.java) + intent.putExtra("WORD_ID", word.id) + intent.putExtra("WORD", word.word) + intent.putExtra("MEANING", word.meaning) + startActivity(intent) + } + + override fun onDeleteButtonClick(word: WordEntity) { + wordViewModel.delete(word) + } + }) + + activityBinding.buttonAdd.setOnClickListener{ + startActivity(Intent(this, SecondActivity::class.java)) + } + + wordViewModel.allWords.observe(this) {words -> + adapter.setWords(words) + } } } \ No newline at end of file diff --git a/app/src/main/java/com/example/android_25_2/SecondActivity.kt b/app/src/main/java/com/example/android_25_2/SecondActivity.kt new file mode 100644 index 0000000..a666afc --- /dev/null +++ b/app/src/main/java/com/example/android_25_2/SecondActivity.kt @@ -0,0 +1,57 @@ +package com.example.android_25_2 + +import android.os.Bundle +import android.widget.Toast +import androidx.appcompat.app.AppCompatActivity +import androidx.databinding.DataBindingUtil +import androidx.lifecycle.ViewModelProvider +import com.example.android_25_2.databinding.ActivitySecondBinding + +class SecondActivity : AppCompatActivity() { + + private lateinit var activityBinding: ActivitySecondBinding + private lateinit var wordViewModel: WordViewModel + private var wordId: Int? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + activityBinding = DataBindingUtil.setContentView(this, R.layout.activity_second) + + wordViewModel = ViewModelProvider(this)[WordViewModel::class.java] + + wordId = intent.getIntExtra("WORD_ID", -1) + if (wordId == -1) { + wordId = null + } + val existingWord = intent.getStringExtra("WORD") + val existingMeaning = intent.getStringExtra("MEANING") + + if (wordId != null) { + activityBinding.editTextWord.setText(existingWord) + activityBinding.editTextMeaning.setText(existingMeaning) + } + activityBinding.buttonSave.setOnClickListener { + val word = activityBinding.editTextWord.text.toString() + val meaning = activityBinding.editTextMeaning.text.toString() + + if (word.isEmpty() || meaning.isEmpty()) { + Toast.makeText(this, R.string.empty_message, Toast.LENGTH_SHORT).show() + return@setOnClickListener + } + + if (wordId != null) { + val wordEntity = WordEntity( + id = wordId!!, + word = word, + meaning = meaning + ) + wordViewModel.update(wordEntity) + } else { + val wordEntity = WordEntity(word = word, meaning = meaning) + wordViewModel.insert(wordEntity) + } + + finish() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/android_25_2/WordAdapter.kt b/app/src/main/java/com/example/android_25_2/WordAdapter.kt new file mode 100644 index 0000000..d9bf17f --- /dev/null +++ b/app/src/main/java/com/example/android_25_2/WordAdapter.kt @@ -0,0 +1,73 @@ +package com.example.android_25_2 + +import android.view.ViewGroup +import android.view.LayoutInflater +import androidx.recyclerview.widget.RecyclerView +import com.example.android_25_2.databinding.ItemBinding + +class WordAdapter: RecyclerView.Adapter(){ + + private var words = emptyList() + + interface OnWordItemClickListener { + fun onWordItemClick(position: Int, word: WordEntity) + fun onEditButtonClick(word: WordEntity) + fun onDeleteButtonClick(word: WordEntity) + } + + private var listener: OnWordItemClickListener? = null + fun setOnWordItemClickListener(listener: OnWordItemClickListener) { + this.listener = listener + } + + inner class WordViewHolder(val binding: ItemBinding) : + RecyclerView.ViewHolder(binding.root) + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WordAdapter.WordViewHolder { + val binding = ItemBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + return WordViewHolder(binding) + } + + override fun onBindViewHolder(holder: WordAdapter.WordViewHolder, position: Int) { + val word = words[position] + holder.binding.word = word + holder.binding.executePendingBindings() + + holder.binding.root.setOnClickListener { + listener?.onWordItemClick(holder.adapterPosition, word) + } + + holder.binding.buttonEdit.setOnClickListener { + listener?.onEditButtonClick(word) + } + + holder.binding.buttonDelete.setOnClickListener { + listener?.onDeleteButtonClick(word) + } + } + + override fun getItemCount() = words.size + + fun setWords(newWords: List) { + this.words = newWords + notifyDataSetChanged() + } + + fun getWord(position: Int): WordEntity { + return words[position] + } + + fun moveToTop(position: Int) { + val word = words[position] + val currentList = words.toMutableList() + currentList.removeAt(position) + currentList.add(0, word) + words = currentList + + notifyItemMoved(position, 0) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/android_25_2/WordDao.kt b/app/src/main/java/com/example/android_25_2/WordDao.kt new file mode 100644 index 0000000..8ee2800 --- /dev/null +++ b/app/src/main/java/com/example/android_25_2/WordDao.kt @@ -0,0 +1,23 @@ +package com.example.android_25_2 + +import androidx.lifecycle.LiveData +import androidx.room.Dao +import androidx.room.Delete +import androidx.room.Insert +import androidx.room.Query +import androidx.room.Update + +@Dao +interface WordDao { + @Insert + suspend fun insert(vararg word: WordEntity) + + @Update + suspend fun update(word: WordEntity) + + @Delete + suspend fun delete(word: WordEntity) + + @Query("SELECT * FROM word_table") + fun getAll(): LiveData> +} \ No newline at end of file diff --git a/app/src/main/java/com/example/android_25_2/WordDatabase.kt b/app/src/main/java/com/example/android_25_2/WordDatabase.kt new file mode 100644 index 0000000..52c2185 --- /dev/null +++ b/app/src/main/java/com/example/android_25_2/WordDatabase.kt @@ -0,0 +1,28 @@ +package com.example.android_25_2 + +import android.content.Context +import androidx.room.Database +import androidx.room.Room +import androidx.room.RoomDatabase + +@Database(entities = [WordEntity::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/android_25_2/WordEntity.kt b/app/src/main/java/com/example/android_25_2/WordEntity.kt new file mode 100644 index 0000000..413cc1f --- /dev/null +++ b/app/src/main/java/com/example/android_25_2/WordEntity.kt @@ -0,0 +1,12 @@ +package com.example.android_25_2 + +import androidx.room.Entity +import androidx.room.PrimaryKey + +@Entity (tableName = "word_table") +data class WordEntity( + @PrimaryKey (autoGenerate = true) + val id: Int = 0, + val word: String, + val meaning: String +) \ No newline at end of file diff --git a/app/src/main/java/com/example/android_25_2/WordViewModel.kt b/app/src/main/java/com/example/android_25_2/WordViewModel.kt new file mode 100644 index 0000000..c957f06 --- /dev/null +++ b/app/src/main/java/com/example/android_25_2/WordViewModel.kt @@ -0,0 +1,31 @@ +package com.example.android_25_2 + +import android.app.Application +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.LiveData +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.launch + +class WordViewModel(application: Application) : AndroidViewModel(application) { + + private val wordDao: WordDao + val allWords: LiveData> + + init { + val database = WordDatabase.getDatabase(application) + wordDao = database.wordDao() + allWords = wordDao.getAll() + } + + fun insert(word: WordEntity) = viewModelScope.launch { + wordDao.insert(word) + } + + fun update(word: WordEntity) = viewModelScope.launch { + wordDao.update(word) + } + + fun delete(word: WordEntity) = viewModelScope.launch { + wordDao.delete(word) + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/id_add.xml b/app/src/main/res/drawable/id_add.xml new file mode 100644 index 0000000..9f83b8f --- /dev/null +++ b/app/src/main/res/drawable/id_add.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 9affce0..df630de 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,10 +1,30 @@ - + 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_second.xml b/app/src/main/res/layout/activity_second.xml new file mode 100644 index 0000000..edf745f --- /dev/null +++ b/app/src/main/res/layout/activity_second.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + +