diff --git a/app/build.gradle.kts b/app/build.gradle.kts index d3941a7..24aaf57 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,6 +1,19 @@ +import java.util.Properties + plugins { alias(libs.plugins.android.application) alias(libs.plugins.jetbrains.kotlin.android) + +} + + +fun getApiKey(): String { + val properties = Properties() + val localPropertiesFile = rootProject.file("local.properties") + if (localPropertiesFile.exists()) { + localPropertiesFile.inputStream().use { properties.load(it) } + } + return properties.getProperty("API_KEY") ?: "" } android { @@ -13,6 +26,8 @@ android { targetSdk = 34 versionCode = 1 versionName = "1.0" + buildConfigField("String", "API_KEY", "\"${getApiKey()}\"") + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } @@ -27,6 +42,12 @@ android { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 } + viewBinding { + enable = true + } + buildFeatures{ + buildConfig=true + } kotlinOptions { jvmTarget = "1.8" } @@ -42,4 +63,13 @@ dependencies { testImplementation(libs.junit) androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) + implementation("com.github.bumptech.glide:glide:4.16.0") + annotationProcessor("com.github.bumptech.glide:compiler:4.16.0") + + implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0") + implementation ("com.google.code.gson:gson:2.8.6") + implementation("com.squareup.retrofit2:retrofit:2.9.0") + implementation("com.squareup.retrofit2:converter-gson:2.9.0") + implementation("com.squareup.okhttp3:logging-interceptor:4.9.0") + implementation("com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2") } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 0acee2a..56af475 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -12,6 +12,9 @@ android:supportsRtl="true" android:theme="@style/Theme.Android_study_14_2" tools:targetApi="31"> + diff --git a/app/src/main/java/com/alom/androidstudy2/AddMemoActivity.kt b/app/src/main/java/com/alom/androidstudy2/AddMemoActivity.kt new file mode 100644 index 0000000..3cce763 --- /dev/null +++ b/app/src/main/java/com/alom/androidstudy2/AddMemoActivity.kt @@ -0,0 +1,52 @@ +package com.alom.androidstudy2 + +import android.content.Context +import android.os.Bundle +import androidx.activity.enableEdgeToEdge +import androidx.activity.viewModels +import androidx.appcompat.app.AppCompatActivity +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat +import com.alom.androidstudy2.databinding.ActivityAddMemoBinding + +class AddMemoActivity : AppCompatActivity() { + + private lateinit var binding: ActivityAddMemoBinding + + private val viewModel: MainViewModel by viewModels { + ViewModelFactory( + RepositoryImpl( + getSharedPreferences("MyPrefs", Context.MODE_PRIVATE), + RetrofitClient.apiService + ) + ) + } + + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityAddMemoBinding.inflate(layoutInflater) + setContentView(binding.root) + + binding.btnSave.setOnClickListener { + val title = binding.etTitle.text.toString() + val price = binding.etPrice.text.toString() + val time = binding.etTime.text.toString() + + + val newMemo = Memo( + title = title, + price = price, + imageUrl = "", + time = time, + id = (0..9999999).random() + ) + + + viewModel.addMemo(newMemo) + + + finish() + } + } +} diff --git a/app/src/main/java/com/alom/androidstudy2/AddMemoRequest.kt b/app/src/main/java/com/alom/androidstudy2/AddMemoRequest.kt new file mode 100644 index 0000000..37318ec --- /dev/null +++ b/app/src/main/java/com/alom/androidstudy2/AddMemoRequest.kt @@ -0,0 +1,11 @@ +package com.alom.androidstudy2 + +import com.google.gson.annotations.SerializedName + + + +data class AddMemoRequest( + @SerializedName("p_title") val title: String, + @SerializedName("p_price") val price: String, + @SerializedName("p_time") val time: String +) \ No newline at end of file diff --git a/app/src/main/java/com/alom/androidstudy2/AddMemoResponse.kt b/app/src/main/java/com/alom/androidstudy2/AddMemoResponse.kt new file mode 100644 index 0000000..95d5a1c --- /dev/null +++ b/app/src/main/java/com/alom/androidstudy2/AddMemoResponse.kt @@ -0,0 +1,8 @@ +package com.alom.androidstudy2 + +import com.google.gson.annotations.SerializedName + +data class AddMemoResponse( + @SerializedName("result") val result: Int + +) \ No newline at end of file diff --git a/app/src/main/java/com/alom/androidstudy2/GetMemoResponse.kt b/app/src/main/java/com/alom/androidstudy2/GetMemoResponse.kt new file mode 100644 index 0000000..5bde647 --- /dev/null +++ b/app/src/main/java/com/alom/androidstudy2/GetMemoResponse.kt @@ -0,0 +1,9 @@ +package com.alom.androidstudy2 + +import com.google.gson.annotations.SerializedName + +data class GetMemoResponse( + @SerializedName("result") val result: Int, + @SerializedName("message") val message: String, + @SerializedName("data") val data: List +) \ No newline at end of file diff --git a/app/src/main/java/com/alom/androidstudy2/MainActivity.kt b/app/src/main/java/com/alom/androidstudy2/MainActivity.kt index 54ce3ce..9ef042d 100644 --- a/app/src/main/java/com/alom/androidstudy2/MainActivity.kt +++ b/app/src/main/java/com/alom/androidstudy2/MainActivity.kt @@ -1,20 +1,51 @@ package com.alom.androidstudy2 +import android.content.Context +import android.content.Intent import android.os.Bundle import androidx.activity.enableEdgeToEdge +import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat +import androidx.lifecycle.lifecycleScope +import com.alom.androidstudy2.databinding.ActivityMainBinding class MainActivity : AppCompatActivity() { + + private lateinit var binding: ActivityMainBinding + private lateinit var memoListAdapter: MemoListAdapter + + + private val viewModel: MainViewModel by viewModels { + ViewModelFactory( + RepositoryImpl( + getSharedPreferences("MyPrefs", Context.MODE_PRIVATE), + RetrofitClient.apiService + ) + ) + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - enableEdgeToEdge() - setContentView(R.layout.activity_main) - 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 + binding = ActivityMainBinding.inflate(layoutInflater) + setContentView(binding.root) + + + memoListAdapter = MemoListAdapter() + binding.recyclerView.adapter = memoListAdapter + + + lifecycleScope.launchWhenStarted { + viewModel.currentMemo.collect { memoList -> + memoListAdapter.submitList(memoList) + } + } + + + binding.btnAdd.setOnClickListener { + val intent = Intent(this, AddMemoActivity::class.java) + startActivity(intent) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/alom/androidstudy2/MainViewModel.kt b/app/src/main/java/com/alom/androidstudy2/MainViewModel.kt new file mode 100644 index 0000000..7543aff --- /dev/null +++ b/app/src/main/java/com/alom/androidstudy2/MainViewModel.kt @@ -0,0 +1,42 @@ +package com.alom.androidstudy2 +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +class MainViewModel(private val repository: MemoRepository) : ViewModel() { + + private var _currentMemo = MutableStateFlow>(emptyList()) + val currentMemo: StateFlow> get() = _currentMemo.asStateFlow() + + init { + + loadMemoList() + } + + fun loadMemoList() { + viewModelScope.launch { + val memos = withContext(Dispatchers.IO) { + repository.getMemos() + } + _currentMemo.emit(memos) + } + } + + fun addMemo(memo: Memo) { + viewModelScope.launch { + + withContext(Dispatchers.IO) { + repository.addMemo(memo) + } + + val memos = repository.getMemos() + + _currentMemo.emit(memos) + } + } +} diff --git a/app/src/main/java/com/alom/androidstudy2/MainviewModelFactory.kt b/app/src/main/java/com/alom/androidstudy2/MainviewModelFactory.kt new file mode 100644 index 0000000..dfbd991 --- /dev/null +++ b/app/src/main/java/com/alom/androidstudy2/MainviewModelFactory.kt @@ -0,0 +1,13 @@ +package com.alom.androidstudy2 + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider + +class ViewModelFactory(private val repository: MemoRepository): ViewModelProvider.Factory { + override fun create(modelClass: Class): T { + if (modelClass.isAssignableFrom(MainViewModel::class.java)) { + return MainViewModel(repository) as T + } + throw IllegalArgumentException("ViewModel class not found") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/alom/androidstudy2/Memo.kt b/app/src/main/java/com/alom/androidstudy2/Memo.kt new file mode 100644 index 0000000..39a6cc2 --- /dev/null +++ b/app/src/main/java/com/alom/androidstudy2/Memo.kt @@ -0,0 +1,12 @@ +package com.alom.androidstudy2 + +import com.google.gson.annotations.SerializedName + +data class Memo( + @SerializedName("id") val id: Int, + @SerializedName("title") val title: String, + @SerializedName("price") val price: String, + @SerializedName("image_url") val imageUrl: String, + @SerializedName("time") val time: String +) + diff --git a/app/src/main/java/com/alom/androidstudy2/MemoListAdapter.kt b/app/src/main/java/com/alom/androidstudy2/MemoListAdapter.kt new file mode 100644 index 0000000..1111896 --- /dev/null +++ b/app/src/main/java/com/alom/androidstudy2/MemoListAdapter.kt @@ -0,0 +1,46 @@ +package com.alom.androidstudy2 + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import com.alom.androidstudy2.databinding.ItemMemoBinding +import com.bumptech.glide.Glide + + +object MemoDiffUtil : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: Memo, newItem: Memo): Boolean { + + return oldItem.id == newItem.id + } + + override fun areContentsTheSame(oldItem: Memo, newItem: Memo): Boolean { + + return oldItem == newItem + } +} + + +class MemoListAdapter : ListAdapter(MemoDiffUtil) { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MemoViewHolder { + val inflater = LayoutInflater.from(parent.context) + val binding = ItemMemoBinding.inflate(inflater, parent, false) + return MemoViewHolder(binding) + } + + override fun onBindViewHolder(holder: MemoViewHolder, position: Int) { + val memoItem = getItem(position) + holder.bind(memoItem) + } + + inner class MemoViewHolder(private val binding: ItemMemoBinding) : RecyclerView.ViewHolder(binding.root) { + fun bind(memo: Memo) { + binding.tvTitle.text = memo.title + binding.tvPrice.text = memo.price + binding.tvTime.text=memo.time + Glide.with(binding.imgItem).load(memo.imageUrl).into(binding.imgItem) + } + } +} diff --git a/app/src/main/java/com/alom/androidstudy2/MemoRepository.kt b/app/src/main/java/com/alom/androidstudy2/MemoRepository.kt new file mode 100644 index 0000000..e214235 --- /dev/null +++ b/app/src/main/java/com/alom/androidstudy2/MemoRepository.kt @@ -0,0 +1,10 @@ +package com.alom.androidstudy2 + +interface MemoRepository { + suspend fun getMemos(): List + suspend fun addMemo(memo: Memo): AddMemoResponse + suspend fun setMemo(memo: Memo) + + + +} \ No newline at end of file diff --git a/app/src/main/java/com/alom/androidstudy2/MemoRepositoryImpl.kt b/app/src/main/java/com/alom/androidstudy2/MemoRepositoryImpl.kt new file mode 100644 index 0000000..d74f7b6 --- /dev/null +++ b/app/src/main/java/com/alom/androidstudy2/MemoRepositoryImpl.kt @@ -0,0 +1,59 @@ +package com.alom.androidstudy2 + + + +import android.content.SharedPreferences +import com.google.gson.Gson + +class RepositoryImpl( + private val sharedPreferences: SharedPreferences, + private val apiService: RetrofitInterface +) : MemoRepository { + + private val gson = Gson() + private val memoKey = "memo_list" + + + override suspend fun setMemo(memo: Memo) { + + val memoJson = sharedPreferences.getString(memoKey, null) + + val memoList = if (memoJson.isNullOrEmpty()) { + mutableListOf() + } else { + gson.fromJson(memoJson, Array::class.java).toMutableList() + } + + memoList.add(memo) + + val updatedJson = gson.toJson(memoList) + + sharedPreferences.edit().putString(memoKey, updatedJson).apply() + } + + override suspend fun getMemos(): List { + val memoJson = sharedPreferences.getString(memoKey, null) + return if (memoJson.isNullOrEmpty()) { + emptyList() + } else { + gson.fromJson(memoJson, Array::class.java).toList() + } + } + + + + override suspend fun addMemo(memo: Memo): AddMemoResponse { + setMemo(memo) + + val request = AddMemoRequest(memo.title, memo.price, memo.time) + val response = apiService.addMemo(request) + + return if (response.isSuccessful) { + + response.body() ?: AddMemoResponse(result = 200) + } else { + + AddMemoResponse(result = response.code()) + } + } +} diff --git a/app/src/main/java/com/alom/androidstudy2/RetrofitClient.kt b/app/src/main/java/com/alom/androidstudy2/RetrofitClient.kt new file mode 100644 index 0000000..9c25284 --- /dev/null +++ b/app/src/main/java/com/alom/androidstudy2/RetrofitClient.kt @@ -0,0 +1,41 @@ +package com.alom.androidstudy2 +import okhttp3.Interceptor +import okhttp3.OkHttpClient +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory +import com.google.gson.GsonBuilder +import com.jakewharton.retrofit2.adapter.kotlin.coroutines.CoroutineCallAdapterFactory + + +object RetrofitClient { + val apiService: RetrofitInterface + get() = instance.create(RetrofitInterface::class.java) + + private val instance: Retrofit + get() { + val gson = GsonBuilder() + .setLenient() + .create() + + + val apiKeyInterceptor = Interceptor { chain -> + val originalRequest = chain.request() + val newRequest = originalRequest.newBuilder() + .addHeader("getApiKey", BuildConfig.API_KEY) + .build() + chain.proceed(newRequest) + } + + + val okHttpClient = OkHttpClient.Builder() + .addInterceptor(apiKeyInterceptor) + .build() + + return Retrofit.Builder() + .baseUrl("https://goaplrynweyxovekoezl.supabase.co/rest/v1/") + .client(okHttpClient) + .addConverterFactory(GsonConverterFactory.create(gson)) + .addCallAdapterFactory(CoroutineCallAdapterFactory()) + .build() + } +} diff --git a/app/src/main/java/com/alom/androidstudy2/RetrofitInterface.kt b/app/src/main/java/com/alom/androidstudy2/RetrofitInterface.kt new file mode 100644 index 0000000..3232951 --- /dev/null +++ b/app/src/main/java/com/alom/androidstudy2/RetrofitInterface.kt @@ -0,0 +1,18 @@ +package com.alom.androidstudy2 +import retrofit2.Response +import retrofit2.http.Body +import retrofit2.http.GET +import retrofit2.http.POST + +interface RetrofitInterface { + @POST("/rpc/add_item4") + suspend fun addMemo( + @Body request:AddMemoRequest + ): Response + + @GET("/rpc/get_item4") + suspend fun getMemo( + ):Response + + +} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_add_memo.xml b/app/src/main/res/layout/activity_add_memo.xml new file mode 100644 index 0000000..43e3c40 --- /dev/null +++ b/app/src/main/res/layout/activity_add_memo.xml @@ -0,0 +1,56 @@ + + + + + + + + + +