Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .idea/kotlinc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
id("kotlin-kapt")
}

android {
Expand All @@ -17,6 +18,11 @@ android {
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}

buildFeatures {
viewBinding = true
dataBinding = true
Comment on lines +22 to +23

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기서 질문! viewBinding과 dataBinding은 무슨 차이일까요?

}

buildTypes {
release {
isMinifyEnabled = false
Expand All @@ -42,7 +48,15 @@ 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)
implementation(libs.coil)
kapt(libs.androidx.room.compiler)
}
9 changes: 8 additions & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />

<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
Expand All @@ -13,13 +17,16 @@
android:theme="@style/Theme.Android_252"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:name="presentation.MainActivity"
android:exported="true" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name="presentation.SecondActivity"
android:exported="false" />
</application>

</manifest>
20 changes: 0 additions & 20 deletions app/src/main/java/com/example/android_25_2/MainActivity.kt

This file was deleted.

23 changes: 23 additions & 0 deletions app/src/main/java/data/WordDao.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package data

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<List<WordEntity>>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LiveData는 안드로이드 생명주기를 인식하고 있는 클래스입니다.
따라서, 안드로이드 생명주기 밖에서 동작하는 data layer에서 사용하기는 부적절해보입니다.

}
28 changes: 28 additions & 0 deletions app/src/main/java/data/WordDatabase.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package data

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
}
}
}
}
13 changes: 13 additions & 0 deletions app/src/main/java/data/WordEntity.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package data

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,
val imagePath: String
)
92 changes: 92 additions & 0 deletions app/src/main/java/presentation/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package presentation

import android.content.Intent
import android.os.Bundle
import android.view.View
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 coil.load
import com.example.android_25_2.R
import com.example.android_25_2.databinding.ActivityMainBinding
import data.WordEntity

class MainActivity : AppCompatActivity() {

private lateinit var activityBinding: ActivityMainBinding
private lateinit var wordViewModel: WordViewModel
private lateinit var adapter: WordAdapter
companion object {
const val EXTRA_WORD_ID = "WORD_ID"
const val EXTRA_WORD = "WORD"
const val EXTRA_MEANING = "MEANING"
const val EXTRA_IMAGE_PATH = "IMAGE_PATH"
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
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) {
showSelectedWord(word)
}

override fun onEditButtonClick(word: WordEntity) {
val intent = Intent(this@MainActivity, SecondActivity::class.java)
intent.putExtra(EXTRA_WORD_ID, word.id)
intent.putExtra(EXTRA_WORD, word.word)
intent.putExtra(EXTRA_MEANING, word.meaning)
intent.putExtra(EXTRA_IMAGE_PATH, word.imagePath)
startActivity(intent)
Comment on lines +60 to +64

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bundle을 사용하지 않은 이유가 있을까요?

}

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)
}
}
private fun showSelectedWord(word: WordEntity) {
activityBinding.textViewSelectedWord.visibility = View.VISIBLE
activityBinding.textViewSelectedMeaning.visibility = View.VISIBLE

activityBinding.textViewSelectedWord.text = word.word
activityBinding.textViewSelectedMeaning.text = word.meaning

word.imagePath.let {
activityBinding.imageViewSelected.load(it)
activityBinding.imageViewSelected.visibility = View.VISIBLE
}
}
}
131 changes: 131 additions & 0 deletions app/src/main/java/presentation/SecondActivity.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package presentation

import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.provider.Settings
import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.ViewModelProvider
import coil.load
import com.example.android_25_2.databinding.ActivitySecondBinding
import androidx.core.net.toUri
import android.Manifest
import com.example.android_25_2.R
import data.WordEntity

class SecondActivity : AppCompatActivity() {

private lateinit var activityBinding: ActivitySecondBinding
private lateinit var wordViewModel: WordViewModel
private var wordId: Int? = null
private var selectedImageUri: Uri? = null
lateinit var pickImage: ActivityResultLauncher<String>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

private 처리해주세요


override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
activityBinding = DataBindingUtil.setContentView(this, R.layout.activity_second)

pickImage = registerForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? ->
uri?.let {
selectedImageUri = it
activityBinding.imageViewPreview.load(it)
}
}

wordViewModel = ViewModelProvider(this)[WordViewModel::class.java]

wordId = intent.getIntExtra(MainActivity.EXTRA_WORD_ID, -1)
val existingWord = intent.getStringExtra(MainActivity.EXTRA_WORD)
val existingMeaning = intent.getStringExtra(MainActivity.EXTRA_MEANING)
val existingImagePath = intent.getStringExtra(MainActivity.EXTRA_IMAGE_PATH)

if (wordId != null) {
activityBinding.editTextWord.setText(existingWord)
activityBinding.editTextMeaning.setText(existingMeaning)
existingImagePath?.let {
activityBinding.imageViewPreview.load(it)
}
}

activityBinding.buttonSelectImage.setOnClickListener {
permissionCheck()
}

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
}

val imagePath: String = when {
selectedImageUri != null -> selectedImageUri.toString()
existingImagePath != null -> existingImagePath
else -> ""
}

if (wordId != null && wordId != -1) {
val wordEntity = WordEntity(
id = wordId!!,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wordId != null이 선언되었으니 타입 추론이 되지 않나요?

word = word,
meaning = meaning,
imagePath = imagePath
)
wordViewModel.update(wordEntity)
} else {
val wordEntity = WordEntity(
word = word,
meaning = meaning,
imagePath = imagePath
)
wordViewModel.insert(wordEntity)
}
finish()
}
}
private fun permissionCheck() {
val mediaPermission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
Manifest.permission.READ_MEDIA_IMAGES
} else {
Manifest.permission.READ_EXTERNAL_STORAGE
}

if (ContextCompat.checkSelfPermission(this, mediaPermission)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(
this, arrayOf(mediaPermission), 1000
)

if (!ActivityCompat.shouldShowRequestPermissionRationale(this, mediaPermission)) {
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
.setData("package:${packageName}".toUri())
startActivity(intent)
}
} else {
pickImage.launch("image/*")
}
}

override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == 1000) {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
pickImage.launch("image/*")
}
}
}
}
Loading