From adfbc47ac3bca2a431d3cf64992d244a81221a36 Mon Sep 17 00:00:00 2001 From: Glenn-syj Date: Thu, 26 Oct 2023 23:43:20 +0900 Subject: [PATCH 1/4] Minimum Spec Done. Working on layout files. --- assignment-3/.idea/.gitignore | 3 + assignment-3/.idea/.name | 1 + assignment-3/.idea/compiler.xml | 6 + assignment-3/.idea/gradle.xml | 20 ++++ assignment-3/.idea/kotlinc.xml | 6 + assignment-3/.idea/misc.xml | 9 ++ assignment-3/.idea/vcs.xml | 6 + assignment-3/app/build.gradle.kts | 26 ++++ assignment-3/app/src/main/AndroidManifest.xml | 11 ++ .../com/jutak/assignment3/DetailActivity.kt | 65 ++++++++++ .../com/jutak/assignment3/MainActivity.kt | 73 ++++++++++- .../com/jutak/assignment3/MainRepository.kt | 21 ++++ .../com/jutak/assignment3/MainViewModel.kt | 68 +++++++++++ .../com/jutak/assignment3/MyApplication.kt | 8 ++ .../main/java/com/jutak/assignment3/MyData.kt | 48 ++++++++ .../java/com/jutak/assignment3/MyRestAPI.kt | 18 +++ .../com/jutak/assignment3/NetworkModule.kt | 46 +++++++ .../jutak/assignment3/OnItemClickListener.kt | 5 + .../com/jutak/assignment3/RepsitoryModule.kt | 17 +++ .../com/jutak/assignment3/WordListAdapter.kt | 113 ++++++++++++++++++ .../app/src/main/res/drawable/back_arrow.png | Bin 0 -> 2422 bytes .../app/src/main/res/drawable/write_icon.png | Bin 0 -> 11070 bytes .../src/main/res/layout/activity_detail.xml | 58 +++++++++ .../app/src/main/res/layout/activity_main.xml | 84 ++++++++++++- .../main/res/layout/custom_dialog_layout.xml | 30 +++++ .../main/res/layout/custom_title_layout.xml | 23 ++++ .../main/res/layout/post_dialog_layout.xml | 65 ++++++++++ .../app/src/main/res/layout/word_list.xml | 28 +++++ .../src/main/res/layout/word_list_info.xml | 34 ++++++ assignment-3/build.gradle.kts | 8 +- 30 files changed, 892 insertions(+), 8 deletions(-) create mode 100644 assignment-3/.idea/.gitignore create mode 100644 assignment-3/.idea/.name create mode 100644 assignment-3/.idea/compiler.xml create mode 100644 assignment-3/.idea/gradle.xml create mode 100644 assignment-3/.idea/kotlinc.xml create mode 100644 assignment-3/.idea/misc.xml create mode 100644 assignment-3/.idea/vcs.xml create mode 100644 assignment-3/app/src/main/java/com/jutak/assignment3/DetailActivity.kt create mode 100644 assignment-3/app/src/main/java/com/jutak/assignment3/MainRepository.kt create mode 100644 assignment-3/app/src/main/java/com/jutak/assignment3/MainViewModel.kt create mode 100644 assignment-3/app/src/main/java/com/jutak/assignment3/MyApplication.kt create mode 100644 assignment-3/app/src/main/java/com/jutak/assignment3/MyData.kt create mode 100644 assignment-3/app/src/main/java/com/jutak/assignment3/MyRestAPI.kt create mode 100644 assignment-3/app/src/main/java/com/jutak/assignment3/NetworkModule.kt create mode 100644 assignment-3/app/src/main/java/com/jutak/assignment3/OnItemClickListener.kt create mode 100644 assignment-3/app/src/main/java/com/jutak/assignment3/RepsitoryModule.kt create mode 100644 assignment-3/app/src/main/java/com/jutak/assignment3/WordListAdapter.kt create mode 100644 assignment-3/app/src/main/res/drawable/back_arrow.png create mode 100644 assignment-3/app/src/main/res/drawable/write_icon.png create mode 100644 assignment-3/app/src/main/res/layout/activity_detail.xml create mode 100644 assignment-3/app/src/main/res/layout/custom_dialog_layout.xml create mode 100644 assignment-3/app/src/main/res/layout/custom_title_layout.xml create mode 100644 assignment-3/app/src/main/res/layout/post_dialog_layout.xml create mode 100644 assignment-3/app/src/main/res/layout/word_list.xml create mode 100644 assignment-3/app/src/main/res/layout/word_list_info.xml diff --git a/assignment-3/.idea/.gitignore b/assignment-3/.idea/.gitignore new file mode 100644 index 00000000..26d33521 --- /dev/null +++ b/assignment-3/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/assignment-3/.idea/.name b/assignment-3/.idea/.name new file mode 100644 index 00000000..d44d7837 --- /dev/null +++ b/assignment-3/.idea/.name @@ -0,0 +1 @@ +Assignment3 \ No newline at end of file diff --git a/assignment-3/.idea/compiler.xml b/assignment-3/.idea/compiler.xml new file mode 100644 index 00000000..b589d56e --- /dev/null +++ b/assignment-3/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/assignment-3/.idea/gradle.xml b/assignment-3/.idea/gradle.xml new file mode 100644 index 00000000..ae388c2a --- /dev/null +++ b/assignment-3/.idea/gradle.xml @@ -0,0 +1,20 @@ + + + + + + + \ No newline at end of file diff --git a/assignment-3/.idea/kotlinc.xml b/assignment-3/.idea/kotlinc.xml new file mode 100644 index 00000000..fdf8d994 --- /dev/null +++ b/assignment-3/.idea/kotlinc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/assignment-3/.idea/misc.xml b/assignment-3/.idea/misc.xml new file mode 100644 index 00000000..8978d23d --- /dev/null +++ b/assignment-3/.idea/misc.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/assignment-3/.idea/vcs.xml b/assignment-3/.idea/vcs.xml new file mode 100644 index 00000000..6c0b8635 --- /dev/null +++ b/assignment-3/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/assignment-3/app/build.gradle.kts b/assignment-3/app/build.gradle.kts index d4e95d33..112ce42c 100644 --- a/assignment-3/app/build.gradle.kts +++ b/assignment-3/app/build.gradle.kts @@ -1,6 +1,10 @@ + + plugins { id("com.android.application") id("org.jetbrains.kotlin.android") + id("kotlin-kapt") + id("dagger.hilt.android.plugin") } android { @@ -17,6 +21,10 @@ android { testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } + buildFeatures { + viewBinding = true + } + buildTypes { release { isMinifyEnabled = false @@ -35,12 +43,30 @@ android { } } + + + dependencies { implementation("androidx.core:core-ktx:1.12.0") implementation("androidx.appcompat:appcompat:1.6.1") + implementation("androidx.fragment:fragment-ktx:1.5.7") + implementation("androidx.activity:activity-ktx:1.7.1") implementation("com.google.android.material:material:1.9.0") + implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0") implementation("androidx.constraintlayout:constraintlayout:2.1.4") + // OkHttp + implementation("com.squareup.okhttp3:okhttp:4.10.0") + // Retrofit + implementation("com.squareup.retrofit2:retrofit:2.9.0") + implementation("com.squareup.retrofit2:converter-moshi:2.9.0") + // moshi + implementation("com.squareup.moshi:moshi:1.14.0") + implementation("com.squareup.moshi:moshi-kotlin:1.14.0") + + implementation("com.google.dagger:hilt-android:2.47") + kapt("com.google.dagger:hilt-android-compiler:2.47") + testImplementation("junit:junit:4.13.2") androidTestImplementation("androidx.test.ext:junit:1.1.5") androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") diff --git a/assignment-3/app/src/main/AndroidManifest.xml b/assignment-3/app/src/main/AndroidManifest.xml index 0662af62..f6c4c9c1 100644 --- a/assignment-3/app/src/main/AndroidManifest.xml +++ b/assignment-3/app/src/main/AndroidManifest.xml @@ -2,7 +2,10 @@ + + + + + + \ No newline at end of file diff --git a/assignment-3/app/src/main/java/com/jutak/assignment3/DetailActivity.kt b/assignment-3/app/src/main/java/com/jutak/assignment3/DetailActivity.kt new file mode 100644 index 00000000..79a47564 --- /dev/null +++ b/assignment-3/app/src/main/java/com/jutak/assignment3/DetailActivity.kt @@ -0,0 +1,65 @@ +package com.jutak.assignment3 + +import android.content.Intent +import android.os.Bundle +import android.util.Log +import android.widget.TextView +import androidx.activity.viewModels +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.lifecycleScope +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView.LayoutManager +import com.jutak.assignment3.databinding.ActivityDetailBinding +import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.launch + +@AndroidEntryPoint +class DetailActivity : AppCompatActivity(), OnItemClickListener { + + private lateinit var binding: ActivityDetailBinding + private val viewModel: MainViewModel by viewModels() + + override fun onItemClick(itemId: Int) { + // 클릭한 아이템의 ID를 사용하여 필요한 동작 수행 + // 예: 상세 화면으로 전환하면서 ID를 전달 + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityDetailBinding.inflate(layoutInflater) + setContentView(binding.root) + + // Intent로 전달된 데이터 가져오기 + val itemId = intent.getIntExtra("itemId", 1) + val recAdapter = WordListAdapter(this, viewModel.getWordInfos(itemId), this) + + val backButton = binding.backButton + backButton.setOnClickListener() { + onBackPressed() + } + + binding.wordInfo.adapter = recAdapter + binding.wordInfo.layoutManager = LinearLayoutManager(this) + + viewModel.wordListData.observe(this) { wordList -> + // LiveData를 관찰하여 데이터를 얻고, adapter를 업데이트 + Log.d("DetailActivity", "Word list size: ${wordList.size}") // 디버그 로그 추가 + recAdapter.setItems(wordList) + recAdapter.notifyDataSetChanged() + } + +// lifecycleScope.launch { +// try { +// val wordList = viewModel.getWordInfos(itemId) // 데이터 로딩 (비동기 작업) +// recAdapter.setItems(wordList) +// } catch (e: Exception) { +// // 오류 처리 +// Log.e("DetailActivity", "Error loading data: ${e.message}") +// } +// } + + // 가져온 데이터를 상세 화면에 표시 +// binding.detailTextView.text = "Item ID: $itemId" // 상세 정보에 따라 데이터 표시 방식을 수정 + + } +} \ No newline at end of file diff --git a/assignment-3/app/src/main/java/com/jutak/assignment3/MainActivity.kt b/assignment-3/app/src/main/java/com/jutak/assignment3/MainActivity.kt index 9181f8df..6bcbae45 100644 --- a/assignment-3/app/src/main/java/com/jutak/assignment3/MainActivity.kt +++ b/assignment-3/app/src/main/java/com/jutak/assignment3/MainActivity.kt @@ -1,11 +1,80 @@ package com.jutak.assignment3 +import android.content.Intent import androidx.appcompat.app.AppCompatActivity import android.os.Bundle +import android.util.Log +import androidx.activity.viewModels +import androidx.appcompat.app.AlertDialog +import androidx.lifecycle.ViewModelProvider +import androidx.recyclerview.widget.LinearLayoutManager +import com.jutak.assignment3.databinding.ActivityMainBinding +import com.jutak.assignment3.databinding.CustomTitleLayoutBinding +import com.jutak.assignment3.databinding.PostDialogLayoutBinding +import dagger.hilt.android.AndroidEntryPoint +import javax.inject.Inject -class MainActivity : AppCompatActivity() { + +@AndroidEntryPoint +class MainActivity : AppCompatActivity(), OnItemClickListener { + + private lateinit var binding: ActivityMainBinding + + private val viewModel: MainViewModel by viewModels() + + override fun onItemClick(itemId: Int) { + // 클릭한 아이템의 ID를 사용하여 필요한 동작 수행 + // 예: 상세 화면으로 전환하면서 ID를 전달 + val intent = Intent(this, DetailActivity::class.java) + intent.putExtra("itemId", itemId) + startActivity(intent) + } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContentView(R.layout.activity_main) + binding = ActivityMainBinding.inflate(layoutInflater) + setContentView(binding.root) + + binding.postBtn.setOnClickListener() { + showInputDialog() + } + + val adapter = WordListAdapter(this, viewModel.getWordListInfo(), this) + binding.wordList.adapter= adapter + binding.wordList.layoutManager = LinearLayoutManager(this) + + viewModel.wordListInfoData.observe(this) { wordListInfo -> + // LiveData를 관찰하여 데이터를 얻고, adapter를 업데이트 + Log.d("DetailActivity", "Word list size: ${wordListInfo.size}") // 디버그 로그 추가 + adapter.setItems(wordListInfo) + } + + + } + + private fun showInputDialog() { + val dialogBinding = PostDialogLayoutBinding.inflate(layoutInflater) + val customTitleLayoutBinding = CustomTitleLayoutBinding.inflate(layoutInflater) + + val dialog = AlertDialog.Builder(this) + .setView(dialogBinding.root) + .setCustomTitle(customTitleLayoutBinding.root) + .setPositiveButton("Submit") { dialog, _ -> + val owner = dialogBinding.ownerEditText.text.toString() + val name = dialogBinding.nameEditText.text.toString() + val password = dialogBinding.passwordEditText.text.toString() + + // 데이터 처리 (예: 로그로 출력) + val message = MyData.WordListPostInfo(name, owner, password) + viewModel.pushWordList(message) + dialog.dismiss() + } + .setNegativeButton("Cancel") { dialog, _ -> + dialog.dismiss() + } + .create() + + dialog.show() } + + } \ No newline at end of file diff --git a/assignment-3/app/src/main/java/com/jutak/assignment3/MainRepository.kt b/assignment-3/app/src/main/java/com/jutak/assignment3/MainRepository.kt new file mode 100644 index 00000000..337f4233 --- /dev/null +++ b/assignment-3/app/src/main/java/com/jutak/assignment3/MainRepository.kt @@ -0,0 +1,21 @@ +package com.jutak.assignment3 + +import dagger.hilt.android.AndroidEntryPoint +import retrofit2.Call +import javax.inject.Inject + +class MainRepository @Inject constructor(private val api: MyRestAPI) { + + suspend fun getWordListInfo(): MutableList { + return api.getWordListInfo().toMutableList() + } + + suspend fun getWordList(id: Int): MyData.WordList { + return api.getWordList(id) + } + + suspend fun pushWordList(data: MyData.WordListPostInfo): List { + return api.createWordList(data) + } + +} \ No newline at end of file diff --git a/assignment-3/app/src/main/java/com/jutak/assignment3/MainViewModel.kt b/assignment-3/app/src/main/java/com/jutak/assignment3/MainViewModel.kt new file mode 100644 index 00000000..8001ca10 --- /dev/null +++ b/assignment-3/app/src/main/java/com/jutak/assignment3/MainViewModel.kt @@ -0,0 +1,68 @@ +package com.jutak.assignment3 + +import androidx.lifecycle.ViewModel +import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject +import android.util.Log +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.Observer +import androidx.lifecycle.viewModelScope +import com.jutak.assignment3.MainRepository +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +@HiltViewModel +class MainViewModel @Inject constructor(private val repository: MainRepository) : ViewModel() { + + private val _wordListInfoData = MutableLiveData>() + val wordListInfoData: LiveData> get() = _wordListInfoData + private val _wordListData = MutableLiveData>() + val wordListData: LiveData> get() = _wordListData + // ViewModel에서 사용할 메서드 및 데이터를 정의 + + private var wordListInfo = mutableListOf() + private var wordInfos = mutableListOf() + + fun getWordListInfo(): MutableList { + fetchWordListInfo() + return wordListInfo + } + + fun getWordInfos(id: Int): MutableList { + fetchWordList(id) + return wordInfos + } + + fun fetchWordListInfo() { + viewModelScope.launch(Dispatchers.IO) { + wordListInfo = repository.getWordListInfo() + withContext(Dispatchers.Main) { + _wordListInfoData.value = wordListInfo + Log.d("MainViewModel", "WordListInfos Size: ${wordListInfo.size}") // 디버그 로그 추가 + } + } + } + + fun fetchWordList(id: Int) { + viewModelScope.launch(Dispatchers.IO) { + wordInfos = repository.getWordList(id).word_list.toMutableList() + Log.d("MainViewModel", "Word list size: ${wordInfos.size}") // 디버그 로그 추가 + withContext(Dispatchers.Main) { + _wordListData.setValue(wordInfos) + } + } + } + + fun pushWordList(data: MyData.WordListPostInfo) { + viewModelScope.launch(Dispatchers.IO) { + val response = repository.pushWordList(data).toMutableList() + withContext(Dispatchers.Main) { + _wordListInfoData.value = response + Log.d("MainViewModel", "WordListInfos Size: ${response.size}") // 디버그 로그 추가 + } + } + } +} \ No newline at end of file diff --git a/assignment-3/app/src/main/java/com/jutak/assignment3/MyApplication.kt b/assignment-3/app/src/main/java/com/jutak/assignment3/MyApplication.kt new file mode 100644 index 00000000..255676e6 --- /dev/null +++ b/assignment-3/app/src/main/java/com/jutak/assignment3/MyApplication.kt @@ -0,0 +1,8 @@ +package com.jutak.assignment3 + +import android.app.Application +import okhttp3.OkHttpClient +import dagger.hilt.android.HiltAndroidApp + +@HiltAndroidApp +class MyApplication: Application() \ No newline at end of file diff --git a/assignment-3/app/src/main/java/com/jutak/assignment3/MyData.kt b/assignment-3/app/src/main/java/com/jutak/assignment3/MyData.kt new file mode 100644 index 00000000..508789e7 --- /dev/null +++ b/assignment-3/app/src/main/java/com/jutak/assignment3/MyData.kt @@ -0,0 +1,48 @@ +package com.jutak.assignment3 + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +sealed class MyData(val viewType: ViewType) { + + @JsonClass(generateAdapter = true) + data class WordListInfo( + @Json(name="id") val id: Int, + @Json(name="name") val name: String, + @Json(name="owner") val owner: String, + ) : MyData(ViewType.WORD_LIST_INFO) + + @JsonClass(generateAdapter = true) + data class WordList( + @Json(name="id") val id: Int, + @Json(name="name") val name: String, + @Json(name="owner") val owner: String, + @Json(name="word_list") val word_list: List + ) : MyData(ViewType.WORD_LIST) + + @JsonClass(generateAdapter = true) + data class WordInfo( + @Json(name="spell") val spell: String, + @Json(name="meaning") val meaning: String, + @Json(name="synonym") val synonym: String, + @Json(name="antonym") val antonym: String, + @Json(name="sentence") val sentence: String + ) : MyData(ViewType.WORD_INFO) + + @JsonClass(generateAdapter = true) + data class WordListPostInfo( + @Json(name="name") val name: String, + @Json(name="owner") val owner: String, + @Json(name="password") val word_list: String + ) : MyData(ViewType.WORD_LIST_POST_INFO) + + + + enum class ViewType { + WORD_LIST_INFO, + WORD_LIST, + WORD_INFO, + WORD_LIST_POST_INFO, + } +} diff --git a/assignment-3/app/src/main/java/com/jutak/assignment3/MyRestAPI.kt b/assignment-3/app/src/main/java/com/jutak/assignment3/MyRestAPI.kt new file mode 100644 index 00000000..3ea36f76 --- /dev/null +++ b/assignment-3/app/src/main/java/com/jutak/assignment3/MyRestAPI.kt @@ -0,0 +1,18 @@ +package com.jutak.assignment3 +import retrofit2.Call +import retrofit2.http.Body +import retrofit2.http.GET +import retrofit2.http.POST +import retrofit2.http.Path + +interface MyRestAPI { + @GET("/myapp/v1/word_lists") + suspend fun getWordListInfo(): List + + @GET("/myapp/v1/word_list/{id}") + suspend fun getWordList(@Path("id") id: Int): MyData.WordList + + @POST("/myapp/v1/word_list") + suspend fun createWordList(@Body data: MyData.WordListPostInfo): List + +} \ No newline at end of file diff --git a/assignment-3/app/src/main/java/com/jutak/assignment3/NetworkModule.kt b/assignment-3/app/src/main/java/com/jutak/assignment3/NetworkModule.kt new file mode 100644 index 00000000..ff0c1100 --- /dev/null +++ b/assignment-3/app/src/main/java/com/jutak/assignment3/NetworkModule.kt @@ -0,0 +1,46 @@ +package com.jutak.assignment3 + +import com.squareup.moshi.Moshi +import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import okhttp3.OkHttpClient +import retrofit2.Retrofit +import retrofit2.converter.moshi.MoshiConverterFactory + +@Module +@InstallIn(SingletonComponent::class) // 앱 전체 수명 주기 동안 단일 인스턴스 (싱글톤) +class NetworkModule { + @Provides // OkHttpClient 타입의 객체를 어떻게 만드는 지 dagger에게 알려 준다. + fun provideOkHttpClient(): OkHttpClient { + return OkHttpClient.Builder().build() + } + + @Provides + fun provideMoshi(): Moshi { + return Moshi.Builder().add(KotlinJsonAdapterFactory()) + .build() + } + + @Provides + fun provideRetrofit( + okHttpClient: OkHttpClient, + moshi: Moshi, + ): Retrofit { + return Retrofit.Builder().baseUrl("http://ec2-13-209-69-159.ap-northeast-2.compute.amazonaws.com:8000/myapp/v1/") + .client(okHttpClient) + .addConverterFactory(MoshiConverterFactory.create(moshi)) + .build() + + } + + @Provides + fun MyRestAPI( + retrofit: Retrofit, + ): MyRestAPI { + return retrofit.create(MyRestAPI::class.java) + } + +} \ No newline at end of file diff --git a/assignment-3/app/src/main/java/com/jutak/assignment3/OnItemClickListener.kt b/assignment-3/app/src/main/java/com/jutak/assignment3/OnItemClickListener.kt new file mode 100644 index 00000000..a887adb6 --- /dev/null +++ b/assignment-3/app/src/main/java/com/jutak/assignment3/OnItemClickListener.kt @@ -0,0 +1,5 @@ +package com.jutak.assignment3 + +interface OnItemClickListener { + fun onItemClick(itemId: Int) +} \ No newline at end of file diff --git a/assignment-3/app/src/main/java/com/jutak/assignment3/RepsitoryModule.kt b/assignment-3/app/src/main/java/com/jutak/assignment3/RepsitoryModule.kt new file mode 100644 index 00000000..f63ecb7e --- /dev/null +++ b/assignment-3/app/src/main/java/com/jutak/assignment3/RepsitoryModule.kt @@ -0,0 +1,17 @@ +package com.jutak.assignment3 + +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object RepositoryModule { + @Provides + @Singleton + fun provideMainRepository(api: MyRestAPI): MainRepository { + return MainRepository(api) + } +} \ No newline at end of file diff --git a/assignment-3/app/src/main/java/com/jutak/assignment3/WordListAdapter.kt b/assignment-3/app/src/main/java/com/jutak/assignment3/WordListAdapter.kt new file mode 100644 index 00000000..a0c0c341 --- /dev/null +++ b/assignment-3/app/src/main/java/com/jutak/assignment3/WordListAdapter.kt @@ -0,0 +1,113 @@ +package com.jutak.assignment3 + +import android.content.Context +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.appcompat.app.AlertDialog +import androidx.recyclerview.widget.RecyclerView +import com.jutak.assignment3.databinding.CustomDialogLayoutBinding +import com.jutak.assignment3.databinding.WordListBinding +import com.jutak.assignment3.databinding.WordListInfoBinding + +class WordListAdapter(private val context:Context, private var items: MutableList, private val itemClickListener: OnItemClickListener) : RecyclerView.Adapter() { + + fun setItems(newItems: MutableList) { + items = newItems + notifyDataSetChanged() + } + + private fun showItemDialog(item: MyData.WordInfo) { + val customLayoutBinding = CustomDialogLayoutBinding.inflate(LayoutInflater.from(context)) + + // 다이얼로그 생성 및 커스텀 레이아웃 설정 + val alertDialog = AlertDialog.Builder(context) + .setView(customLayoutBinding.root) + .create() + + // 다이얼로그 내용 설정 + customLayoutBinding.dialogText.text = "Spell: ${item.spell}" + + "\nMeaning: ${item.meaning}" + + "\nSynonym: ${item.synonym}" + + "\nAntonym: ${item.antonym}" + + "\nSentence: ${item.sentence}" + + // Positive Button 클릭 리스너 설정 + customLayoutBinding.positiveButton.setOnClickListener { + // Positive Button을 눌렀을 때의 동작을 여기에 추가 + alertDialog.dismiss() // 다이얼로그 닫기 + } + // 다이얼로그 표시 + alertDialog.show() + } + + inner class WordListInfoViewHolder(private val binding: WordListInfoBinding) : + RecyclerView.ViewHolder(binding.root) { + + fun bind(item: MyData.WordListInfo) { + binding.wordListOwner.text = item.owner + binding.wordListName.text = item.name + } + } + + inner class WordListViewHolder(private val binding: WordListBinding) : + RecyclerView.ViewHolder(binding.root) { + + fun bind(item: MyData.WordInfo) { + binding.wordSpell.text = item.spell + binding.wordMeaning.text = item.meaning + binding.root.setOnClickListener { + showItemDialog(item) + } + } + + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + val inflater = LayoutInflater.from(parent.context) + return when (viewType) { + MyData.ViewType.WORD_LIST_INFO.ordinal -> { + val binding = WordListInfoBinding.inflate(LayoutInflater.from(parent.context), parent, false) + WordListInfoViewHolder(binding) + } + MyData.ViewType.WORD_INFO.ordinal -> { + val binding = WordListBinding.inflate(LayoutInflater.from(parent.context), parent, false) + WordListViewHolder(binding) + } + else -> throw IllegalArgumentException("Invalid view type") + } + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + when (holder) { + is WordListAdapter<*>.WordListInfoViewHolder -> { + val item = items[position] as MyData.WordListInfo + val layoutParams = holder.itemView.layoutParams + layoutParams.height = 100 + holder.itemView.requestLayout() + holder.bind(item) + holder.itemView.setOnClickListener { + itemClickListener.onItemClick(item.id) // 아이템의 ID를 전달 + } + } + is WordListAdapter<*>.WordListViewHolder -> { + val item = items[position] as MyData.WordInfo + val layoutParams = holder.itemView.layoutParams + layoutParams.height = 120 + holder.itemView.requestLayout() + holder.bind(item) + } + } + } + + override fun getItemCount(): Int { + return items.size + } + + override fun getItemViewType(position: Int): Int { + return when (items[position]) { + is MyData.WordListInfo-> MyData.ViewType.WORD_LIST_INFO.ordinal + is MyData.WordInfo -> MyData.ViewType.WORD_INFO.ordinal + else -> throw IllegalArgumentException("Invalid item type") + } + } +} \ No newline at end of file diff --git a/assignment-3/app/src/main/res/drawable/back_arrow.png b/assignment-3/app/src/main/res/drawable/back_arrow.png new file mode 100644 index 0000000000000000000000000000000000000000..ebca5484d66353b2b6f166c9a83e9fcd55998787 GIT binary patch literal 2422 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 literal 0 HcmV?d00001 diff --git a/assignment-3/app/src/main/res/drawable/write_icon.png b/assignment-3/app/src/main/res/drawable/write_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..53c3cf69a18348cd5a5fdb2bb360625d210b3e81 GIT binary patch literal 11070 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 Zt&!a|!%K0e!ChB~Y + + + + + + + + + + + + + \ No newline at end of file diff --git a/assignment-3/app/src/main/res/layout/activity_main.xml b/assignment-3/app/src/main/res/layout/activity_main.xml index 17eab17b..09b15de3 100644 --- a/assignment-3/app/src/main/res/layout/activity_main.xml +++ b/assignment-3/app/src/main/res/layout/activity_main.xml @@ -6,13 +6,87 @@ android:layout_height="match_parent" tools:context=".MainActivity"> - + + + + + + + + + + + + + + app:layout_constraintEnd_toEndOf="parent" + > + \ No newline at end of file diff --git a/assignment-3/app/src/main/res/layout/custom_dialog_layout.xml b/assignment-3/app/src/main/res/layout/custom_dialog_layout.xml new file mode 100644 index 00000000..6cf059ac --- /dev/null +++ b/assignment-3/app/src/main/res/layout/custom_dialog_layout.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assignment-3/app/src/main/res/layout/custom_title_layout.xml b/assignment-3/app/src/main/res/layout/custom_title_layout.xml new file mode 100644 index 00000000..74ba97a0 --- /dev/null +++ b/assignment-3/app/src/main/res/layout/custom_title_layout.xml @@ -0,0 +1,23 @@ + + + + + + + + + \ No newline at end of file diff --git a/assignment-3/app/src/main/res/layout/post_dialog_layout.xml b/assignment-3/app/src/main/res/layout/post_dialog_layout.xml new file mode 100644 index 00000000..0a8e5f16 --- /dev/null +++ b/assignment-3/app/src/main/res/layout/post_dialog_layout.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assignment-3/app/src/main/res/layout/word_list.xml b/assignment-3/app/src/main/res/layout/word_list.xml new file mode 100644 index 00000000..c0ae233e --- /dev/null +++ b/assignment-3/app/src/main/res/layout/word_list.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/assignment-3/app/src/main/res/layout/word_list_info.xml b/assignment-3/app/src/main/res/layout/word_list_info.xml new file mode 100644 index 00000000..93c4d495 --- /dev/null +++ b/assignment-3/app/src/main/res/layout/word_list_info.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/assignment-3/build.gradle.kts b/assignment-3/build.gradle.kts index ee49ce0e..1b5ff79e 100644 --- a/assignment-3/build.gradle.kts +++ b/assignment-3/build.gradle.kts @@ -1,5 +1,11 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.2.0-beta01" apply false + id("com.android.application") version "8.1.1" apply false id("org.jetbrains.kotlin.android") version "1.9.0" apply false +} + +buildscript { + dependencies { + classpath("com.google.dagger:hilt-android-gradle-plugin:2.47") + } } \ No newline at end of file From 658317bc1e76024fe0122b91ab9f5dc58db9ba03 Mon Sep 17 00:00:00 2001 From: Glenn-syj Date: Fri, 27 Oct 2023 18:33:26 +0900 Subject: [PATCH 2/4] Additional Spec Implemented wo/ Debugging. --- .../com/jutak/assignment3/DetailActivity.kt | 99 +++++++++++++++--- .../com/jutak/assignment3/MainRepository.kt | 13 +++ .../com/jutak/assignment3/MainViewModel.kt | 40 +++++++ .../main/java/com/jutak/assignment3/MyData.kt | 6 +- .../java/com/jutak/assignment3/MyRestAPI.kt | 12 +++ .../app/src/main/res/drawable/plus_icon.png | Bin 0 -> 3992 bytes .../src/main/res/drawable/trash_bin_icon.png | Bin 0 -> 12570 bytes .../src/main/res/layout/activity_detail.xml | 47 ++++++++- .../app/src/main/res/layout/activity_main.xml | 2 +- .../main/res/layout/edit_dialog_layout.xml | 37 +++++++ .../res/layout/word_add_dialog_layout.xml | 92 ++++++++++++++++ .../app/src/main/res/layout/word_list.xml | 27 ++++- .../src/main/res/layout/word_list_info.xml | 13 ++- 13 files changed, 360 insertions(+), 28 deletions(-) create mode 100644 assignment-3/app/src/main/res/drawable/plus_icon.png create mode 100644 assignment-3/app/src/main/res/drawable/trash_bin_icon.png create mode 100644 assignment-3/app/src/main/res/layout/edit_dialog_layout.xml create mode 100644 assignment-3/app/src/main/res/layout/word_add_dialog_layout.xml diff --git a/assignment-3/app/src/main/java/com/jutak/assignment3/DetailActivity.kt b/assignment-3/app/src/main/java/com/jutak/assignment3/DetailActivity.kt index 79a47564..46d4b90c 100644 --- a/assignment-3/app/src/main/java/com/jutak/assignment3/DetailActivity.kt +++ b/assignment-3/app/src/main/java/com/jutak/assignment3/DetailActivity.kt @@ -3,13 +3,21 @@ package com.jutak.assignment3 import android.content.Intent import android.os.Bundle import android.util.Log +import android.view.View import android.widget.TextView +import android.widget.Toast import androidx.activity.viewModels +import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity +import androidx.core.view.isVisible import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView.LayoutManager import com.jutak.assignment3.databinding.ActivityDetailBinding +import com.jutak.assignment3.databinding.CustomTitleLayoutBinding +import com.jutak.assignment3.databinding.EditDialogLayoutBinding +import com.jutak.assignment3.databinding.PostDialogLayoutBinding +import com.jutak.assignment3.databinding.WordAddDialogLayoutBinding import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.launch @@ -18,10 +26,11 @@ class DetailActivity : AppCompatActivity(), OnItemClickListener { private lateinit var binding: ActivityDetailBinding private val viewModel: MainViewModel by viewModels() - + // valid 인증 받은 password 및 permission을 단어 삭제와 추가에도 계속 써야 하므로, DetailActivity 내 전역변수 선언 + private var password = "" + private var permission = false override fun onItemClick(itemId: Int) { - // 클릭한 아이템의 ID를 사용하여 필요한 동작 수행 - // 예: 상세 화면으로 전환하면서 ID를 전달 + } override fun onCreate(savedInstanceState: Bundle?) { @@ -35,9 +44,32 @@ class DetailActivity : AppCompatActivity(), OnItemClickListener { val backButton = binding.backButton backButton.setOnClickListener() { + onBackPressed() // deprecated 되어서 바꿔야 함! + } + + val editButton = binding.detailEditBtn + editButton.setOnClickListener() { + showVerifyDialog(itemId) + editButton.visibility = View.GONE + binding.detailRemoveBtn.visibility = View.VISIBLE + binding.detailAddBtn.visibility = View.VISIBLE + } + + val detailRemoveButton = binding.detailRemoveBtn + detailRemoveButton.setOnClickListener() { + val toastText = viewModel.deleteWordList(itemId, password) + Toast.makeText(this@DetailActivity, toastText, Toast.LENGTH_SHORT).show() + // 요청 성공 시 메인화면으로 <- 요청 성공/실패 체크 onBackPressed() + + } + val detailAddButton = binding.detailAddBtn + detailAddButton.setOnClickListener() { + // 다이얼로그 생성 후 해당 입력값들로 MyData.WordInfo 인스턴스 생성 및 viewModel.putWord() + showWordAddDialog(itemId) } + binding.wordInfo.adapter = recAdapter binding.wordInfo.layoutManager = LinearLayoutManager(this) @@ -48,18 +80,57 @@ class DetailActivity : AppCompatActivity(), OnItemClickListener { recAdapter.notifyDataSetChanged() } -// lifecycleScope.launch { -// try { -// val wordList = viewModel.getWordInfos(itemId) // 데이터 로딩 (비동기 작업) -// recAdapter.setItems(wordList) -// } catch (e: Exception) { -// // 오류 처리 -// Log.e("DetailActivity", "Error loading data: ${e.message}") -// } -// } + } - // 가져온 데이터를 상세 화면에 표시 -// binding.detailTextView.text = "Item ID: $itemId" // 상세 정보에 따라 데이터 표시 방식을 수정 + private fun showVerifyDialog(itemId: Int) { + val dialogBinding = EditDialogLayoutBinding.inflate(layoutInflater) + val customTitleLayoutBinding = CustomTitleLayoutBinding.inflate(layoutInflater) + val dialog = AlertDialog.Builder(this) + .setView(dialogBinding.root) + .setCustomTitle(customTitleLayoutBinding.root) + .setTitle("수정 권한 확인하기") + .setPositiveButton("Submit") { dialog, _ -> + password = dialogBinding.inputPassword.text.toString() + + // 비밀번호 검증 (뷰모델로 password 전송하고, 뷰모델은 모델로 password 전송. + permission = viewModel.getPermission(itemId, password) + dialog.dismiss() + } + .setNegativeButton("Cancel") { dialog, _ -> + dialog.dismiss() + } + .create() + + dialog.show() } + + private fun showWordAddDialog(itemId: Int) { + val dialogBinding = WordAddDialogLayoutBinding.inflate(layoutInflater) + val customTitleLayoutBinding = CustomTitleLayoutBinding.inflate(layoutInflater) + + val dialog = AlertDialog.Builder(this) + .setView(dialogBinding.root) + .setCustomTitle(customTitleLayoutBinding.root) + .setTitle("단어 추가하기") + .setPositiveButton("Submit") { dialog, _ -> + val spell = dialogBinding.dialogWordSpell.text.toString() + val meaning = dialogBinding.dialogWordMeaning.text.toString() + val synonym = dialogBinding.dialogWordSynonym.text?.toString() + val antonym = dialogBinding.dialogWordAntonym.text?.toString() + val sentence = dialogBinding.dialogWordSentence.text?.toString() + viewModel.putWord(itemId, MyData.WordInfo(spell, meaning, synonym, antonym, sentence)) + // 비밀번호 검증 (뷰모델로 password 전송하고, 뷰모델은 모델로 password 전송. + + dialog.dismiss() + } + .setNegativeButton("Cancel") { dialog, _ -> + dialog.dismiss() + } + .create() + + dialog.show() + } + + } \ No newline at end of file diff --git a/assignment-3/app/src/main/java/com/jutak/assignment3/MainRepository.kt b/assignment-3/app/src/main/java/com/jutak/assignment3/MainRepository.kt index 337f4233..3372a5f9 100644 --- a/assignment-3/app/src/main/java/com/jutak/assignment3/MainRepository.kt +++ b/assignment-3/app/src/main/java/com/jutak/assignment3/MainRepository.kt @@ -2,6 +2,7 @@ package com.jutak.assignment3 import dagger.hilt.android.AndroidEntryPoint import retrofit2.Call +import retrofit2.Response import javax.inject.Inject class MainRepository @Inject constructor(private val api: MyRestAPI) { @@ -18,4 +19,16 @@ class MainRepository @Inject constructor(private val api: MyRestAPI) { return api.createWordList(data) } + suspend fun verifyPassword(id: Int, password: String): Boolean { + return api.verifyPermission(id, password) + } + + suspend fun deleteWordList(id: Int, password: String): Response { + return api.deleteWordList(id, password) + } + + suspend fun putWord(id: Int, word: MyData.WordInfo): MutableList { + return api.putWord(id, word).word_list.toMutableList() + } + } \ No newline at end of file diff --git a/assignment-3/app/src/main/java/com/jutak/assignment3/MainViewModel.kt b/assignment-3/app/src/main/java/com/jutak/assignment3/MainViewModel.kt index 8001ca10..bc51cb7a 100644 --- a/assignment-3/app/src/main/java/com/jutak/assignment3/MainViewModel.kt +++ b/assignment-3/app/src/main/java/com/jutak/assignment3/MainViewModel.kt @@ -4,6 +4,7 @@ import androidx.lifecycle.ViewModel import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject import android.util.Log +import android.widget.Toast import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData @@ -25,6 +26,7 @@ class MainViewModel @Inject constructor(private val repository: MainRepository) private var wordListInfo = mutableListOf() private var wordInfos = mutableListOf() + private var permission = false fun getWordListInfo(): MutableList { fetchWordListInfo() @@ -36,6 +38,11 @@ class MainViewModel @Inject constructor(private val repository: MainRepository) return wordInfos } + fun getPermission(id: Int, password: String): Boolean { + pushPassword(id, password) + return permission + } + fun fetchWordListInfo() { viewModelScope.launch(Dispatchers.IO) { wordListInfo = repository.getWordListInfo() @@ -65,4 +72,37 @@ class MainViewModel @Inject constructor(private val repository: MainRepository) } } } + + fun pushPassword(id: Int, password: String) { + viewModelScope.launch(Dispatchers.IO) { + val response = repository.verifyPassword(id, password) + withContext(Dispatchers.Main) { + permission = response + } + } + } + + fun deleteWordList(id: Int, password: String): String { + var bodyText = "" + viewModelScope.launch(Dispatchers.IO) { + val response = repository.deleteWordList(id, password) + if (response.isSuccessful) { + bodyText = response.body().toString() + } + else { + bodyText = response.errorBody().toString() + } + } + return bodyText + } + + fun putWord(id: Int, word: MyData.WordInfo) { + + viewModelScope.launch(Dispatchers.IO) { + wordInfos = repository.putWord(id, word) + withContext(Dispatchers.Main) { + _wordListData.value = wordInfos + } + } + } } \ No newline at end of file diff --git a/assignment-3/app/src/main/java/com/jutak/assignment3/MyData.kt b/assignment-3/app/src/main/java/com/jutak/assignment3/MyData.kt index 508789e7..a030469b 100644 --- a/assignment-3/app/src/main/java/com/jutak/assignment3/MyData.kt +++ b/assignment-3/app/src/main/java/com/jutak/assignment3/MyData.kt @@ -25,9 +25,9 @@ sealed class MyData(val viewType: ViewType) { data class WordInfo( @Json(name="spell") val spell: String, @Json(name="meaning") val meaning: String, - @Json(name="synonym") val synonym: String, - @Json(name="antonym") val antonym: String, - @Json(name="sentence") val sentence: String + @Json(name="synonym") val synonym: String?, + @Json(name="antonym") val antonym: String?, + @Json(name="sentence") val sentence: String? ) : MyData(ViewType.WORD_INFO) @JsonClass(generateAdapter = true) diff --git a/assignment-3/app/src/main/java/com/jutak/assignment3/MyRestAPI.kt b/assignment-3/app/src/main/java/com/jutak/assignment3/MyRestAPI.kt index 3ea36f76..47286750 100644 --- a/assignment-3/app/src/main/java/com/jutak/assignment3/MyRestAPI.kt +++ b/assignment-3/app/src/main/java/com/jutak/assignment3/MyRestAPI.kt @@ -1,8 +1,11 @@ package com.jutak.assignment3 import retrofit2.Call +import retrofit2.Response import retrofit2.http.Body +import retrofit2.http.DELETE import retrofit2.http.GET import retrofit2.http.POST +import retrofit2.http.PUT import retrofit2.http.Path interface MyRestAPI { @@ -15,4 +18,13 @@ interface MyRestAPI { @POST("/myapp/v1/word_list") suspend fun createWordList(@Body data: MyData.WordListPostInfo): List + @POST("/myapp/v1/word_list/{id}/permission") + suspend fun verifyPermission(@Path("id") id: Int, @Body password: String): Boolean + + @DELETE("/myapp/v1/word_list/{id}") + suspend fun deleteWordList(@Path("id") id: Int, @Body password: String): Response + + @PUT("/myapp/v1/word_list/{id}") + suspend fun putWord(@Path("id") id: Int, @Body word: MyData.WordInfo): MyData.WordList + } \ No newline at end of file diff --git a/assignment-3/app/src/main/res/drawable/plus_icon.png b/assignment-3/app/src/main/res/drawable/plus_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..ac620e0be1f3fed39d17eb110780649cc4066f12 GIT binary patch literal 3992 zcmeHKX;%|z7QU6JEG{UxwlogiMWVJMtwM|jaEB61Z1o5TLL9)LC`$se1&DJ-X@*v` zB1%jML%X%gCX7N9lBkVzsX#j@a9Cuq1VIfF)EEh5N_#%^59p87Id$H;&wJ}uo%`N< z?(<}B<9bcC{>mBvrefY3w*%0qM;cg8qLz1wPd-u01krkIrzLfXEyI4KY^xaW01@TC z<@1d&Jr9&o;Jmm^{&72^_r)db6^27XLW1-Ds7O)h-k5ObXrVx^<9r2RGYi|eZfBz6 zO-INtorXQNL(S}*#cst%U5jq?G`-{DvoG02U;4VZc-0RV&Wh@%PWo4A);Gb&qY}># zZ=UJ)mbb3(R@OlH*Ddk&hbqg~xEZSpFH6N7q99XX0f+l_Xny@O)@W8yt<*H|&sqdtYww`u)7`AG zqF2|^3SOKqXg3uyjsCdb^08ixDj3si?;9oo<8Ub65Ps{pdA)%CAv^6fXbl}Wnb_NA zVl_7ood)pjDMputjn$=#@>+APU@nm@7#jH`ivASXOe`s01~BDd2>z`syd_o1w&BxSj@eF6%2|jffIeS&3Ts*SZ;qNxZn9$t50kk<0?nJrFeA!rC+=mCb zemqZu?{x06bURIZ{9sqMwTmSW(dutS>ySyM*3?I($%}{06Vg?$h&u~r@ux?MY#`|O zshFB=m`+c0&uW{UuS}WVC05cTFXH;z=~Kd%5(TOZxM>HaifQ%kTFI_{e}1?n_;Xox zf3~j01$Hcur2u~o0uTGQRDJ5Kp`6y!;7sG<(axZYp+|DMJsB|57GRq3oG=xEv3XlC3k;Hicyg3^s@AZT30RM`VNe<(|bATZM~> z>!=s5aL~`6G6VdaR4gJD(_z90Nr@FG1|#qWW>VPmhQU4cb8ai3(vcX&JWi9f&H_u| z`zd|&+i>2#Y{8@tw$SWktVT6?WQIY%6_Knn@o-O`ek6c%s5JEUREdFUe2%ho`eBd^ zRh=Xgq@1coUdw)X?W3O)KsA4YGJC=>w&&{voCw+Pom{ z3RHeagkn(+5etDYW=c)lXMZ2FE_x--<$+A2+1~K8cU5QRX>|^!t!ITYnHp5tVok;^mtPk#2{sM0g z%%@el6Lwfy%+CeXPmf0972&ZVN6@x>+Q;%16T*KQltY%nSD`6(>*DV4GR{bAkjT1t}Wm3W9 zUxL*(NyXt`Hb+NxZ zX{1qSRI`XdgU#<{s0Jr*h&mGsz#AXM)QXxNG-?9&M`CGiM8!>YXzEBU1LHE2c=(*n z_SP`Bp?^u5zVoJ$Imbp47Ax;ckt*!>Qgy^>U?lm?7J&21a?IltS&fYyV&x}Z523ebj$%5Vz5r!~+;2wU za9VNy;%_4C2DU-3>htju@O&Od^ji-U?k}FWe-;y>PK(p!4hUzzq|$yP5?v zkOWkwUqRoU2=QPnVMd6PoW(mO?1f`5f+JGDvP-%LE?l`Tkf8JcKljSITro*y7s|zYCfKzDEHc6p8g5N_1ml{ z)@{yv+OjGeH8%Yp2+q%^{0r}Xuiat`rBx2HKP=E*Xp(Z|HN!9cdREKGt6#`|J!tjG zONo#M_g}+5UPC+O)R?HWt!8IU?te7eXo_lL2oD&^sG}ouA~CxCuGnzOwZ(eeIQ0Onr`0K3PR( zZ(&`8mkrcJarOC>GU~j=Wf(Jurp>1&d)A!Ry6XPu;vJaI{{4@L1SOVYCj?`4=8brp zm&Y#XvLBI(-G&d1WiwQ1@6Jzvn*luiT1vEa+&6ci3o<8XBHGtcJiY8EjnN#6w=q^T zfS;k`>id^Op)1Mz=0rS!muL@K2m390i-0Qc=o0x@GfRE{4y6+rrPK*&Urs}l{P27( z#hNPi#h$&BI)5;oO;nUus3o!w1$CN<{;n5hiYM2?}PlM0b2hi31nbX1O&V@{!%l0%4FyHG;el}MX;c`knB+F=(WS&|+k6`ioU?>Of~`evqxh zhv6~(G4=wr^O0xgXDq&6-S@ZY5*v4s&RIk;i@npMzGJnO<)=g&!#7LL*4zC#Cz~c? z8mZIce08fP<)#K}A@!>F-Tj(sN%I#hSTLu=q{R5c^l*FEsYIhB|9Z#CF^ABZ_j$>> zE{pstR`-7S(3U>p;FG7Ee7@i$>$rhPUf?FU($)WQ^2dmb;5v7m_gY^pmzJwAT{1RP zF_xZQ$9wvE>&ge_@~FUyb$ktHgXpMWkzl-y<+a>b>^*ggC92?+l>{3#sWZ^QD=`KN z!OL@QOpj5wy^Y_l8$=g{+qeiEl27d(4$kyopp^0$1I1uWWF$DxZjRqBag~UU{6Ke1 zPcD*6*rxabWxTr(vjrc)V{uELZFeO77jl+}guD(1V|lL?F#B#yFio_YP~f}%wRKWn zv@>~PurG3ZyE|&UZ~9z1(w4F}S0%O}iHRz&C#&YF#1vT5P~ESlMhk-VL`(Uum!$;E z$RszR9G9-9d=X=J#>NcMG^u;IFiE@xYk0-0RH*9yqWw^e;~!_i5fi9Pi9!fN&?5o=)_ywG?2jWAd-$H!6Q zlM1@3n07VXa?~J`viWMVU#`m84M`Z=JxY37v!-{dFnLfct^0f=_b{?J-*t2*K`=L> z?Pv?;`F#*sbC@`{Own(;$^5>FL{r;4tg%fp?_WPGfLFZHE8Dp~onKJav(-8D#R1g~ zXW!PNjKT{so(Ry6ZcbjoOqcXJW>0BsV??Ki$r#)TS+hUdgcj!GF$hPn;WuUU;LWMv zk)&_E44ZV8*V-@W$Xd1TBLYhz8;)dC;6l+judF;|@j(+qbTCOyQ0KJiy5)h>Hkq#! zP=RJcjtahBI^3o}RuemNf^}R+WVV=FqgKbJB)sJLaOt<*4ey7bwmA)%l{aR51@i>T z_v6(o6pJ_M`e`a(UA~Z2L%GLr@}x9S>Tl=y9?AdCE4u}2Z7@VB_NHag;f`ZZC5mN= z4RrG!s9fwbdtQZ0;3JWDE=2q1jd=(+@_%c5q=43EcWLEnQMcJ7h!-=0_jx!TFrGiU zLTwLK6HBu+mK8_|lm@nY?JzDZFWBX288Na~@QLs*pYVx@^64qLpL?~h@?yN(=)23y zT~T9mnuGo3NPpdt+XbC>e|vc~7WdC)V*GUFwW%2U_wzATk$nNYSe=mvFI*dWj252R ziz)-pafbQta7fFgSqXHL0|sYn?FxUCzbrh)ViE1xozkMSyeTV}(`&CQqurmcJZpPu zcWit_n5V3edsFUV={2rZcvJz02G2e|YfCEa+UqLW(s=jWmaHj;R~MMoe!i2|)5zFZ zZ=X||9s#?p^QjrH(IwSF9s8ZOGFOZKd-VN2E8AM@fZd=dZnN`0j$Xc9 zdN7yk^BVm#_1h=DSwNw^b<35VCWp^I0pX4Q>H%X2Jz8+gRp_7do)AbZhxwTMtJ|qqM@d7@*ij)k0}>BUTJa zu<7^z!^e4^h?PRD<8wGF92UoW;E1}p?(zTlxa-+#HXzgIy0{ZopQRG3?sd(CHnrcQ z;%UfYOFMfM6SIPnCzfV!ENkAR7KX-O@TNGU?1Kk)!IN#2JX&~W$eP4Vv+r93%aSL~ z^+gusld^n}w5O$7k#lZ4(2Z8;&TvB(yVgys6aTok#LUk(#q3QwLOqT>!4|7YosWr) z)W^12C2UJ9SQn4p%dq5_dvDY=tw};^-3EeV#2c_a^R^>H;^Av_{kSSG1M4(F=pHRwv2hj zEV!4GmvZia81C}>i}w5hE%UyX&qTZM(K?RoZ(@$;l&00qe}pRLgxZXiexPa_`Aq!L zcO3LY@~4Y?9(ht3{o~=5nU9=UA-a;bm+@r-Vt7tQi0vK!UA^v20Fhi?VVKctjPj~) zRH}Pb>^U5u)xSSU8SlDUp~_W>RyUV4%C51UJJyB;yN@DubMMUB(1SsMMjB>R60#^! zUGMA1uJ>=R=3YZlRnjK&QuhI#iHhIlMGulo;B?0N9#AXMiK4*?cUVA($%`{$oRcWaDX`QFaCg(%~+ZK6=vq|Qbg$Ay#bX^ z@tu*ex+86Ue{N3<)KpWU*`rkEB5R}6&~kmgl0}jMd|2i^_)GHe+Wr?#aB>WTLAx!=zJ7+|a^d78h{lT#XR(JBnH*?d`1&B{nC-9_W>`$L%bE3k-?EMKp_MD>8XeQ6c}OtC&HF=`edi4rqI ztvjW;gOTjhC~L=NPg(v|_0fBMY59LC>t^R~r~(gbrUB;hR_IM!1+x zxedcaA06I=oGi1a;X;293z=BhZ^nyEpP$pDZ*MyB;`KS=kX_x6tK6Z}rXFvloD1jn zjNJXLwMR!_mVBUe-Im1Wx179^vu#gi-m6%j&RfMqh&6mb@E5;F{tHD@T(h3%lS5x> z-d57JaYn*!;9FjYsHmvHUFgcC|Fsj1OM0umXie%Q77~VdLVk$DM}G1t|Ibu{w+n61RgKXhWXL4^CUu1s=G#qFD9xN2Dj%VOuLjuod{Oc29A-K#?Mkw~71M2X>3eE^Q5QW# zN;_8dzPi_)4wR~!bYnAbB|>h(&FBL)><@@H>XIhZum(;-O56c)QYJBxsZBo&TYq0H zdfxSZ{p!aOu-#L}BI^A#sY~kF&xRhhiGog#vI7ra!_Z>gp_jaLqzMVQx>t|=mE_Iq zqC2N2b#L55QtE2qGQn#_aU>pV+X9G*a@DVo7d{?9UyhBN8DYUx?HSXh=6!X(0{zGp zYr2F?iO=ZETD3H>DOu66*-6i4Xw$nrrUS?zX-aqYOP*LF*d@42tbI{+S-q~M1@gS1 zgKc%HvaML_A=6YbBUPwOvbwvrCkne%4-`&D=<8MUcAb7Pg6{3t2Oi;Lu+_Ysoc# zdA2+Val=2S*{n2YTIuO4q74>&tsj}UOB0(Pb4&h0`H_%3?ce+b-G8^7j&tKwX~;Yz zgIjXkRUh*`f%2(~&41)m^F3(zs4m*xL{&g6iz$A}!n64%QM zpG(KSR{FVuw)4`2n^!V_7^Ic{_L+fhEY}@Ez8V@sCdj<)j`yB2uVT5QnQ@Ycs!!Rm z-&h~XYNKY((hT*1E*e3h;fjGs-HQp1c7rhrR?JZ6kH`>|8!Ctt))mZqRsCcdgJe-^V(uGg`8^3`h%uLZsPam%WzCMm?(ekv`-5~j+*5A{g+jTpwK~^sb%u5eZAjDh#^Yl zwdA5bdQaY)J+~&7&f5{;=@Fip;j7O;tc1g(w!+fRaY4aiX<^+}cAab<|z z6?vUm*H);dQSG1AN7UO=v|jUS+v2=Wsdal1H{MM+J+h^2PD&o~-H@elKE@CS#+x_k zZg#RLQX0gve5lGSR?R}bCTn#b?fr{-za9B0M?OfiDmN88DNO3>ojM^HxH!2nomOV7 z=4i5pe?*IQ{D`SNr7ZgRu`h4%AhYK=`UAa2yvT+65m4-qZn*dyU$L{)SW|cAVa8ya z+4Eb(5bE79{nn4ii|8B=t$)j*o{|v#9#=Vf=sbi}f6FuaBg5Brs^9BH9R6lfuWj!&G^3%WnI@R5=btO6oX`zxkXSKZ;jhOZ z4I31d>z`9X5(>!1NW%HoD#g!QyX?B}vX0lE{SYA=r8kqb+pgPvOI+NBFD|Es&n^oO zALkvVG@zAk$Zj3l<<_eFL#THpq*LVf=T^nQNhiv#lBh+*vMmqd*4w-0-7IB@gk6sx zpQPcx((r{;;$(Q#Xu*Wai@t}?b_baBY?wO6_uBGc_vrq&BmO3HThErSRiRkDMEUyz z)~yL)5}cKRulmFN(NhJY{9D35PR%>Cxqy2PitVUtyk-9gv%aDzL-Zq@e>V`E&Xo)-rAkRUpL*!@F>*P&-5s~;8TmVC8>yphbo{yhW|d3azh^N z**HE^t2R4R#lgb=W`~#^h~B+A^4@U9nUsVMA`YkF=OtU3mrCrg4U2mFN{YRNO6=gj z;8>Y1I%E~9tdUl`SxRjARC z|8exvzf0c6G|brJBUq$D8EY?N8B`iR`Bgh3O2=Q{qVT{*vX`a}TK_hC0u|5_^$_Y?Bre>5prZ%SCK!(n!IFB3K^9yS@<93_n$WVgW7m40rMUVW8|+LLnogh5gP>OeqSvViKjov&{N301u>}|J5q}{n?VpAwEjx+ z7%GA*6p4(7+}vA-q8jQKKwyz3rIdCK9i%qemrQ?1Q^xnmBbApwk-IvJ94Vz$z~x2K ztZj1qv=o3Ca6EFoCmuo_DpsWuKiq_IgoD|qq&9KU8dP`H6BStN)pY9d2HIjV=MVO@ zBehbv@U;~C(32TMfJ~ptK9nY&QSz06KVcx{b?D0tEMqO8w;Iq>K$}Ps5(Jb{qJs)P zt-@zX00X$^Sq*aof08h%-v2jA7{kK zK$C$>IYIjdUIbt|BbtIIx4#Br1(wz=^~b6_J-oQWCr=MJXL$dK@|nrWP?mk_oYM+7 zxQ6bNl@GWj1K(ZtwETee8%~qEUat=p(t%nPsKhjA$5#L{4uJebBkD<-H>mv)sO3X) zKltnjz)6BQJPYDY05Sa@_Qaje_ICx1+KYX;h&j*9DokUCJZgJHZTyF`4m#oswg+dg zOT&NoOFJI|8;P4efLSh~sO{e*^bC?Dr}RjCY|PRN-g{s9gP3X5#+Tanlx?lvqkL6( zJUNfEci+G=P3pLOhOoK@wP&Zi8FbN0@*(kAm#Tbv%f5+aG?WBWH31#HdlM=+RU0k3 zu7tI(o0Vmt7|Y*6!>RsDoeiyjZ~IH4)AH6_$rj#Upi=S(FahTrc}kYOH2D-AJWiPd zI%dn)d+alQe@&b+E~S8^cyNTZC`F=uhSVQ7WS|XdVlpqQM8qPV7>Unv&nXSQR!m3W zgiHXIPbxnr{)3_jP_(D~eF+l~fDPO!+P9Ln(@3rirvZRy!uU_#`P|sv@fd@$tZ!F9 zAtk8nrB(Jeqa!D!1(-aN`a*LCjaU|l{d{I1++QDuWB-+OwZPZIiC+MbLE2)8J{srnFP+)%FpD9O z)?Y2Bm*)ir`D(*Buzvd2uoBx&>KxX-p}iB zu-Jt6%3E9LjoW^T7jpIKm{edx{;0oIS;|7t)ff4NtINLsO+&8%;wJ6^V#vqiD zh=D7$)C)Q_Paq4R)q_6)&};!TQ2SKOxgU-NcANKex1OkV(-EoAr%jS7}6uwH>g!XcaDW4x{@5UZ)}M}+gA3^XLr2oOOIdADFpZij;pej0QlFa zF-i$}5-8u8hHv05f7+vVyL&V0bk|4SSm&( zzJVjCK^gDChaQ&}FhD13VAFu-IzV9oF*~q(R!Nme{lFEp(a{9(p@1%U^uP|PHsmq` z*`>u{s%*oG1$L7?Ik=L@7>ztExbeD~tC_ncdy>8T1*j+Z?YQViDUUT^4cLZc@~IU8 z4S8C}RPj798^5rH93^XieoNHGgNNKus|h3$E&wqE1QL{IDF3vt*wLz=f^w*WUBILP zEJJ%%j16B9?=0p#2|f(02?SM2qZmkoT%bTM$iffCTe0hNVYd(#;<)BR#a-n?O)Ziw zru>}SlH{Z(ugDcBM|+lMbT7<`rqswZTYreZC44vf+SYY1l#jYDAXE}klz$)Ls5e}~ z$e7G2UGA%BBC+!@-FZ1c3wc@1Y1DVFe`cU~E{7bkU5NJB3)%_+Ol`DYo40R;EWF{# z-wkq&oF=WV8Jw#UD9BbIzLNYDzY+Ud>zD$8G92MFvQqB&*7FQ6csE?boIN|}jZpxT z773FtN`*)dL9z|rB00kORY~HykW?s;+!olCi>wK9yCjX(-MYKVG*Vad0mx`n2T6k` z=r@1@dF;QbfD_;-2U^EK|7HXV3Hf`}4dnS8R(P4br7Y z?ff|eP5>?#MxRL$+hJ4T3}k2-@KCw>k*}lv!w}W)rOL7_re;=-lw`{@7xBBRoz5Gd z>&c$jcm{Lc2DF!wi>O7iSDkn|Y>)TUj{R2Q=D+Odv2u_oVx&jj%}Tl9s~~hc_B-a> z3obIM7I8f$D<$<&J>&tiBN&?xJ1WLF9>A>k0C}iZb>F5;cnHmpC3^v2H<&Z|5KgLU z8)jVxenuPd$oaESSm;4|7CcYDlShH|yYXY=JDjaY07^K`S(eKQP_rzkJ2u!;u0yg> zUm^w^XIBNwt48_Ypa4Lw2e3!lx-=a81qrks9E&d{m7HNMj#n)`Y_6|G#kmS-tD-P- zHj5<_04aCj1L7ED1kK{2MaUXF_b^wO*{<{0TP4kyN*HKTFM871{4eNY@Anyo0;*mI z-!>wya3ktbKproVc6>AzXFF=!$YLEox$juO;bL0+N{z})DKMOprA=28dZCf70?WwlzQlyCd0y15y>YTBwSF8EQj5 za~vWIoVPVe2x>x-05)MV7S>^6`I3f6*A2D+kXu|l(3o+KyDb212rZoq=?`^Wc30*Xn3*?;N`;ZIVFX2gJDF4vfh%04+EY6 ztM%-M^B;f_>^vuhk#HeqX{>intLW)wZdMtrNhP)eb%&V2evkKaI~=XvYZ5~4_s$O{ z_9IX?USCBemuiEnuzpa>IIb}i?3aNXxErvWw~&Hw<*P3Br7U`dv>mOHfsR`ITCTBp z1EsO_6ph$kwaBV4p&IN4X6cM97sTKGy7M3nQj-Ta5>ULY9=-E&vc4SOkpZeSNR@9` zh92D53EVMGHL3GVkdIshFhHJ=D@G}k5FFG8p}<$HgGnI!KLeT=?>d|$JfZ=U*_~a4 z3=k^xVW=(og|0ZMP9&2Xs0J#bSIZTD9TZ z3=af3u1W+$xex&50(ttRGp@p`N%}C;G9dt5bkPxqSzY<}z{ooGgV+QtzIQp1Or~xNNOdUC z3`8J;Hu!}=uI6jHnJGV6_+Ual)ZZP^v|U16#v23 zyt`A3S_P!|iue39awQuzz~;CxD=2pj;IMWKy9m7wrShoy_>Txk$02>M&Q4jBa6V=W zv>DVzeHdM5_DK@;$RkC_jldq3==xCyHNf_r{jGlF4k_jiFB8cS;&A6e8p~s}Uaw)+ zXeb~NcgMY*@5JUn+59j28P$d@xGX`Ik@&EY1lw{1K>mW(z=iVKSyQgjV+qtWPU3^B zxaC(gaVY-1O=t#t`1bz#IU8*NBt^ydbc+C+Ld<2uk=-1)uOcyD{>pXfvxcf?l9fHKacO7QJ;4Jdb2(>uW#>{J z%TNq*GjiYd8`xC#2R!vGev$-t>a*;2<2{foWRr@z3_k}@_kwDGqGkuvNUubW*aloy zQDSYA<@?r2;eS9jan>b1yKPIC3SJF6J1ySoU=_-mMB3=crdb8J9t!DI9VkXEual63 zi1CQwj2f*#AIW68kD2pA2kokg!{Ol9!_B8b?u-^heMMifMUdxZgSxCkYsi7woz-&3 zp8K5UhS$Ka4rBlc;d&#+5fV!)5=$m_a6Sxyd+b?&|Ixhw;t)1F8C==_9W`9?1FhLCe zo&430C6Tl?+SNnrdr!|l>yF>Zo|x#gv3Pv$23(6u6v;Ms)P+SjhUTOk(nzp%T2Cd! zbVgKcw8XjElkjSGZrH7if7w$BzS923=PrC0zFMVc9AxcZYYV07dIov}?!sUuFGFZ8 zu6GC$+Rom2-Zu8NmRTu>00c;!PKGUTPQ740@&lfazl>@=+NCwmJN$lzP$o=_;{psI za*gDkNr^ZJ5(ORIUMH(TAtvW}?}5Ztn}&Z5RumXSNQH&RjpkBv?Oe;R<)T~%*K(ia zp@u%{{h;jL5#p22ef?!r;+Z`9!<9VqH_b{dc3-WNPqira>**f7R2m$&Oe?MbkNaIg zQz?4araI3ZMPgK<8|b0D8wS=Vv0V3Z7uR`76x-Ze^;fq%*ceLf|J>;Cs!!2zo<61t zna!p^wDmQwJyLGB11)&k!sm&Ozg$i_|4PKM<)YiS-X3)qt|dp<=8_es+zU+}LkF zuNl;%c3bsgGs%I++GnmL_tppKwofxQ=lCD!ynkrw{)utNUdWdF*$E`oJzhI$@4?DE`uH|O-@BS7-ve2gWoC3|I$K(yq3_Bul( z{fhTAG7pl{ZXF~VHO6MnTE~v=Y#+C+oHMn6^L=^8R9Al}B@Im(J_*`G6joJ^oZVeK zL1pxh&d3yB8ZbU}VquQY@WY+u6HJW{R6$*YS|D03!%BD7!}8jh?!t7LW`~>@0~rH} zaTmdP;#HOHqm=7;lxZeML3HbGp?lF%H7S?T$04$u<($_d@f`mjc^&~}Lx%Y3Yfsic zf>%L=CH^>4H!4rsuH0O-!RF@W&8ih9+ASkXpDa}ipFf^1es}ZbopUN?!Vb3V zlSZ=bSZli|=}#vqr%iZQ)byWSJL%eDFUE8~1Z~;%rz9@%aRE_a#Ms`nz^ZN)4WA2d z9X=gEiPti`_YBJr5S|aQ(=yzMbiQ}TFBMb>SH?&Qa}Sq z5>0ITf4*NGqKYok&7te`{17%fD_4uFM4UG(T(-9Md~?|w_3Rs4^r0a5wTgAidWgzV z#WQ8O!bGL$Anp-0u&WYyA(=WMM*IqLc%n<|J$;e=N-FU$XlWDjr2cytJS#ZCJZ{{l zO^n+HmO^>YVmVw&*9EsSu&%wNQzg`lg`G;WzYlr8M{6#~1NCB!^ z^fimjPDs+RSI^ouTiG3InzF86lD_hOIg5d$Yk680L5hL~8UED*hPD4oTb+?uvqF9F zea6$dS%b{2&-EAbudR2j>KOjb>{E`A7k3!hB_zo5B?Wr0=el;EtW$I6H`gUAy+HXY zSG~S{W(}u4`=03Gaa}jf(Ll@)`2e~JeX$coOS^Bx)-3j-sdb+e6valn(48gKbaQI* zgUNT55}YGl+PPvC?QwdE-^iv(lkEe)93De8Zo=GW>yDCqwC!J}LpP8YL0{Va^g_*& z>@BS0vLf5&bhYoR{HFU|kX?Fh4xQ*@CsfA7$iaOY|F_FlT53|UF-b@jsq8aW`F?%1 z{+XS~X94Sj)YR@#gKu9G2s2{HU1$uX1&*xaD@8rTIl?tI`f(BT98^9~dA?j5-QQWozPMIHr*m-`pTrLr8F z$(nB!g^oCBQHk&64?|F5^OD|bJ^#5GY}2rLqGiU31z(xT6XSiq991+#deg4ZA&|Ya z{=7K6GCChKmLDRsEpfxUP~DMKpirZ-FUJHkMC-!T*iFJxWrL-`*PQr z2s3CH$!=U-Hqr8sY5}>eXMmBY$O>BY-hLT6pgb9BJIdYwck{niotycUkXi2aySnIC zzv)5-dZ9D&+xS#YX5H6nmqE)VNodo27L)d#8DBn~)dqu~pp2|9X2fi^`7H8PSUD#l}dHw-(cb-)rB?hQf7^ zyU;N59yK?~JmpS*YRK+Z@8?e&6FaJpw_pTCMvRVuGlo?^&r5pILXD zD$)=%@yZ^AXtzOiO)-7`kcf>;Ld;e0^wW1mk+(n(nw>_S*T-Ydzo>O9LEo_)g}Ih| zIzCTyhpok+AA$qQ>p8-`V-o* zvH1Io5;8SAG5h+ysF|ANiITQ1GJTYkhV!df2)+5^;nSxUaP0W<%k+ZuIm#SK(cK?Y lBX^Qd`MjG`7&xPvn*L3348NuW|BnHgo9!{p{nh!*e*p%1rDXsB literal 0 HcmV?d00001 diff --git a/assignment-3/app/src/main/res/layout/activity_detail.xml b/assignment-3/app/src/main/res/layout/activity_detail.xml index 453714f7..13c821d5 100644 --- a/assignment-3/app/src/main/res/layout/activity_detail.xml +++ b/assignment-3/app/src/main/res/layout/activity_detail.xml @@ -9,18 +9,16 @@ + android:background="#AAAAA0"> + + + + + + + + android:background="#AAAAA0"> + + + + + + + + + + + + \ No newline at end of file diff --git a/assignment-3/app/src/main/res/layout/word_add_dialog_layout.xml b/assignment-3/app/src/main/res/layout/word_add_dialog_layout.xml new file mode 100644 index 00000000..b8817f97 --- /dev/null +++ b/assignment-3/app/src/main/res/layout/word_add_dialog_layout.xml @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assignment-3/app/src/main/res/layout/word_list.xml b/assignment-3/app/src/main/res/layout/word_list.xml index c0ae233e..e05f8018 100644 --- a/assignment-3/app/src/main/res/layout/word_list.xml +++ b/assignment-3/app/src/main/res/layout/word_list.xml @@ -1,14 +1,20 @@ + android:layout_height="match_parent" + android:paddingVertical="5dp"> @@ -18,11 +24,24 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/word_meaning" - android:layout_width="wrap_content" + android:layout_width="0dp" android:layout_height="wrap_content" + android:textSize="18sp" + android:paddingLeft="12dp" + android:ellipsize="end" + android:singleLine="true" + app:layout_constraintWidth_percent="0.6" app:layout_constraintStart_toEndOf="@+id/word_spell" - app:layout_constraintEnd_toEndOf="parent"> + app:layout_constraintEnd_toEndOf="parent" + > + + \ No newline at end of file diff --git a/assignment-3/app/src/main/res/layout/word_list_info.xml b/assignment-3/app/src/main/res/layout/word_list_info.xml index 93c4d495..f537e808 100644 --- a/assignment-3/app/src/main/res/layout/word_list_info.xml +++ b/assignment-3/app/src/main/res/layout/word_list_info.xml @@ -2,7 +2,8 @@ + android:layout_height="match_parent" + android:paddingVertical="3dp"> + + \ No newline at end of file From af5e276342ffb0c1befcbf9daeb4ffd24500cf8c Mon Sep 17 00:00:00 2001 From: Glenn-syj Date: Mon, 30 Oct 2023 03:16:16 +0900 Subject: [PATCH 3/4] Additional Spec Done. Challenge Spec on working: Error Handling. --- .../com/jutak/assignment3/DetailActivity.kt | 114 +++++++++---- .../com/jutak/assignment3/MainActivity.kt | 54 +++++-- .../com/jutak/assignment3/MainRepository.kt | 45 ++++-- .../com/jutak/assignment3/MainViewModel.kt | 150 ++++++++++++------ .../main/java/com/jutak/assignment3/MyData.kt | 14 ++ .../java/com/jutak/assignment3/MyRestAPI.kt | 13 +- .../com/jutak/assignment3/WordListAdapter.kt | 6 +- 7 files changed, 287 insertions(+), 109 deletions(-) diff --git a/assignment-3/app/src/main/java/com/jutak/assignment3/DetailActivity.kt b/assignment-3/app/src/main/java/com/jutak/assignment3/DetailActivity.kt index 46d4b90c..689a3dd6 100644 --- a/assignment-3/app/src/main/java/com/jutak/assignment3/DetailActivity.kt +++ b/assignment-3/app/src/main/java/com/jutak/assignment3/DetailActivity.kt @@ -19,7 +19,9 @@ import com.jutak.assignment3.databinding.EditDialogLayoutBinding import com.jutak.assignment3.databinding.PostDialogLayoutBinding import com.jutak.assignment3.databinding.WordAddDialogLayoutBinding import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext @AndroidEntryPoint class DetailActivity : AppCompatActivity(), OnItemClickListener { @@ -28,19 +30,49 @@ class DetailActivity : AppCompatActivity(), OnItemClickListener { private val viewModel: MainViewModel by viewModels() // valid 인증 받은 password 및 permission을 단어 삭제와 추가에도 계속 써야 하므로, DetailActivity 내 전역변수 선언 private var password = "" - private var permission = false + private var wordInfos = emptyList() override fun onItemClick(itemId: Int) { } - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityDetailBinding.inflate(layoutInflater) setContentView(binding.root) + + // Intent로 전달된 데이터 가져오기 val itemId = intent.getIntExtra("itemId", 1) - val recAdapter = WordListAdapter(this, viewModel.getWordInfos(itemId), this) + + lifecycleScope.launch(Dispatchers.IO) { + // 비동기 작업을 수행할 코루틴 블록 + val result = viewModel.fetchWordList(itemId) + withContext(Dispatchers.Main) { + // 메인 스레드에서 수행할 작업 + if (result != "SUCCESS") { + Toast.makeText(this@DetailActivity, result, Toast.LENGTH_SHORT).show() + } + } + wordInfos = viewModel.getWordList(itemId) + withContext(Dispatchers.Main) { + // 메인 스레드에서 수행할 작업 + if (result == "SUCCESS") { + val recAdapter = WordListAdapter(this@DetailActivity, wordInfos.toMutableList(), this@DetailActivity) + binding.wordInfo.adapter = recAdapter + binding.wordInfo.layoutManager = LinearLayoutManager(this@DetailActivity) + + viewModel.wordListData.observe(this@DetailActivity) { wordList -> + // LiveData를 관찰하여 데이터를 얻고, adapter를 업데이트 + Log.d("DetailActivity", "Word list size: ${wordList.size}") // 디버그 로그 추가 + recAdapter.setItems(wordList) + recAdapter.notifyDataSetChanged() + } + } + } + + } + + val backButton = binding.backButton backButton.setOnClickListener() { @@ -50,19 +82,33 @@ class DetailActivity : AppCompatActivity(), OnItemClickListener { val editButton = binding.detailEditBtn editButton.setOnClickListener() { showVerifyDialog(itemId) - editButton.visibility = View.GONE - binding.detailRemoveBtn.visibility = View.VISIBLE - binding.detailAddBtn.visibility = View.VISIBLE + viewModel.permission.observe(this@DetailActivity) { permission -> + // LiveData를 관찰하여 데이터를 얻고, adapter를 업데이트 + if (permission) { + editButton.visibility = View.GONE + binding.detailRemoveBtn.visibility = View.VISIBLE + binding.detailAddBtn.visibility = View.VISIBLE + } + } + } val detailRemoveButton = binding.detailRemoveBtn detailRemoveButton.setOnClickListener() { - val toastText = viewModel.deleteWordList(itemId, password) - Toast.makeText(this@DetailActivity, toastText, Toast.LENGTH_SHORT).show() - // 요청 성공 시 메인화면으로 <- 요청 성공/실패 체크 - onBackPressed() - + lifecycleScope.launch(Dispatchers.IO) { + // 비동기 작업을 수행할 코루틴 블록 + val result = viewModel.deleteWordList(itemId, password) + withContext(Dispatchers.Main) { + // 메인 스레드에서 수행할 작업 + if (result != "SUCCESS") { + Toast.makeText(this@DetailActivity, result, Toast.LENGTH_SHORT).show() + } + // 요청 성공 시 메인화면으로 <- 요청 성공/실패 체크] + onBackPressed() + } + } } + val detailAddButton = binding.detailAddBtn detailAddButton.setOnClickListener() { // 다이얼로그 생성 후 해당 입력값들로 MyData.WordInfo 인스턴스 생성 및 viewModel.putWord() @@ -70,18 +116,10 @@ class DetailActivity : AppCompatActivity(), OnItemClickListener { } - binding.wordInfo.adapter = recAdapter - binding.wordInfo.layoutManager = LinearLayoutManager(this) - viewModel.wordListData.observe(this) { wordList -> - // LiveData를 관찰하여 데이터를 얻고, adapter를 업데이트 - Log.d("DetailActivity", "Word list size: ${wordList.size}") // 디버그 로그 추가 - recAdapter.setItems(wordList) - recAdapter.notifyDataSetChanged() - } } - + // Dialog abstract class를 만들어도 될듯...? itemId와 Binding 받아서... private fun showVerifyDialog(itemId: Int) { val dialogBinding = EditDialogLayoutBinding.inflate(layoutInflater) val customTitleLayoutBinding = CustomTitleLayoutBinding.inflate(layoutInflater) @@ -94,8 +132,20 @@ class DetailActivity : AppCompatActivity(), OnItemClickListener { password = dialogBinding.inputPassword.text.toString() // 비밀번호 검증 (뷰모델로 password 전송하고, 뷰모델은 모델로 password 전송. - permission = viewModel.getPermission(itemId, password) - dialog.dismiss() + lifecycleScope.launch(Dispatchers.IO) { + // 비동기 작업을 수행할 코루틴 블록 + val result = viewModel.pushPassword(itemId, password) + + withContext(Dispatchers.Main) { + // 메인 스레드에서 수행할 작업 + if (result != "SUCCESS") { + Toast.makeText(this@DetailActivity, result, Toast.LENGTH_SHORT).show() + + } + } + + dialog.dismiss() + } } .setNegativeButton("Cancel") { dialog, _ -> dialog.dismiss() @@ -116,11 +166,21 @@ class DetailActivity : AppCompatActivity(), OnItemClickListener { .setPositiveButton("Submit") { dialog, _ -> val spell = dialogBinding.dialogWordSpell.text.toString() val meaning = dialogBinding.dialogWordMeaning.text.toString() - val synonym = dialogBinding.dialogWordSynonym.text?.toString() - val antonym = dialogBinding.dialogWordAntonym.text?.toString() - val sentence = dialogBinding.dialogWordSentence.text?.toString() - viewModel.putWord(itemId, MyData.WordInfo(spell, meaning, synonym, antonym, sentence)) - // 비밀번호 검증 (뷰모델로 password 전송하고, 뷰모델은 모델로 password 전송. + val synonym = dialogBinding.dialogWordSynonym.text?.toString() ?: null + val antonym = dialogBinding.dialogWordAntonym.text?.toString() ?: null + val sentence = dialogBinding.dialogWordSentence.text?.toString() ?: null + val wordDetail = MyData.WordInfo(spell, meaning, synonym, antonym, sentence) + val word = MyData.WordPutInfo(password, wordDetail) + lifecycleScope.launch(Dispatchers.IO) { + // 비동기 작업을 수행할 코루틴 블록 + val result = viewModel.putWord(itemId, word) + withContext(Dispatchers.Main) { + // 메인 스레드에서 수행할 작업 + if (result != "SUCCESS") { + Toast.makeText(this@DetailActivity, result, Toast.LENGTH_SHORT).show() + } + } + } dialog.dismiss() } diff --git a/assignment-3/app/src/main/java/com/jutak/assignment3/MainActivity.kt b/assignment-3/app/src/main/java/com/jutak/assignment3/MainActivity.kt index 6bcbae45..8f0b24d9 100644 --- a/assignment-3/app/src/main/java/com/jutak/assignment3/MainActivity.kt +++ b/assignment-3/app/src/main/java/com/jutak/assignment3/MainActivity.kt @@ -4,14 +4,19 @@ import android.content.Intent import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import android.util.Log +import android.widget.Toast import androidx.activity.viewModels import androidx.appcompat.app.AlertDialog import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager import com.jutak.assignment3.databinding.ActivityMainBinding import com.jutak.assignment3.databinding.CustomTitleLayoutBinding import com.jutak.assignment3.databinding.PostDialogLayoutBinding import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import javax.inject.Inject @@ -22,6 +27,8 @@ class MainActivity : AppCompatActivity(), OnItemClickListener { private val viewModel: MainViewModel by viewModels() + private var wordListInfos = emptyList() + override fun onItemClick(itemId: Int) { // 클릭한 아이템의 ID를 사용하여 필요한 동작 수행 // 예: 상세 화면으로 전환하면서 ID를 전달 @@ -34,19 +41,38 @@ class MainActivity : AppCompatActivity(), OnItemClickListener { binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) + lifecycleScope.launch(Dispatchers.IO) { + // 비동기 작업을 수행할 코루틴 블록 + val result = viewModel.fetchWordListInfo() + withContext(Dispatchers.Main) { + // 메인 스레드에서 수행할 작업 + if (result != "SUCCESS") { + Toast.makeText(this@MainActivity, result, Toast.LENGTH_SHORT).show() + } + } + wordListInfos = viewModel.getWordListInfo() + withContext(Dispatchers.Main) { + // 메인 스레드에서 수행할 작업 + if (result == "SUCCESS") { + val adapter = WordListAdapter(this@MainActivity, wordListInfos.toMutableList(), this@MainActivity) + binding.wordList.adapter= adapter + binding.wordList.layoutManager = LinearLayoutManager(this@MainActivity) + + viewModel.wordListInfoData.observe(this@MainActivity) { wordListInfo -> + // LiveData를 관찰하여 데이터를 얻고, adapter를 업데이트 + Log.d("DetailActivity", "Word list size: ${wordListInfo.size}") // 디버그 로그 추가 + adapter.setItems(wordListInfo) + } + } + } + + } + binding.postBtn.setOnClickListener() { showInputDialog() } - val adapter = WordListAdapter(this, viewModel.getWordListInfo(), this) - binding.wordList.adapter= adapter - binding.wordList.layoutManager = LinearLayoutManager(this) - viewModel.wordListInfoData.observe(this) { wordListInfo -> - // LiveData를 관찰하여 데이터를 얻고, adapter를 업데이트 - Log.d("DetailActivity", "Word list size: ${wordListInfo.size}") // 디버그 로그 추가 - adapter.setItems(wordListInfo) - } } @@ -65,7 +91,17 @@ class MainActivity : AppCompatActivity(), OnItemClickListener { // 데이터 처리 (예: 로그로 출력) val message = MyData.WordListPostInfo(name, owner, password) - viewModel.pushWordList(message) + + lifecycleScope.launch(Dispatchers.IO) { + // 비동기 작업을 수행할 코루틴 블록 + val result = viewModel.pushWordList(message) + withContext(Dispatchers.Main) { + // 메인 스레드에서 수행할 작업 + if (result != "SUCCESS") { + Toast.makeText(this@MainActivity, result, Toast.LENGTH_SHORT).show() + } + } + } dialog.dismiss() } .setNegativeButton("Cancel") { dialog, _ -> diff --git a/assignment-3/app/src/main/java/com/jutak/assignment3/MainRepository.kt b/assignment-3/app/src/main/java/com/jutak/assignment3/MainRepository.kt index 3372a5f9..80fca8e2 100644 --- a/assignment-3/app/src/main/java/com/jutak/assignment3/MainRepository.kt +++ b/assignment-3/app/src/main/java/com/jutak/assignment3/MainRepository.kt @@ -1,34 +1,51 @@ package com.jutak.assignment3 -import dagger.hilt.android.AndroidEntryPoint -import retrofit2.Call import retrofit2.Response +import java.io.IOException import javax.inject.Inject class MainRepository @Inject constructor(private val api: MyRestAPI) { - suspend fun getWordListInfo(): MutableList { - return api.getWordListInfo().toMutableList() + enum class Signs { + SUCCESS, + FAILURE, + EXCEPTION } - suspend fun getWordList(id: Int): MyData.WordList { - return api.getWordList(id) + suspend fun getWordListInfo(): Pair { + return proceedException(api.getWordListInfo()) } - suspend fun pushWordList(data: MyData.WordListPostInfo): List { - return api.createWordList(data) + suspend fun getWordList(id: Int): Pair { + return proceedException(api.getWordList(id)) } - suspend fun verifyPassword(id: Int, password: String): Boolean { - return api.verifyPermission(id, password) + suspend fun pushWordList(data: MyData.WordListPostInfo): Pair { + return proceedException(api.createWordList(data)) } - suspend fun deleteWordList(id: Int, password: String): Response { - return api.deleteWordList(id, password) + suspend fun verifyPassword(id: Int, password: String): Pair { + return proceedException(api.verifyPermission(id, MyData.PasswordJSON(password))) } - suspend fun putWord(id: Int, word: MyData.WordInfo): MutableList { - return api.putWord(id, word).word_list.toMutableList() + suspend fun deleteWordList(id: Int, password: String): Pair { + return proceedException(api.deleteWordList(id, MyData.PasswordJSON(password))) + } + + suspend fun putWord(id: Int, word: MyData.WordPutInfo): Pair { + return proceedException(api.putWord(id, word)) + } + + fun proceedException(response: Response): Pair { + try { + if (response.isSuccessful) { + return Pair(Signs.SUCCESS, response.body()) + } else { + return Pair(Signs.FAILURE, response.errorBody()?.string()) + } + } catch (e: IOException){ + return Pair(Signs.EXCEPTION, "Network Error") + } } } \ No newline at end of file diff --git a/assignment-3/app/src/main/java/com/jutak/assignment3/MainViewModel.kt b/assignment-3/app/src/main/java/com/jutak/assignment3/MainViewModel.kt index bc51cb7a..8b7a5709 100644 --- a/assignment-3/app/src/main/java/com/jutak/assignment3/MainViewModel.kt +++ b/assignment-3/app/src/main/java/com/jutak/assignment3/MainViewModel.kt @@ -23,86 +23,136 @@ class MainViewModel @Inject constructor(private val repository: MainRepository) private val _wordListData = MutableLiveData>() val wordListData: LiveData> get() = _wordListData // ViewModel에서 사용할 메서드 및 데이터를 정의 + private val _permission = MutableLiveData() + val permission: LiveData get() = _permission - private var wordListInfo = mutableListOf() + private var wordListInfos = mutableListOf() private var wordInfos = mutableListOf() - private var permission = false + private var permissionData = false - fun getWordListInfo(): MutableList { + suspend fun getWordListInfo(): MutableList { fetchWordListInfo() - return wordListInfo + return wordListInfos } - fun getWordInfos(id: Int): MutableList { + suspend fun getWordList(id: Int): MutableList { fetchWordList(id) return wordInfos } - fun getPermission(id: Int, password: String): Boolean { + suspend fun getPermission(id: Int, password: String): Boolean { pushPassword(id, password) - return permission + return permissionData } - fun fetchWordListInfo() { - viewModelScope.launch(Dispatchers.IO) { - wordListInfo = repository.getWordListInfo() - withContext(Dispatchers.Main) { - _wordListInfoData.value = wordListInfo - Log.d("MainViewModel", "WordListInfos Size: ${wordListInfo.size}") // 디버그 로그 추가 - } + + suspend fun fetchWordListInfo(): String { + val fetchedList: Pair = repository.getWordListInfo() + val sign = fetchedList.first + val responseValue = fetchedList.second + when (sign) { + MainRepository.Signs.SUCCESS -> + withContext(Dispatchers.Main) { + wordListInfos = responseValue as MutableList + _wordListInfoData.value = wordListInfos + } + MainRepository.Signs.FAILURE -> return responseValue as String + MainRepository.Signs.EXCEPTION -> return responseValue as String } + + return "SUCCESS" } - fun fetchWordList(id: Int) { - viewModelScope.launch(Dispatchers.IO) { - wordInfos = repository.getWordList(id).word_list.toMutableList() - Log.d("MainViewModel", "Word list size: ${wordInfos.size}") // 디버그 로그 추가 + // suspend fun이 아닐 경우, 다 받아오기 전에 함수가 종료될 수 있음 + // suspend fun을 쓰고, viewModelScope...를 없애는 경우: Activity에서도 코루틴을... + suspend fun fetchWordList(id: Int): String { + val fetchedList: Pair = repository.getWordList(id) + val sign = fetchedList.first + val responseValue = fetchedList.second + when (sign) { + MainRepository.Signs.SUCCESS -> withContext(Dispatchers.Main) { - _wordListData.setValue(wordInfos) - } + val wordList = responseValue as MyData.WordList + wordInfos = wordList.word_list.toMutableList() + _wordListData.value = wordInfos + } + MainRepository.Signs.FAILURE -> return responseValue as String + MainRepository.Signs.EXCEPTION -> return responseValue as String } + + return "SUCCESS" } - fun pushWordList(data: MyData.WordListPostInfo) { - viewModelScope.launch(Dispatchers.IO) { - val response = repository.pushWordList(data).toMutableList() - withContext(Dispatchers.Main) { - _wordListInfoData.value = response - Log.d("MainViewModel", "WordListInfos Size: ${response.size}") // 디버그 로그 추가 - } + suspend fun pushWordList(data: MyData.WordListPostInfo): String { + val fetchedList: Pair = repository.pushWordList(data) + val sign = fetchedList.first + val responseValue = fetchedList.second + when (sign) { + MainRepository.Signs.SUCCESS -> + withContext(Dispatchers.Main) { + _wordListInfoData.value = responseValue as MutableList + } + MainRepository.Signs.FAILURE -> return responseValue as String + MainRepository.Signs.EXCEPTION -> return responseValue as String } + + + + return "SUCCESS" } - fun pushPassword(id: Int, password: String) { - viewModelScope.launch(Dispatchers.IO) { - val response = repository.verifyPassword(id, password) - withContext(Dispatchers.Main) { - permission = response - } + suspend fun pushPassword(id: Int, password: String): String { + val fetchedList: Pair = repository.verifyPassword(id, password) + val sign = fetchedList.first + val responseValue = fetchedList.second + + when (sign) { + MainRepository.Signs.SUCCESS -> + withContext(Dispatchers.Main) { + val response = responseValue as MyData.Validation + permissionData = response.valid + _permission.value = permissionData + } + MainRepository.Signs.FAILURE -> return responseValue as String + MainRepository.Signs.EXCEPTION -> return responseValue as String } + + return "SUCCESS" } - fun deleteWordList(id: Int, password: String): String { - var bodyText = "" - viewModelScope.launch(Dispatchers.IO) { - val response = repository.deleteWordList(id, password) - if (response.isSuccessful) { - bodyText = response.body().toString() - } - else { - bodyText = response.errorBody().toString() - } + suspend fun deleteWordList(id: Int, password: String): String { + if (!(_permission.value ?: false)) { + return "Wrong Access." + } + val fetchedList: Pair = repository.deleteWordList(id, password) + val sign = fetchedList.first + val responseValue = fetchedList.second + + when (sign) { + MainRepository.Signs.SUCCESS -> fetchWordListInfo() + MainRepository.Signs.FAILURE -> return responseValue as String + MainRepository.Signs.EXCEPTION -> return responseValue as String } - return bodyText + + return "SUCCESS" } - fun putWord(id: Int, word: MyData.WordInfo) { + suspend fun putWord(id: Int, word: MyData.WordPutInfo): String { + if (!(_permission.value?: false)) { + return "Wrong Access." + } + + val fetchedList: Pair = repository.putWord(id, word) + val sign = fetchedList.first + val responseValue = fetchedList.second + Log.d("VM", "putWord On") - viewModelScope.launch(Dispatchers.IO) { - wordInfos = repository.putWord(id, word) - withContext(Dispatchers.Main) { - _wordListData.value = wordInfos - } + when (sign) { + MainRepository.Signs.SUCCESS -> fetchWordList(id) + MainRepository.Signs.FAILURE -> return responseValue as String + MainRepository.Signs.EXCEPTION -> return responseValue as String } + + return "SUCCESS" } } \ No newline at end of file diff --git a/assignment-3/app/src/main/java/com/jutak/assignment3/MyData.kt b/assignment-3/app/src/main/java/com/jutak/assignment3/MyData.kt index a030469b..0b1c323b 100644 --- a/assignment-3/app/src/main/java/com/jutak/assignment3/MyData.kt +++ b/assignment-3/app/src/main/java/com/jutak/assignment3/MyData.kt @@ -37,7 +37,21 @@ sealed class MyData(val viewType: ViewType) { @Json(name="password") val word_list: String ) : MyData(ViewType.WORD_LIST_POST_INFO) + @JsonClass(generateAdapter = true) + data class PasswordJSON( + @Json(name="password") val password: String + ) + + @JsonClass(generateAdapter = true) + data class WordPutInfo( + @Json(name="password") val password: String, + @Json(name="word") val word: WordInfo + ) + @JsonClass(generateAdapter = true) + data class Validation( + @Json(name="valid") val valid: Boolean + ) enum class ViewType { WORD_LIST_INFO, diff --git a/assignment-3/app/src/main/java/com/jutak/assignment3/MyRestAPI.kt b/assignment-3/app/src/main/java/com/jutak/assignment3/MyRestAPI.kt index 47286750..7b05a939 100644 --- a/assignment-3/app/src/main/java/com/jutak/assignment3/MyRestAPI.kt +++ b/assignment-3/app/src/main/java/com/jutak/assignment3/MyRestAPI.kt @@ -8,23 +8,24 @@ import retrofit2.http.POST import retrofit2.http.PUT import retrofit2.http.Path +//JSON으로 소통하기에 POST 혹은 GET으로 들어오는 DATA는 Json 객체여야 함! interface MyRestAPI { @GET("/myapp/v1/word_lists") - suspend fun getWordListInfo(): List + suspend fun getWordListInfo(): Response> @GET("/myapp/v1/word_list/{id}") - suspend fun getWordList(@Path("id") id: Int): MyData.WordList + suspend fun getWordList(@Path("id") id: Int): Response @POST("/myapp/v1/word_list") - suspend fun createWordList(@Body data: MyData.WordListPostInfo): List + suspend fun createWordList(@Body data: MyData.WordListPostInfo): Response> @POST("/myapp/v1/word_list/{id}/permission") - suspend fun verifyPermission(@Path("id") id: Int, @Body password: String): Boolean + suspend fun verifyPermission(@Path("id") id: Int, @Body password: MyData.PasswordJSON): Response @DELETE("/myapp/v1/word_list/{id}") - suspend fun deleteWordList(@Path("id") id: Int, @Body password: String): Response + suspend fun deleteWordList(@Path("id") id: Int, @Body password: MyData.PasswordJSON): Response @PUT("/myapp/v1/word_list/{id}") - suspend fun putWord(@Path("id") id: Int, @Body word: MyData.WordInfo): MyData.WordList + suspend fun putWord(@Path("id") id: Int, @Body word: MyData.WordPutInfo): Response } \ No newline at end of file diff --git a/assignment-3/app/src/main/java/com/jutak/assignment3/WordListAdapter.kt b/assignment-3/app/src/main/java/com/jutak/assignment3/WordListAdapter.kt index a0c0c341..826dcd04 100644 --- a/assignment-3/app/src/main/java/com/jutak/assignment3/WordListAdapter.kt +++ b/assignment-3/app/src/main/java/com/jutak/assignment3/WordListAdapter.kt @@ -27,9 +27,9 @@ class WordListAdapter(private val context:Context, private var items: // 다이얼로그 내용 설정 customLayoutBinding.dialogText.text = "Spell: ${item.spell}" + "\nMeaning: ${item.meaning}" + - "\nSynonym: ${item.synonym}" + - "\nAntonym: ${item.antonym}" + - "\nSentence: ${item.sentence}" + "\nSynonym: ${item.synonym.takeIf { it != null }}" + + "\nAntonym: ${item.antonym.takeIf { it != null }}" + + "\nSentence: ${item.sentence.takeIf { it != null }}" // Positive Button 클릭 리스너 설정 customLayoutBinding.positiveButton.setOnClickListener { From 58c5f0babaf6c07587112cf0eccdb9ee2897c0e4 Mon Sep 17 00:00:00 2001 From: Glenn-syj Date: Mon, 30 Oct 2023 06:01:46 +0900 Subject: [PATCH 4/4] Challenge almost done wo/ using resultFor... . Need to modify non-syntax errors with HTTP communication. --- .../com/jutak/assignment3/DetailActivity.kt | 23 ++++++++++++------- .../com/jutak/assignment3/MainActivity.kt | 4 ++++ .../com/jutak/assignment3/MainRepository.kt | 10 ++++++-- .../com/jutak/assignment3/MainViewModel.kt | 2 ++ .../main/java/com/jutak/assignment3/MyData.kt | 1 + .../java/com/jutak/assignment3/MyRestAPI.kt | 7 ++++-- 6 files changed, 35 insertions(+), 12 deletions(-) diff --git a/assignment-3/app/src/main/java/com/jutak/assignment3/DetailActivity.kt b/assignment-3/app/src/main/java/com/jutak/assignment3/DetailActivity.kt index 689a3dd6..f6d6f294 100644 --- a/assignment-3/app/src/main/java/com/jutak/assignment3/DetailActivity.kt +++ b/assignment-3/app/src/main/java/com/jutak/assignment3/DetailActivity.kt @@ -1,11 +1,14 @@ package com.jutak.assignment3 +import android.app.Activity import android.content.Intent import android.os.Bundle import android.util.Log import android.view.View import android.widget.TextView import android.widget.Toast +import androidx.activity.result.ActivityResult +import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.viewModels import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity @@ -31,6 +34,8 @@ class DetailActivity : AppCompatActivity(), OnItemClickListener { // valid 인증 받은 password 및 permission을 단어 삭제와 추가에도 계속 써야 하므로, DetailActivity 내 전역변수 선언 private var password = "" private var wordInfos = emptyList() + + override fun onItemClick(itemId: Int) { } @@ -76,7 +81,8 @@ class DetailActivity : AppCompatActivity(), OnItemClickListener { val backButton = binding.backButton backButton.setOnClickListener() { - onBackPressed() // deprecated 되어서 바꿔야 함! + val intent = Intent(this@DetailActivity, MainActivity::class.java) + startActivity(intent) } val editButton = binding.detailEditBtn @@ -104,7 +110,8 @@ class DetailActivity : AppCompatActivity(), OnItemClickListener { Toast.makeText(this@DetailActivity, result, Toast.LENGTH_SHORT).show() } // 요청 성공 시 메인화면으로 <- 요청 성공/실패 체크] - onBackPressed() + val intent = Intent(this@DetailActivity, MainActivity::class.java) + startActivity(intent) } } } @@ -129,22 +136,22 @@ class DetailActivity : AppCompatActivity(), OnItemClickListener { .setCustomTitle(customTitleLayoutBinding.root) .setTitle("수정 권한 확인하기") .setPositiveButton("Submit") { dialog, _ -> - password = dialogBinding.inputPassword.text.toString() - + val inputPassword = dialogBinding.inputPassword.text.toString() + password = inputPassword // 비밀번호 검증 (뷰모델로 password 전송하고, 뷰모델은 모델로 password 전송. lifecycleScope.launch(Dispatchers.IO) { // 비동기 작업을 수행할 코루틴 블록 - val result = viewModel.pushPassword(itemId, password) + val result = viewModel.pushPassword(itemId, inputPassword) withContext(Dispatchers.Main) { // 메인 스레드에서 수행할 작업 if (result != "SUCCESS") { + // 왜 text가 안 뜨지? Toast.makeText(this@DetailActivity, result, Toast.LENGTH_SHORT).show() } + dialog.dismiss() } - - dialog.dismiss() } } .setNegativeButton("Cancel") { dialog, _ -> @@ -193,4 +200,4 @@ class DetailActivity : AppCompatActivity(), OnItemClickListener { } -} \ No newline at end of file +} diff --git a/assignment-3/app/src/main/java/com/jutak/assignment3/MainActivity.kt b/assignment-3/app/src/main/java/com/jutak/assignment3/MainActivity.kt index 8f0b24d9..990d91cb 100644 --- a/assignment-3/app/src/main/java/com/jutak/assignment3/MainActivity.kt +++ b/assignment-3/app/src/main/java/com/jutak/assignment3/MainActivity.kt @@ -1,10 +1,14 @@ package com.jutak.assignment3 +import android.app.Activity import android.content.Intent import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import android.util.Log import android.widget.Toast +import androidx.activity.result.ActivityResult +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.viewModels import androidx.appcompat.app.AlertDialog import androidx.lifecycle.ViewModelProvider diff --git a/assignment-3/app/src/main/java/com/jutak/assignment3/MainRepository.kt b/assignment-3/app/src/main/java/com/jutak/assignment3/MainRepository.kt index 80fca8e2..80ef796f 100644 --- a/assignment-3/app/src/main/java/com/jutak/assignment3/MainRepository.kt +++ b/assignment-3/app/src/main/java/com/jutak/assignment3/MainRepository.kt @@ -1,5 +1,6 @@ package com.jutak.assignment3 +import android.util.Log import retrofit2.Response import java.io.IOException import javax.inject.Inject @@ -26,10 +27,14 @@ class MainRepository @Inject constructor(private val api: MyRestAPI) { suspend fun verifyPassword(id: Int, password: String): Pair { return proceedException(api.verifyPermission(id, MyData.PasswordJSON(password))) + // 왜 잘못 입력했는데 200이 뜨지... } suspend fun deleteWordList(id: Int, password: String): Pair { - return proceedException(api.deleteWordList(id, MyData.PasswordJSON(password))) + val response = api.deleteWordList(id, MyData.PasswordJSON(password)) + Log.d("Model", "${response.code()}") + return proceedException(response) + } suspend fun putWord(id: Int, word: MyData.WordPutInfo): Pair { @@ -39,9 +44,10 @@ class MainRepository @Inject constructor(private val api: MyRestAPI) { fun proceedException(response: Response): Pair { try { if (response.isSuccessful) { + Log.d("Model", "${response.body()}") return Pair(Signs.SUCCESS, response.body()) } else { - return Pair(Signs.FAILURE, response.errorBody()?.string()) + return Pair(Signs.FAILURE, response.errorBody()?.string() ?: response.code().toString()) } } catch (e: IOException){ return Pair(Signs.EXCEPTION, "Network Error") diff --git a/assignment-3/app/src/main/java/com/jutak/assignment3/MainViewModel.kt b/assignment-3/app/src/main/java/com/jutak/assignment3/MainViewModel.kt index 8b7a5709..4353261e 100644 --- a/assignment-3/app/src/main/java/com/jutak/assignment3/MainViewModel.kt +++ b/assignment-3/app/src/main/java/com/jutak/assignment3/MainViewModel.kt @@ -128,6 +128,8 @@ class MainViewModel @Inject constructor(private val repository: MainRepository) val sign = fetchedList.first val responseValue = fetchedList.second + Log.d("VM", "dwL On") + when (sign) { MainRepository.Signs.SUCCESS -> fetchWordListInfo() MainRepository.Signs.FAILURE -> return responseValue as String diff --git a/assignment-3/app/src/main/java/com/jutak/assignment3/MyData.kt b/assignment-3/app/src/main/java/com/jutak/assignment3/MyData.kt index 0b1c323b..152f9284 100644 --- a/assignment-3/app/src/main/java/com/jutak/assignment3/MyData.kt +++ b/assignment-3/app/src/main/java/com/jutak/assignment3/MyData.kt @@ -6,6 +6,7 @@ import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) sealed class MyData(val viewType: ViewType) { + // Parcelable 하게 만들어야 intent로 전달 가능... @JsonClass(generateAdapter = true) data class WordListInfo( @Json(name="id") val id: Int, diff --git a/assignment-3/app/src/main/java/com/jutak/assignment3/MyRestAPI.kt b/assignment-3/app/src/main/java/com/jutak/assignment3/MyRestAPI.kt index 7b05a939..6db72259 100644 --- a/assignment-3/app/src/main/java/com/jutak/assignment3/MyRestAPI.kt +++ b/assignment-3/app/src/main/java/com/jutak/assignment3/MyRestAPI.kt @@ -4,9 +4,11 @@ import retrofit2.Response import retrofit2.http.Body import retrofit2.http.DELETE import retrofit2.http.GET +import retrofit2.http.HTTP import retrofit2.http.POST import retrofit2.http.PUT import retrofit2.http.Path +import retrofit2.http.Query //JSON으로 소통하기에 POST 혹은 GET으로 들어오는 DATA는 Json 객체여야 함! interface MyRestAPI { @@ -22,8 +24,9 @@ interface MyRestAPI { @POST("/myapp/v1/word_list/{id}/permission") suspend fun verifyPermission(@Path("id") id: Int, @Body password: MyData.PasswordJSON): Response - @DELETE("/myapp/v1/word_list/{id}") - suspend fun deleteWordList(@Path("id") id: Int, @Body password: MyData.PasswordJSON): Response + // DELETE 메소드에서 Body를 사용하기 위한 방법 + @HTTP(method="DELETE", path="/myapp/v1/word_list/{id}", hasBody=true) + suspend fun deleteWordList(@Path("id") id: Int, @Body data: MyData.PasswordJSON): Response @PUT("/myapp/v1/word_list/{id}") suspend fun putWord(@Path("id") id: Int, @Body word: MyData.WordPutInfo): Response