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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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 311f3cb..47a3943 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -1,19 +1,75 @@
-
-
-
-
-
\ No newline at end of file
+ xmlns:tools="http://schemas.android.com/tools">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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..c439e8b
--- /dev/null
+++ b/app/src/main/res/layout/item_word.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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 c6c4daf..23273dc 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1,3 +1,21 @@
BCSD_Android_2025-1
+ 기본 메모 제목
+ 기본 메모 내용
+ 삭제 버튼
+ 수정 버튼
+ 추가 버튼
+ 이미지 추가
+ 선택된 이미지 미리 보기
+
+ 단어 추가
+ 단어
+ 뜻
+ 추가
+
+ 단어를 입력하세요
+ 뜻을 입력하세요
+
+ 단어
+ 뜻
\ No newline at end of file
diff --git a/build.gradle.kts b/build.gradle.kts
index 922f551..1503fc2 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -2,4 +2,12 @@
plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.kotlin.android) apply false
+}
+
+buildscript {
+
+ dependencies {
+ classpath(libs.gradle)
+ classpath(libs.kotlin.gradle.plugin)
+ }
}
\ No newline at end of file
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 283fec9..07152a8 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,26 +1,47 @@
[versions]
-agp = "8.9.0"
-kotlin = "2.0.21"
-coreKtx = "1.13.1"
+agp = "8.9.3"
+gradle = "8.2.2"
+kotlin = "2.1.10"
+coreKtx = "1.16.0"
junit = "4.13.2"
junitVersion = "1.2.1"
espressoCore = "3.6.1"
-appcompat = "1.7.0"
+appcompat = "1.7.1"
+kotlinGradlePlugin = "1.9.0"
material = "1.12.0"
-activity = "1.9.3"
+activity = "1.10.1"
constraintlayout = "2.2.1"
+room = "2.7.2"
+lifecycle = "2.9.1"
+kotlinx-coroutines = "1.7.3"
+ksp = "2.0.0-1.0.20"
+
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
+gradle = { module = "com.android.tools.build:gradle", version.ref = "gradle" }
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" }
+kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlinGradlePlugin" }
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
androidx-activity = { group = "androidx.activity", name = "activity", version.ref = "activity" }
androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
+
+
+androidx-room-runtime = {module = "androidx.room:room-runtime",version.ref = "room"}
+androidx-room-ktx = {module = "androidx.room:room-ktx", version.ref = "room"}
+androidx-room-compiler = {module = "androidx.room:room-compiler", version.ref = "room"}
+
+androidx-lifecycle-viewmodel-ktx = {module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "lifecycle"}
+androidx-lifecycle-livedata-ktx = {module = "androidx.lifecycle:lifecycle-livedata-ktx", version.ref = "lifecycle"}
+androidx-lifecycle-runtime-ktx = {module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycle"}
+androidx-lifecycle-viewmodel-savedstate = {module = "androidx.lifecycle:lifecycle-viewmodel-savedstate", version.ref = "lifecycle"}
+
+kotlinx-coroutines-android = {module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinx-coroutines"}
+
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
-
+kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
\ No newline at end of file
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 6ddf3b7..f659462 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -1,5 +1,6 @@
pluginManagement {
repositories {
+ gradlePluginPortal()
google {
content {
includeGroupByRegex("com\\.android.*")