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/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/kotlinc.xml b/.idea/kotlinc.xml new file mode 100644 index 0000000..c22b6fa --- /dev/null +++ b/.idea/kotlinc.xml @@ -0,0 +1,6 @@ + + + + + \ 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..f7608ed --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ 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/shelf/Uncommitted_changes_before_Checkout_at_2025-07-13__8_26__Changes_.xml b/.idea/shelf/Uncommitted_changes_before_Checkout_at_2025-07-13__8_26__Changes_.xml new file mode 100644 index 0000000..9984e75 --- /dev/null +++ b/.idea/shelf/Uncommitted_changes_before_Checkout_at_2025-07-13__8_26__Changes_.xml @@ -0,0 +1,4 @@ + + \ No newline at end of file diff --git "a/.idea/shelf/Uncommitted_changes_before_Checkout_at_2025-07-13_\354\230\244\355\233\204_8_26_[Changes]/shelved.patch" "b/.idea/shelf/Uncommitted_changes_before_Checkout_at_2025-07-13_\354\230\244\355\233\204_8_26_[Changes]/shelved.patch" new file mode 100644 index 0000000..2ad04a8 --- /dev/null +++ "b/.idea/shelf/Uncommitted_changes_before_Checkout_at_2025-07-13_\354\230\244\355\233\204_8_26_[Changes]/shelved.patch" @@ -0,0 +1,49 @@ +Index: app/src/main/java/com/example/bcsd_android_2025_1/MainActivity.kt +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP +<+>package com.example.bcsd_android_2025_1\r\nimport android.os.Bundle\r\nimport android.widget.Button\r\nimport android.widget.TextView\r\nimport androidx.appcompat.app.AppCompatActivity\r\nimport androidx.lifecycle.lifecycleScope\r\nimport androidx.recyclerview.widget.LinearLayoutManager\r\nimport androidx.recyclerview.widget.RecyclerView\r\nimport kotlinx.coroutines.Job\r\nimport kotlinx.coroutines.delay\r\nimport kotlinx.coroutines.isActive\r\nimport kotlinx.coroutines.launch\r\n\r\nclass MainActivity : AppCompatActivity() {\r\n\r\n private lateinit var tvTimer : TextView\r\n private lateinit var btnLap : Button\r\n private lateinit var btnStart : Button\r\n private lateinit var btnStop : Button\r\n\r\n private lateinit var rvRecord : RecyclerView\r\n private lateinit var lapAdapter: LapAdapter\r\n\r\n private var isRunning = false\r\n private var runningTime = 0L\r\n private var job: Job? = null\r\n\r\n override fun onCreate(savedInstanceState: Bundle?) {\r\n super.onCreate(savedInstanceState)\r\n setContentView(R.layout.activity_main)\r\n\r\n tvTimer = findViewById(R.id.tv_main_timer)\r\n btnLap = findViewById(R.id.btn_main_lap)\r\n btnStart = findViewById(R.id.btn_main_start)\r\n btnStop = findViewById(R.id.btn_main_stop)\r\n rvRecord = findViewById(R.id.rv_main_record)\r\n rvRecord.layoutManager = LinearLayoutManager(this)\r\n lapAdapter = LapAdapter()\r\n rvRecord.adapter = lapAdapter\r\n\r\n btnStart.setOnClickListener {\r\n if (isRunning) {\r\n pauseTimer()\r\n } else {\r\n startTimer()\r\n }\r\n }\r\n\r\n btnStop.setOnClickListener {\r\n stopTimer()\r\n }\r\n\r\n btnLap.setOnClickListener {\r\n if (isRunning) {\r\n lapAdapter.addLap(formatTime(runningTime))\r\n }\r\n }\r\n }\r\n\r\n private fun startTimer() {\r\n val startTime = System.currentTimeMillis() - runningTime\r\n job = lifecycleScope.launch {\r\n while (isActive) {\r\n runningTime = System.currentTimeMillis() - startTime\r\n tvTimer.text = formatTime(runningTime)\r\n delay(10)\r\n }\r\n }\r\n isRunning = true\r\n btnStart.text = getString(R.string.btn_pause)\r\n }\r\n\r\n private fun pauseTimer() {\r\n job?.cancel()\r\n isRunning = false\r\n btnStart.text = getString(R.string.btn_start)\r\n }\r\n\r\n private fun stopTimer() {\r\n job?.cancel()\r\n runningTime = 0L\r\n isRunning = false\r\n tvTimer.text = getString(R.string.tv_time)\r\n btnStart.text = getString(R.string.btn_start)\r\n lapAdapter.clear()\r\n }\r\n\r\n private fun formatTime(ms: Long): String {\r\n val min = (ms/1000)/60\r\n val sec = (ms/1000)%60\r\n val millisec = (ms%1000) / 10\r\n return String.format(\"%02d:%02d:%02d\", min, sec, millisec)\r\n }\r\n\r\n\r\n}\r\n\r\n +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +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 +--- a/app/src/main/java/com/example/bcsd_android_2025_1/MainActivity.kt (revision a566fccc5cae9f9de481ed1c810a31555d2dca50) ++++ b/app/src/main/java/com/example/bcsd_android_2025_1/MainActivity.kt (date 1751870590293) +@@ -10,6 +10,7 @@ + import kotlinx.coroutines.delay + import kotlinx.coroutines.isActive + import kotlinx.coroutines.launch ++import java.util.Locale + + class MainActivity : AppCompatActivity() { + +@@ -89,7 +90,7 @@ + val min = (ms/1000)/60 + val sec = (ms/1000)%60 + val millisec = (ms%1000) / 10 +- return String.format("%02d:%02d:%02d", min, sec, millisec) ++ return String.format(Locale.getDefault(),"%02d:%02d:%02d", min, sec, millisec) + } + + +Index: app/src/main/res/layout/item.xml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP +<+>\r\n\r\n\r\n \r\n \r\n\r\n\r\n +=================================================================== +diff --git a/app/src/main/res/layout/item.xml b/app/src/main/res/layout/item.xml +--- a/app/src/main/res/layout/item.xml (revision a566fccc5cae9f9de481ed1c810a31555d2dca50) ++++ b/app/src/main/res/layout/item.xml (date 1752170182341) +@@ -1,5 +1,6 @@ + + + +@@ -18,4 +19,5 @@ + android:layout_marginStart="5dp"/> + + ++ + +\ No newline at end of file diff --git "a/.idea/shelf/Uncommitted_changes_before_Checkout_at_2025-07-13_\354\230\244\355\233\204_8_26_[Changes]1/shelved.patch" "b/.idea/shelf/Uncommitted_changes_before_Checkout_at_2025-07-13_\354\230\244\355\233\204_8_26_[Changes]1/shelved.patch" new file mode 100644 index 0000000..e69de29 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/.kotlin/errors/errors-1752480219597.log b/.kotlin/errors/errors-1752480219597.log new file mode 100644 index 0000000..a44172d --- /dev/null +++ b/.kotlin/errors/errors-1752480219597.log @@ -0,0 +1,57 @@ +kotlin version: 2.1.10 +error message: java.lang.IncompatibleClassChangeError: class com.google.devtools.ksp.common.PersistentMap cannot inherit from final class org.jetbrains.kotlin.com.intellij.util.io.PersistentHashMap + at java.base/java.lang.ClassLoader.defineClass1(Native Method) + at java.base/java.lang.ClassLoader.defineClass(Unknown Source) + at java.base/java.security.SecureClassLoader.defineClass(Unknown Source) + at java.base/java.net.URLClassLoader.defineClass(Unknown Source) + at java.base/java.net.URLClassLoader$1.run(Unknown Source) + at java.base/java.net.URLClassLoader$1.run(Unknown Source) + at java.base/java.security.AccessController.doPrivileged(Unknown Source) + at java.base/java.net.URLClassLoader.findClass(Unknown Source) + at java.base/java.lang.ClassLoader.loadClass(Unknown Source) + at java.base/java.lang.ClassLoader.loadClass(Unknown Source) + at java.base/java.lang.ClassLoader.defineClass1(Native Method) + at java.base/java.lang.ClassLoader.defineClass(Unknown Source) + at java.base/java.security.SecureClassLoader.defineClass(Unknown Source) + at java.base/java.net.URLClassLoader.defineClass(Unknown Source) + at java.base/java.net.URLClassLoader$1.run(Unknown Source) + at java.base/java.net.URLClassLoader$1.run(Unknown Source) + at java.base/java.security.AccessController.doPrivileged(Unknown Source) + at java.base/java.net.URLClassLoader.findClass(Unknown Source) + at java.base/java.lang.ClassLoader.loadClass(Unknown Source) + at java.base/java.lang.ClassLoader.loadClass(Unknown Source) + at com.google.devtools.ksp.common.IncrementalContextBase.(IncrementalContextBase.kt:103) + at com.google.devtools.ksp.IncrementalContext.(IncrementalContext.kt:64) + at com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension$doAnalysis$2.invoke(KotlinSymbolProcessingExtension.kt:192) + at com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension$doAnalysis$2.invoke(KotlinSymbolProcessingExtension.kt:189) + at com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension.handleException(KotlinSymbolProcessingExtension.kt:414) + at com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension.doAnalysis(KotlinSymbolProcessingExtension.kt:189) + at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(TopDownAnalyzerFacadeForJVM.kt:112) + at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration$default(TopDownAnalyzerFacadeForJVM.kt:75) + at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze$lambda$9(KotlinToJVMBytecodeCompiler.kt:356) + at org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport.analyzeAndReport(AnalyzerWithCompilerReport.kt:112) + at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze(KotlinToJVMBytecodeCompiler.kt:347) + at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.runFrontendAndGenerateIrUsingClassicFrontend(KotlinToJVMBytecodeCompiler.kt:177) + at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli(KotlinToJVMBytecodeCompiler.kt:102) + at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:169) + at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:43) + at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:102) + at org.jetbrains.kotlin.cli.common.CLICompiler.exec(CLICompiler.kt:316) + at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1706) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Unknown Source) + at java.base/java.lang.reflect.Method.invoke(Unknown Source) + at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(Unknown Source) + at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source) + at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source) + at java.base/java.security.AccessController.doPrivileged(Unknown Source) + at java.rmi/sun.rmi.transport.Transport.serviceCall(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(Unknown Source) + at java.base/java.security.AccessController.doPrivileged(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(Unknown Source) + at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source) + at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) + at java.base/java.lang.Thread.run(Unknown Source) + + diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 5791375..3435b9b 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,22 +1,34 @@ plugins { alias(libs.plugins.android.application) alias(libs.plugins.kotlin.android) + id("org.jetbrains.kotlin.kapt") + id("com.google.devtools.ksp") version "2.1.10-1.0.29" + + } android { namespace = "com.example.bcsd_android_2025_1" - compileSdk = 34 + compileSdk = 35 defaultConfig { applicationId = "com.example.bcsd_android_2025_1" minSdk = 26 - targetSdk = 34 + targetSdk = 35 versionCode = 1 versionName = "1.0" + multiDexEnabled = true + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } + buildFeatures { + dataBinding = true + viewBinding = true + } + + buildTypes { release { isMinifyEnabled = false @@ -33,10 +45,10 @@ android { kotlinOptions { jvmTarget = "11" } + } dependencies { - implementation(libs.androidx.core.ktx) implementation(libs.androidx.appcompat) implementation(libs.material) @@ -45,4 +57,18 @@ dependencies { testImplementation(libs.junit) androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) + + implementation(libs.androidx.room.runtime) + implementation(libs.androidx.room.ktx) + ksp(libs.androidx.room.compiler) + + implementation(libs.androidx.lifecycle.viewmodel.ktx) + implementation(libs.androidx.lifecycle.livedata.ktx) + implementation(libs.androidx.lifecycle.runtime.ktx) + implementation(libs.androidx.lifecycle.viewmodel.savedstate) + + implementation(libs.kotlinx.coroutines.android) + + implementation("com.github.bumptech.glide:glide:4.16.0") + kapt("com.github.bumptech.glide:compiler:4.16.0") } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4c80941..0a6bd37 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -12,8 +12,9 @@ android:supportsRtl="true" android:theme="@style/Theme.BCSD_Android_20251" tools:targetApi="31"> + 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 deleted file mode 100644 index 3ffa0eb..0000000 --- a/app/src/main/java/com/example/bcsd_android_2025_1/MainActivity.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.example.bcsd_android_2025_1 - -import android.os.Bundle -import androidx.activity.enableEdgeToEdge -import androidx.appcompat.app.AppCompatActivity -import androidx.core.view.ViewCompat -import androidx.core.view.WindowInsetsCompat - -class MainActivity : AppCompatActivity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.activity_main) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/example/bcsd_android_2025_1/data/model/Word.kt b/app/src/main/java/com/example/bcsd_android_2025_1/data/model/Word.kt new file mode 100644 index 0000000..1539a06 --- /dev/null +++ b/app/src/main/java/com/example/bcsd_android_2025_1/data/model/Word.kt @@ -0,0 +1,13 @@ +package com.example.bcsd_android_2025_1.data.model + +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, + val imageUri: String? = null +) : Serializable \ No newline at end of file diff --git a/app/src/main/java/com/example/bcsd_android_2025_1/data/repository/WordRepository.kt b/app/src/main/java/com/example/bcsd_android_2025_1/data/repository/WordRepository.kt new file mode 100644 index 0000000..885857e --- /dev/null +++ b/app/src/main/java/com/example/bcsd_android_2025_1/data/repository/WordRepository.kt @@ -0,0 +1,23 @@ + +package com.example.bcsd_android_2025_1.data.repository + +import androidx.lifecycle.LiveData +import com.example.bcsd_android_2025_1.data.model.Word +import com.example.bcsd_android_2025_1.data.room.WordDao + +class WordRepository(private val wordDao: WordDao) { + + fun getAllWords(): LiveData> = wordDao.getAllWords() + + suspend fun insert(word: Word) { + wordDao.insert(word) + } + + suspend fun update(word: Word) { + wordDao.update(word) + } + + suspend fun delete(word: Word) { + wordDao.delete(word) + } +} diff --git a/app/src/main/java/com/example/bcsd_android_2025_1/data/room/AppDatabase.kt b/app/src/main/java/com/example/bcsd_android_2025_1/data/room/AppDatabase.kt new file mode 100644 index 0000000..9d603de --- /dev/null +++ b/app/src/main/java/com/example/bcsd_android_2025_1/data/room/AppDatabase.kt @@ -0,0 +1,32 @@ +package com.example.bcsd_android_2025_1.data.room + +import android.content.Context +import androidx.room.Database +import androidx.room.Room +import androidx.room.RoomDatabase +import com.example.bcsd_android_2025_1.data.model.Word + +@Database(entities = [Word::class], version = 2, exportSchema = false) +abstract class AppDatabase : RoomDatabase() { + + abstract fun wordDao(): WordDao + + companion object { + @Volatile + private var INSTANCE: AppDatabase? = null + + fun getDatabase(context: Context): AppDatabase { + return INSTANCE ?: synchronized(this) { + val instance = Room.databaseBuilder( + context.applicationContext, + AppDatabase::class.java, + "word_database" + ) + .fallbackToDestructiveMigration(false) + .build() + INSTANCE = instance + instance + } + } + } +} diff --git a/app/src/main/java/com/example/bcsd_android_2025_1/data/room/WordDao.kt b/app/src/main/java/com/example/bcsd_android_2025_1/data/room/WordDao.kt new file mode 100644 index 0000000..c19121c --- /dev/null +++ b/app/src/main/java/com/example/bcsd_android_2025_1/data/room/WordDao.kt @@ -0,0 +1,21 @@ + +package com.example.bcsd_android_2025_1.data.room + +import androidx.lifecycle.LiveData +import androidx.room.* +import com.example.bcsd_android_2025_1.data.model.Word + +@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) +} diff --git a/app/src/main/java/com/example/bcsd_android_2025_1/presentation/view/AddEditActivity.kt b/app/src/main/java/com/example/bcsd_android_2025_1/presentation/view/AddEditActivity.kt new file mode 100644 index 0000000..ac1bab0 --- /dev/null +++ b/app/src/main/java/com/example/bcsd_android_2025_1/presentation/view/AddEditActivity.kt @@ -0,0 +1,81 @@ +package com.example.bcsd_android_2025_1.presentation.view + +import android.net.Uri +import android.os.Build +import android.os.Bundle +import androidx.activity.result.PickVisualMediaRequest +import androidx.activity.result.contract.ActivityResultContracts +import androidx.appcompat.app.AppCompatActivity +import com.bumptech.glide.Glide +import com.example.bcsd_android_2025_1.data.model.Word +import com.example.bcsd_android_2025_1.databinding.ActivityAddEditBinding + + +class AddEditActivity : AppCompatActivity() { + + private lateinit var binding: ActivityAddEditBinding + private var selectedImageUri: String? = null + private var editingWord: Word? = null + + private val pickImageLauncher = registerForActivityResult( + ActivityResultContracts.PickVisualMedia() + ) { uri -> + uri?.let { + selectedImageUri = it.toString() + binding.imagePreview.setImageURI(it) + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityAddEditBinding.inflate(layoutInflater) + setContentView(binding.root) + + editingWord = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + intent.getSerializableExtra("word_to_edit", Word::class.java) + } else { + @Suppress("DEPRECATION") + intent.getSerializableExtra("word_to_edit") as? Word + } + + editingWord?.let { word -> + binding.editTvAddWord.setText(word.word) + binding.editTvAddMean.setText(word.meaning) + selectedImageUri = word.imageUri + + if (!word.imageUri.isNullOrEmpty()) { + Glide.with(this) + .load(Uri.parse(word.imageUri)) + .into(binding.imagePreview) + } + + } + + binding.btnSelectImage.setOnClickListener { + pickImageLauncher.launch( + PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly) + ) + } + + binding.btnAddAdd.setOnClickListener { + val newWord = Word( + id = editingWord?.id ?: 0, + word = binding.editTvAddWord.text.toString(), + meaning = binding.editTvAddMean.text.toString(), + imageUri = selectedImageUri + ) + + val resultIntent = intent + if (editingWord != null) { + resultIntent.putExtra("updated_word", newWord) + } else { + resultIntent.putExtra("new_word", newWord) + } + + setResult(RESULT_OK,resultIntent) + finish() + } + + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/bcsd_android_2025_1/presentation/view/MainActivity.kt b/app/src/main/java/com/example/bcsd_android_2025_1/presentation/view/MainActivity.kt new file mode 100644 index 0000000..a9ec25b --- /dev/null +++ b/app/src/main/java/com/example/bcsd_android_2025_1/presentation/view/MainActivity.kt @@ -0,0 +1,103 @@ +package com.example.bcsd_android_2025_1.presentation.view + +import android.content.Intent +import android.os.Build +import android.os.Bundle +import androidx.activity.result.contract.ActivityResultContracts +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.ViewModelProvider +import androidx.recyclerview.widget.LinearLayoutManager +import com.example.bcsd_android_2025_1.WordAdapter +import com.example.bcsd_android_2025_1.data.model.Word +import com.example.bcsd_android_2025_1.data.repository.WordRepository +import com.example.bcsd_android_2025_1.data.room.AppDatabase +import com.example.bcsd_android_2025_1.databinding.ActivityMainBinding +import com.example.bcsd_android_2025_1.presentation.viewmodel.WordViewModel +import com.example.bcsd_android_2025_1.presentation.viewmodel.WordViewModelFactory + +class MainActivity : AppCompatActivity() { + private lateinit var binding: ActivityMainBinding + private lateinit var wordViewModel: WordViewModel + private lateinit var wordAdapter: WordAdapter + + private var selectedWord: Word? = null + + private val editWordLauncher = registerForActivityResult( + ActivityResultContracts.StartActivityForResult() + ) { result -> + if (result.resultCode == RESULT_OK) { + val updateWord = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + result.data?.getSerializableExtra("updated_word", Word::class.java) + } else { + result.data?.getSerializableExtra("updated_word") as? Word + } + updateWord?.let { + wordViewModel.update(it) + selectedWord = it + showSelectedWord(it) + } + + val newWord = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + result.data?.getSerializableExtra("new_word", Word::class.java) + } else { + result.data?.getSerializableExtra("new_word") as? Word + } + + newWord?.let { + wordViewModel.insert(it) + } + + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityMainBinding.inflate(layoutInflater) + setContentView(binding.root) + + val dao = AppDatabase.getDatabase(applicationContext).wordDao() + val repository = WordRepository(dao) + val factory = WordViewModelFactory(repository) + wordViewModel = ViewModelProvider(this, factory)[WordViewModel::class.java] + + wordAdapter = WordAdapter { word -> + selectedWord = word + showSelectedWord(word) + } + + binding.recyclerviewMain.adapter = wordAdapter + binding.recyclerviewMain.layoutManager = LinearLayoutManager(this) + + wordViewModel.allWords.observe(this) { words -> + wordAdapter.submitList(words) + } + + binding.imageBtnMainEdit.setOnClickListener { + selectedWord?.let { + val intent = Intent(this, AddEditActivity::class.java) + intent.putExtra("word_to_edit", it) + editWordLauncher.launch(intent) + } + } + + binding.imageBtnMainPlus.setOnClickListener { + val intent = Intent(this, AddEditActivity::class.java) + editWordLauncher.launch(intent) + } + + binding.imageBtnMainDelete.setOnClickListener { + selectedWord?.let { + wordViewModel.delete(it) + selectedWord = null + binding.tvMainWord.text = "" + binding.tvMainMean.text = "" + } + } + + } + + private fun showSelectedWord(word: Word) { + binding.tvMainWord.text = word.word + binding.tvMainMean.text = word.meaning + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/bcsd_android_2025_1/presentation/view/WordAdapter.kt b/app/src/main/java/com/example/bcsd_android_2025_1/presentation/view/WordAdapter.kt new file mode 100644 index 0000000..26b76ab --- /dev/null +++ b/app/src/main/java/com/example/bcsd_android_2025_1/presentation/view/WordAdapter.kt @@ -0,0 +1,54 @@ +package com.example.bcsd_android_2025_1 + +import android.net.Uri +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.bumptech.glide.Glide +import com.example.bcsd_android_2025_1.data.model.Word +import com.example.bcsd_android_2025_1.databinding.ItemWordBinding + +class WordAdapter( + private val onItemClick: (Word) -> Unit +) : ListAdapter(DiffCallback()) { + + inner class WordViewHolder(private val binding: ItemWordBinding) : + RecyclerView.ViewHolder(binding.root) { + + fun bind(word: Word) { + binding.itemTvWord.text = word.word + binding.itemTvMean.text = word.meaning + + if (!word.imageUri.isNullOrEmpty()) { + Glide.with(binding.root.context) + .load(Uri.parse(word.imageUri)) + .into(binding.itemIvPreview) + } else { + binding.itemIvPreview.setImageResource(R.drawable.placeholder_image) + } + + binding.root.setOnClickListener { onItemClick(word) } + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WordViewHolder { + val binding = ItemWordBinding.inflate(LayoutInflater.from(parent.context), 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): Boolean { + return oldItem.id == newItem.id + } + + override fun areContentsTheSame(oldItem: Word, newItem: Word): Boolean { + return oldItem == newItem + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/bcsd_android_2025_1/presentation/viewmodel/WordViewModel.kt b/app/src/main/java/com/example/bcsd_android_2025_1/presentation/viewmodel/WordViewModel.kt new file mode 100644 index 0000000..272a7ec --- /dev/null +++ b/app/src/main/java/com/example/bcsd_android_2025_1/presentation/viewmodel/WordViewModel.kt @@ -0,0 +1,45 @@ + +package com.example.bcsd_android_2025_1.presentation.viewmodel + +import androidx.lifecycle.LiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewModelScope +import com.example.bcsd_android_2025_1.data.model.Word +import com.example.bcsd_android_2025_1.data.repository.WordRepository +import kotlinx.coroutines.launch + +class WordViewModel(private val repository: WordRepository) : ViewModel() { + + val allWords: LiveData> = repository.getAllWords() + + 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) + } + } +} + +class WordViewModelFactory( + private val repository: WordRepository +) : ViewModelProvider.Factory { + + override fun create(modelClass: Class): T { + if (modelClass.isAssignableFrom(WordViewModel::class.java)) { + return WordViewModel(repository) as T + } + throw IllegalArgumentException("Unknown ViewModel class") + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/placeholder_image.xml b/app/src/main/res/drawable/placeholder_image.xml new file mode 100644 index 0000000..aaad839 --- /dev/null +++ b/app/src/main/res/drawable/placeholder_image.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_add_edit.xml b/app/src/main/res/layout/activity_add_edit.xml new file mode 100644 index 0000000..56c5732 --- /dev/null +++ b/app/src/main/res/layout/activity_add_edit.xml @@ -0,0 +1,83 @@ + + + + + + + + + + +