diff --git a/.gitignore b/.gitignore
index ac754dc..a8de673 100644
--- a/.gitignore
+++ b/.gitignore
@@ -29,4 +29,4 @@
.externalNativeBuild
.cxx
local.properties
-
+release
\ No newline at end of file
diff --git a/README.md b/README.md
index 85fc5d9..05197da 100644
--- a/README.md
+++ b/README.md
@@ -55,10 +55,10 @@ https://play.google.com/store/apps/details?id=dev.yjyoon.kwnotice
# Screenshots
-
-
-
-
+
+
+
+
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 4e26235..a11457f 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -8,14 +8,14 @@ plugins {
android {
namespace = "dev.yjyoon.kwnotice"
- compileSdk = 32
+ compileSdk = 33
defaultConfig {
applicationId = "dev.yjyoon.kwnotice"
minSdk = 24
- targetSdk = 32
- versionCode = 7
- versionName = "2.1.0"
+ targetSdk = 33
+ versionCode = 10
+ versionName = "2.2.1"
}
buildTypes {
@@ -46,9 +46,9 @@ dependencies {
implementation(project(":data"))
implementation(project(":presentation"))
- implementation(platform(libs.google.firebase.bom))
- implementation(libs.google.firebase.messaging)
- implementation(libs.google.firebase.analytics)
+ implementation(platform(libs.firebase.bom))
+ implementation(libs.firebase.messaging)
+ implementation(libs.firebase.analytics)
implementation(libs.hilt.android)
kapt(libs.hilt.compiler)
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index dcbfdea..52fb5f8 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,7 +1,9 @@
-
+
+
+
+
+ android:theme="@style/Theme.KWNotice">
+
= runCatching {
- suspendCoroutine { continuation ->
+ suspendCoroutine { continuation ->
FirebaseMessaging.getInstance().subscribeToTopic(topic.value)
.addOnCompleteListener {
Log.d("fcm", "Subscribed to ${topic.value} successfully")
@@ -26,7 +26,7 @@ internal class FcmSubscriptionImpl @Inject constructor() : FcmSubscription {
}
override suspend fun unsubscribeFrom(topic: FcmTopic): Result = runCatching {
- suspendCoroutine { continuation ->
+ suspendCoroutine { continuation ->
FirebaseMessaging.getInstance().unsubscribeFromTopic(topic.value)
.addOnCompleteListener {
Log.d("fcm", "Unsubscribed from ${topic.value} successfully")
diff --git a/build.gradle.kts b/build.gradle.kts
index 041af05..107cabb 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -6,7 +6,7 @@ buildscript {
dependencies {
classpath(libs.android.gradle)
- classpath(libs.kotlin.plugin)
+ classpath(libs.kotlin.gradle)
classpath(libs.google.services)
classpath(libs.hilt.gradle)
}
diff --git a/data/build.gradle.kts b/data/build.gradle.kts
index de2bc94..f041bc0 100644
--- a/data/build.gradle.kts
+++ b/data/build.gradle.kts
@@ -12,13 +12,13 @@ properties.load(project.rootProject.file("local.properties").inputStream())
android {
namespace = "dev.yjyoon.kwnotice.data"
- compileSdk = 32
+ compileSdk = 33
defaultConfig {
minSdk = 24
- targetSdk = 32
buildConfigField("String", "BASE_URL", properties["base_url"] as String)
+ buildConfigField("String", "KW_DORM_NOTICE_URL", properties["kw_dorm_notice_url"] as String)
}
compileOptions {
isCoreLibraryDesugaringEnabled = true
@@ -34,12 +34,12 @@ android {
dependencies {
implementation(project(":domain"))
- implementation(libs.androidx.room.runtime)
- implementation(libs.androidx.room.ktx)
- annotationProcessor(libs.androidx.room.compiler)
- kapt(libs.androidx.room.compiler)
+ implementation(libs.room.runtime)
+ implementation(libs.room.ktx)
+ annotationProcessor(libs.room.compiler)
+ kapt(libs.room.compiler)
- implementation(libs.androidx.datastore.preferences)
+ implementation(libs.datastore.preferences)
implementation(libs.retrofit.core)
implementation(libs.retrofit.converter.gson)
diff --git a/data/src/main/java/dev/yjyoon/kwnotice/data/di/DataModule.kt b/data/src/main/java/dev/yjyoon/kwnotice/data/di/DataModule.kt
index e105e9e..a79be10 100644
--- a/data/src/main/java/dev/yjyoon/kwnotice/data/di/DataModule.kt
+++ b/data/src/main/java/dev/yjyoon/kwnotice/data/di/DataModule.kt
@@ -29,6 +29,11 @@ internal object DataModule {
@Named("BaseUrl")
fun provideBaseUrl(): String = BuildConfig.BASE_URL
+ @Provides
+ @Singleton
+ @Named("KwDormNoticeUrl")
+ fun provideKwDormNoticeUrl(): String = BuildConfig.KW_DORM_NOTICE_URL
+
@Provides
@Singleton
@Named("Preferences")
diff --git a/data/src/main/java/dev/yjyoon/kwnotice/data/local/converter/FavoriteConverter.kt b/data/src/main/java/dev/yjyoon/kwnotice/data/local/converter/FavoriteConverter.kt
index cdee6f2..52dee9f 100644
--- a/data/src/main/java/dev/yjyoon/kwnotice/data/local/converter/FavoriteConverter.kt
+++ b/data/src/main/java/dev/yjyoon/kwnotice/data/local/converter/FavoriteConverter.kt
@@ -15,5 +15,5 @@ object FavoriteConverter {
fun dateToString(date: LocalDate): String = date.toString()
@TypeConverter
- fun stringToDate(string: String) = LocalDate.parse(string)
+ fun stringToDate(string: String): LocalDate = LocalDate.parse(string)
}
diff --git a/data/src/main/java/dev/yjyoon/kwnotice/data/local/dao/FavoriteDao.kt b/data/src/main/java/dev/yjyoon/kwnotice/data/local/dao/FavoriteDao.kt
index db624e2..86d7989 100644
--- a/data/src/main/java/dev/yjyoon/kwnotice/data/local/dao/FavoriteDao.kt
+++ b/data/src/main/java/dev/yjyoon/kwnotice/data/local/dao/FavoriteDao.kt
@@ -21,6 +21,9 @@ interface FavoriteDao {
@Query("SELECT id FROM favorite WHERE type = 'SwCentral'")
suspend fun getSwCentralIds(): List
+ @Query("SELECT id FROM favorite WHERE type = 'KwDorm'")
+ suspend fun getKwDormIds(): List
+
@Query("SELECT * FROM favorite")
fun getAll(): Flow>
}
diff --git a/data/src/main/java/dev/yjyoon/kwnotice/data/local/entity/FavoriteEntity.kt b/data/src/main/java/dev/yjyoon/kwnotice/data/local/entity/FavoriteEntity.kt
index 67e7e87..88a3c13 100644
--- a/data/src/main/java/dev/yjyoon/kwnotice/data/local/entity/FavoriteEntity.kt
+++ b/data/src/main/java/dev/yjyoon/kwnotice/data/local/entity/FavoriteEntity.kt
@@ -15,7 +15,7 @@ data class FavoriteEntity(
val date: LocalDate,
val url: String
) {
- enum class Type { KwHome, SwCentral }
+ enum class Type { KwHome, SwCentral, KwDorm, Unknown }
}
fun Favorite.toData() = FavoriteEntity(
@@ -24,6 +24,8 @@ fun Favorite.toData() = FavoriteEntity(
type = when (type) {
Favorite.Type.KwHome -> FavoriteEntity.Type.KwHome
Favorite.Type.SwCentral -> FavoriteEntity.Type.SwCentral
+ Favorite.Type.KwDorm -> FavoriteEntity.Type.KwDorm
+ else -> FavoriteEntity.Type.Unknown
},
date = date,
url = url
@@ -35,6 +37,8 @@ fun FavoriteEntity.toDomain() = Favorite(
type = when (type) {
FavoriteEntity.Type.KwHome -> Favorite.Type.KwHome
FavoriteEntity.Type.SwCentral -> Favorite.Type.SwCentral
+ FavoriteEntity.Type.KwDorm -> Favorite.Type.KwDorm
+ else -> Favorite.Type.Unknown
},
date = date,
url = url
diff --git a/data/src/main/java/dev/yjyoon/kwnotice/data/remote/api/NoticeService.kt b/data/src/main/java/dev/yjyoon/kwnotice/data/remote/api/NoticeService.kt
index b22899f..29d2024 100644
--- a/data/src/main/java/dev/yjyoon/kwnotice/data/remote/api/NoticeService.kt
+++ b/data/src/main/java/dev/yjyoon/kwnotice/data/remote/api/NoticeService.kt
@@ -1,8 +1,10 @@
package dev.yjyoon.kwnotice.data.remote.api
+import dev.yjyoon.kwnotice.data.remote.model.KwDormNoticeResponse
import dev.yjyoon.kwnotice.data.remote.model.KwHomeNoticeResponse
import dev.yjyoon.kwnotice.data.remote.model.SwCentralNoticeResponse
import retrofit2.http.GET
+import retrofit2.http.Url
internal interface NoticeService {
@@ -11,4 +13,7 @@ internal interface NoticeService {
@GET("sw-central")
suspend fun getSwCentralNotices(): List
+
+ @GET
+ suspend fun getKwDormNotices(@Url url: String): List
}
diff --git a/data/src/main/java/dev/yjyoon/kwnotice/data/remote/model/KwDormNoticeResponse.kt b/data/src/main/java/dev/yjyoon/kwnotice/data/remote/model/KwDormNoticeResponse.kt
new file mode 100644
index 0000000..6407c35
--- /dev/null
+++ b/data/src/main/java/dev/yjyoon/kwnotice/data/remote/model/KwDormNoticeResponse.kt
@@ -0,0 +1,23 @@
+package dev.yjyoon.kwnotice.data.remote.model
+
+import com.google.gson.annotations.SerializedName
+import dev.yjyoon.kwnotice.domain.model.Notice
+import java.time.LocalDate
+
+data class KwDormNoticeResponse(
+ @SerializedName("id")
+ val id: Long,
+ @SerializedName("title")
+ val title: String,
+ @SerializedName("url")
+ val url: String,
+ @SerializedName("createdAt")
+ val postedDate: String,
+)
+
+fun KwDormNoticeResponse.toDomain() = Notice.KwDorm(
+ id = id,
+ title = title,
+ url = url,
+ postedDate = LocalDate.parse(postedDate)
+)
diff --git a/data/src/main/java/dev/yjyoon/kwnotice/data/repository/FavoriteRepositoryImpl.kt b/data/src/main/java/dev/yjyoon/kwnotice/data/repository/FavoriteRepositoryImpl.kt
index f178b2c..2bb7107 100644
--- a/data/src/main/java/dev/yjyoon/kwnotice/data/repository/FavoriteRepositoryImpl.kt
+++ b/data/src/main/java/dev/yjyoon/kwnotice/data/repository/FavoriteRepositoryImpl.kt
@@ -29,6 +29,10 @@ internal class FavoriteRepositoryImpl @Inject constructor(
favoriteDao.getSwCentralIds()
}
+ override suspend fun getFavoriteKwDormIds(): Result> = runCatching {
+ favoriteDao.getKwDormIds()
+ }
+
override fun getAllFavoritesStream(): Flow> =
favoriteDao.getAll().map { favorites -> favorites.map { it.toDomain() } }
}
diff --git a/data/src/main/java/dev/yjyoon/kwnotice/data/repository/NoticeRepositoryImpl.kt b/data/src/main/java/dev/yjyoon/kwnotice/data/repository/NoticeRepositoryImpl.kt
index 2d98175..79b11d2 100644
--- a/data/src/main/java/dev/yjyoon/kwnotice/data/repository/NoticeRepositoryImpl.kt
+++ b/data/src/main/java/dev/yjyoon/kwnotice/data/repository/NoticeRepositoryImpl.kt
@@ -4,10 +4,13 @@ import dev.yjyoon.kwnotice.data.remote.api.NoticeService
import dev.yjyoon.kwnotice.data.remote.model.toDomain
import dev.yjyoon.kwnotice.domain.model.Notice
import dev.yjyoon.kwnotice.domain.repository.NoticeRepository
+import java.time.LocalDate
import javax.inject.Inject
+import javax.inject.Named
internal class NoticeRepositoryImpl @Inject constructor(
- private val noticeService: NoticeService
+ private val noticeService: NoticeService,
+ @Named("KwDormNoticeUrl") private val kwDormNoticeUrl: String
) : NoticeRepository {
override suspend fun getKwHomeNotices(): Result> = runCatching {
@@ -17,4 +20,15 @@ internal class NoticeRepositoryImpl @Inject constructor(
override suspend fun getSwCentralNotices(): Result> = runCatching {
noticeService.getSwCentralNotices().map { it.toDomain() }
}
+
+ override suspend fun getKwDormNotices(): Result> = runCatching {
+ noticeService.getKwDormNotices(kwDormNoticeUrl)
+ .filter {
+ LocalDate.parse(it.postedDate)
+ .plusMonths(4)
+ .isAfter(LocalDate.now())
+ }
+ .reversed()
+ .map { it.toDomain() }
+ }
}
diff --git a/domain/src/main/java/dev/yjyoon/kwnotice/domain/model/Favorite.kt b/domain/src/main/java/dev/yjyoon/kwnotice/domain/model/Favorite.kt
index 8a08a97..67d96dd 100644
--- a/domain/src/main/java/dev/yjyoon/kwnotice/domain/model/Favorite.kt
+++ b/domain/src/main/java/dev/yjyoon/kwnotice/domain/model/Favorite.kt
@@ -13,17 +13,20 @@ data class Favorite(
val text: String
) {
KwHome(text = KW_HOME),
- SwCentral(text = SW_CENTRAL)
+ SwCentral(text = SW_CENTRAL),
+ KwDorm(text = KW_DORM),
+ Unknown(text = UNKNOWN)
}
+}
- companion object {
-
- fun stringToType(string: String) = when (string) {
- KW_HOME -> Type.KwHome
- else -> Type.SwCentral
- }
-
- const val KW_HOME = "광운대학교"
- const val SW_CENTRAL = "SW중심대학사업단"
- }
+fun String.toFavoriteType(): Favorite.Type = when (this) {
+ KW_HOME -> Favorite.Type.KwHome
+ SW_CENTRAL -> Favorite.Type.SwCentral
+ KW_DORM -> Favorite.Type.KwDorm
+ else -> Favorite.Type.Unknown
}
+
+const val KW_HOME = "광운대학교"
+const val SW_CENTRAL = "SW중심대학사업단"
+const val KW_DORM = "빛솔재(기숙사)"
+const val UNKNOWN = "UNKNOWN"
diff --git a/domain/src/main/java/dev/yjyoon/kwnotice/domain/model/FcmTopic.kt b/domain/src/main/java/dev/yjyoon/kwnotice/domain/model/FcmTopic.kt
index 9d64402..0fe689b 100644
--- a/domain/src/main/java/dev/yjyoon/kwnotice/domain/model/FcmTopic.kt
+++ b/domain/src/main/java/dev/yjyoon/kwnotice/domain/model/FcmTopic.kt
@@ -3,9 +3,13 @@ package dev.yjyoon.kwnotice.domain.model
enum class FcmTopic(val value: String) {
KwHomeNew(value = TOPIC_KW_HOME_NEW),
KwHomeEdit(value = TOPIC_KW_HOME_EDIT),
- SwCentralNew(value = TOPIC_SW_CENTRAL_NEW)
+ SwCentralNew(value = TOPIC_SW_CENTRAL_NEW),
+ KwDormCommon(value = TOPIC_KW_DORM_COMMON),
+ KwDormRecruitment(value = TOPIC_KW_DORM_RECRUITMENT)
}
private const val TOPIC_KW_HOME_NEW = "kw-home-new"
private const val TOPIC_KW_HOME_EDIT = "kw-home-edit"
private const val TOPIC_SW_CENTRAL_NEW = "sw-central-new"
+private const val TOPIC_KW_DORM_COMMON = "kw-dorm-common"
+private const val TOPIC_KW_DORM_RECRUITMENT = "kw-dorm-recruitment"
diff --git a/domain/src/main/java/dev/yjyoon/kwnotice/domain/model/Notice.kt b/domain/src/main/java/dev/yjyoon/kwnotice/domain/model/Notice.kt
index e12bb3f..e70cfb5 100644
--- a/domain/src/main/java/dev/yjyoon/kwnotice/domain/model/Notice.kt
+++ b/domain/src/main/java/dev/yjyoon/kwnotice/domain/model/Notice.kt
@@ -25,6 +25,13 @@ sealed class Notice {
override val url: String,
override val postedDate: LocalDate,
) : Notice()
+
+ data class KwDorm(
+ override val id: Long,
+ override val title: String,
+ override val url: String,
+ override val postedDate: LocalDate,
+ ) : Notice()
}
fun Notice.toFavorite() = when (this) {
@@ -42,4 +49,11 @@ fun Notice.toFavorite() = when (this) {
date = postedDate,
type = Favorite.Type.SwCentral
)
+ is Notice.KwDorm -> Favorite(
+ id = id,
+ title = title,
+ url = url,
+ date = postedDate,
+ type = Favorite.Type.KwDorm
+ )
}
diff --git a/domain/src/main/java/dev/yjyoon/kwnotice/domain/repository/FavoriteRepository.kt b/domain/src/main/java/dev/yjyoon/kwnotice/domain/repository/FavoriteRepository.kt
index f410fd4..f46c6e8 100644
--- a/domain/src/main/java/dev/yjyoon/kwnotice/domain/repository/FavoriteRepository.kt
+++ b/domain/src/main/java/dev/yjyoon/kwnotice/domain/repository/FavoriteRepository.kt
@@ -13,5 +13,7 @@ interface FavoriteRepository {
suspend fun getFavoriteSwCentralIds(): Result>
+ suspend fun getFavoriteKwDormIds(): Result>
+
fun getAllFavoritesStream(): Flow>
}
diff --git a/domain/src/main/java/dev/yjyoon/kwnotice/domain/repository/NoticeRepository.kt b/domain/src/main/java/dev/yjyoon/kwnotice/domain/repository/NoticeRepository.kt
index 1db0081..dc52a51 100644
--- a/domain/src/main/java/dev/yjyoon/kwnotice/domain/repository/NoticeRepository.kt
+++ b/domain/src/main/java/dev/yjyoon/kwnotice/domain/repository/NoticeRepository.kt
@@ -7,4 +7,5 @@ interface NoticeRepository {
suspend fun getKwHomeNotices(): Result>
suspend fun getSwCentralNotices(): Result>
+ suspend fun getKwDormNotices(): Result>
}
diff --git a/domain/src/main/java/dev/yjyoon/kwnotice/domain/usecase/favorite/GetFavoriteKwIdListUseCase.kt b/domain/src/main/java/dev/yjyoon/kwnotice/domain/usecase/favorite/GetFavoriteKwIdListUseCase.kt
deleted file mode 100644
index f90ea66..0000000
--- a/domain/src/main/java/dev/yjyoon/kwnotice/domain/usecase/favorite/GetFavoriteKwIdListUseCase.kt
+++ /dev/null
@@ -1,12 +0,0 @@
-package dev.yjyoon.kwnotice.domain.usecase.favorite
-
-import dev.yjyoon.kwnotice.domain.repository.FavoriteRepository
-import javax.inject.Inject
-
-class GetFavoriteKwIdListUseCase @Inject constructor(
- private val favoriteRepository: FavoriteRepository
-) {
-
- suspend operator fun invoke(): Result> =
- favoriteRepository.getFavoriteKwHomeIds()
-}
diff --git a/domain/src/main/java/dev/yjyoon/kwnotice/domain/usecase/favorite/GetFavoriteSwIdListUseCase.kt b/domain/src/main/java/dev/yjyoon/kwnotice/domain/usecase/favorite/GetFavoriteSwIdListUseCase.kt
deleted file mode 100644
index 0285202..0000000
--- a/domain/src/main/java/dev/yjyoon/kwnotice/domain/usecase/favorite/GetFavoriteSwIdListUseCase.kt
+++ /dev/null
@@ -1,12 +0,0 @@
-package dev.yjyoon.kwnotice.domain.usecase.favorite
-
-import dev.yjyoon.kwnotice.domain.repository.FavoriteRepository
-import javax.inject.Inject
-
-class GetFavoriteSwIdListUseCase @Inject constructor(
- private val favoriteRepository: FavoriteRepository
-) {
-
- suspend operator fun invoke(): Result> =
- favoriteRepository.getFavoriteSwCentralIds()
-}
diff --git a/domain/src/main/java/dev/yjyoon/kwnotice/domain/usecase/notice/GetKwDormNoticeListUseCase.kt b/domain/src/main/java/dev/yjyoon/kwnotice/domain/usecase/notice/GetKwDormNoticeListUseCase.kt
new file mode 100644
index 0000000..2cbf108
--- /dev/null
+++ b/domain/src/main/java/dev/yjyoon/kwnotice/domain/usecase/notice/GetKwDormNoticeListUseCase.kt
@@ -0,0 +1,13 @@
+package dev.yjyoon.kwnotice.domain.usecase.notice
+
+import dev.yjyoon.kwnotice.domain.model.Notice
+import dev.yjyoon.kwnotice.domain.repository.NoticeRepository
+import javax.inject.Inject
+
+class GetKwDormNoticeListUseCase @Inject constructor(
+ private val noticeRepository: NoticeRepository
+) {
+
+ suspend operator fun invoke(): Result> =
+ noticeRepository.getKwDormNotices()
+}
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index c4f781c..8ae717a 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,69 +1,76 @@
[versions]
-kotlin = "1.6.21"
-coroutines = "1.6.3"
-
-android-gradle = "7.2.1"
-androidx-core = "1.8.0"
-androidx-lifecycle = "2.5.0"
-androidx-activity = "1.5.0"
-androidx-navigation = "2.4.2"
-
-compose = "1.2.0-beta03"
-material3 = "1.0.0-alpha14"
-dagger = "2.42"
-
-room = "2.4.2"
+kotlin = "1.7.20"
+coroutines = "1.6.4"
+
+android-gradle = "7.4.0"
+androidx-core = "1.9.0"
+androidx-lifecycle = "2.5.1"
+androidx-activity = "1.6.1"
+androidx-navigation = "2.5.3"
+
+compose = "1.3.3"
+compose-compiler = "1.3.2"
+material = "1.3.1"
+material3 = "1.0.1"
+dagger = "2.44"
+
+room = "2.5.0"
datastore = "1.0.0"
-google-services = "4.3.13"
-firebase-bom = "30.2.0"
-accompanist = "0.24.9-beta"
+gms = "4.3.15"
+firebase = "31.2.0"
+accompanist = "0.28.0"
okhttp = "4.9.3"
retrofit = "2.9.0"
gson = "2.9.0"
-desugar = "1.1.5"
+desugar = "1.2.2"
junit = "4.13.2"
[libraries]
-kotlin-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
+# Android
+kotlin-gradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" }
coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines" }
-android-gradle = { module = "com.android.tools.build:gradle", version.ref = "android.gradle" }
-androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "androidx-core" }
+android-gradle = { module = "com.android.tools.build:gradle", version.ref = "android-gradle" }
android-desugar = { module = "com.android.tools:desugar_jdk_libs", version.ref = "desugar" }
-androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activity" }
-androidx-lifecycle-runtime = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "androidx-lifecycle" }
-androidx-lifecycle-viewmodel = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "androidx-lifecycle" }
+androidx-core = { module = "androidx.core:core-ktx", version.ref = "androidx-core" }
+lifecycle-runtime = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "androidx-lifecycle" }
# Compose
-androidx-compose-ui-core = { module = "androidx.compose.ui:ui", version.ref = "compose" }
-androidx-compose-ui-tooling-core = { module = "androidx.compose.ui:ui-tooling", version.ref = "compose" }
-androidx-compose-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview", version.ref = "compose" }
-androidx-compose-material3 = { module = "androidx.compose.material3:material3", version.ref = "material3" }
-androidx-compose-navigation = { module = "androidx.navigation:navigation-compose", version.ref = "androidx-navigation" }
-androidx-hilt-navigation-compose = { module = "androidx.hilt:hilt-navigation-compose", version = "1.0.0" }
-
-google-services = { module = "com.google.gms:google-services", version.ref = "google-services" }
-google-firebase-bom = { module = "com.google.firebase:firebase-bom", version.ref = "firebase-bom" }
-google-firebase-messaging = { module = "com.google.firebase:firebase-messaging" }
-google-firebase-analytics = { module = "com.google.firebase:firebase-analytics-ktx" }
-
-google-accompanist-systemuicontroller = { module = "com.google.accompanist:accompanist-systemuicontroller", version.ref = "accompanist" }
-google-accompanist-navigation-animation = { module = "com.google.accompanist:accompanist-navigation-animation", version.ref = "accompanist" }
-google-accompanist-webview = { module = "com.google.accompanist:accompanist-webview", version.ref = "accompanist" }
-google-accompanist-pager = { module = "com.google.accompanist:accompanist-pager", version.ref = "accompanist" }
+compose-ui-core = { module = "androidx.compose.ui:ui", version.ref = "compose" }
+compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling", version.ref = "compose" }
+compose-ui-preview = { module = "androidx.compose.ui:ui-tooling-preview", version.ref = "compose" }
+compose-material = { module = "androidx.compose.material:material", version.ref = "material" }
+compose-material-icon = { module = "androidx.compose.material:material-icon-core", version.ref = "material" }
+compose-material3 = { module = "androidx.compose.material3:material3", version.ref = "material3" }
+
+activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activity" }
+navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "androidx-navigation" }
+lifecycle-viewmodel-compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "androidx-lifecycle" }
+hilt-navigation-compose = { module = "androidx.hilt:hilt-navigation-compose", version = "1.0.0" }
+
+# Firebase
+google-services = { module = "com.google.gms:google-services", version.ref = "gms" }
+firebase-bom = { module = "com.google.firebase:firebase-bom", version.ref = "firebase" }
+firebase-messaging = { module = "com.google.firebase:firebase-messaging" }
+firebase-analytics = { module = "com.google.firebase:firebase-analytics-ktx" }
+
+accompanist-systemuicontroller = { module = "com.google.accompanist:accompanist-systemuicontroller", version.ref = "accompanist" }
+accompanist-navigation-animation = { module = "com.google.accompanist:accompanist-navigation-animation", version.ref = "accompanist" }
+accompanist-webview = { module = "com.google.accompanist:accompanist-webview", version.ref = "accompanist" }
+accompanist-pager = { module = "com.google.accompanist:accompanist-pager", version.ref = "accompanist" }
# Room
-androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" }
-androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "room" }
-androidx-room-ktx = { module = "androidx.room:room-ktx", version.ref = "room" }
+room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" }
+room-compiler = { module = "androidx.room:room-compiler", version.ref = "room" }
+room-ktx = { module = "androidx.room:room-ktx", version.ref = "room" }
# Datastore
-androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastore" }
+datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastore" }
# Network
retrofit-core = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
@@ -79,15 +86,16 @@ hilt-core = { module = "com.google.dagger:hilt-core", version.ref = "dagger" }
hilt-gradle = { module = "com.google.dagger:hilt-android-gradle-plugin", version.ref = "dagger" }
hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "dagger" }
hilt-compiler = { module = "com.google.dagger:hilt-compiler", version.ref = "dagger" }
-
-
javax-inject = "javax.inject:javax.inject:1"
[bundles]
-compose = ["androidx-compose-ui-core",
- "androidx-compose-ui-tooling-core",
- "androidx-compose-ui-tooling-preview",
- "androidx-compose-material3",
- "androidx-activity-compose",
- "androidx-compose-navigation",
- "androidx-hilt-navigation-compose"]
\ No newline at end of file
+compose = [
+ "compose-ui-core",
+ "compose-ui-preview",
+ "compose-material",
+ "compose-material3",
+ "activity-compose",
+ "navigation-compose",
+ "lifecycle-viewmodel-compose",
+ "hilt-navigation-compose"
+]
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 8f6c285..51a71c2 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
#Fri Jul 01 20:05:26 KST 2022
distributionBase=GRADLE_USER_HOME
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
diff --git a/presentation/build.gradle.kts b/presentation/build.gradle.kts
index 3880917..112344b 100644
--- a/presentation/build.gradle.kts
+++ b/presentation/build.gradle.kts
@@ -7,11 +7,10 @@ plugins {
android {
namespace = "dev.yjyoon.kwnotice.presentation"
- compileSdk = 32
+ compileSdk = 33
defaultConfig {
minSdk = 24
- targetSdk = 32
vectorDrawables {
useSupportLibrary = true
@@ -21,7 +20,7 @@ android {
compose = true
}
composeOptions {
- kotlinCompilerExtensionVersion = libs.versions.compose.get()
+ kotlinCompilerExtensionVersion = libs.versions.compose.compiler.get()
}
compileOptions {
isCoreLibraryDesugaringEnabled = true
@@ -31,7 +30,8 @@ android {
}
kotlinOptions {
freeCompilerArgs += listOf(
- "-Xopt-in=kotlin.RequiresOptIn",
+ "-X opt-in=kotlin.RequiresOptIn",
+ "-opt-in=androidx.compose.material.ExperimentalMaterialApi",
"-opt-in=androidx.compose.material3.ExperimentalMaterial3Api",
"-opt-in=androidx.compose.animation.ExperimentalAnimationApi",
)
@@ -42,18 +42,20 @@ android {
dependencies {
implementation(project(":domain"))
- implementation(libs.androidx.core.ktx)
- implementation(libs.androidx.lifecycle.runtime)
+ implementation(libs.androidx.core)
+ implementation(libs.lifecycle.runtime)
implementation(libs.bundles.compose)
- implementation(libs.google.accompanist.webview)
- implementation(libs.google.accompanist.systemuicontroller)
- implementation(libs.google.accompanist.navigation.animation)
- implementation(libs.google.accompanist.pager)
+ implementation(libs.accompanist.webview)
+ implementation(libs.accompanist.systemuicontroller)
+ implementation(libs.accompanist.navigation.animation)
+ implementation(libs.accompanist.pager)
implementation(libs.hilt.android)
kapt(libs.hilt.compiler)
coreLibraryDesugaring(libs.android.desugar)
+
+ debugImplementation(libs.compose.ui.tooling)
}
\ No newline at end of file
diff --git a/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/component/KwNoticeDropdownMenu.kt b/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/component/KwNoticeDropdownMenu.kt
index 3bf7602..3c7d0b6 100644
--- a/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/component/KwNoticeDropdownMenu.kt
+++ b/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/component/KwNoticeDropdownMenu.kt
@@ -17,6 +17,7 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import dev.yjyoon.kwnotice.presentation.R
@@ -24,6 +25,7 @@ import dev.yjyoon.kwnotice.presentation.ui.theme.KwNoticeTheme
@Composable
fun KwNoticeDropdownMenu(
+ modifier: Modifier = Modifier,
@DrawableRes leadingIconRes: Int,
initialItem: String,
items: List,
@@ -33,7 +35,7 @@ fun KwNoticeDropdownMenu(
var selectedItem by remember { mutableStateOf(initialItem) }
Box(
- Modifier.wrapContentSize()
+ modifier.wrapContentSize()
) {
FilterChip(
selected = expanded,
@@ -41,7 +43,9 @@ fun KwNoticeDropdownMenu(
label = {
Text(
text = selectedItem,
- style = MaterialTheme.typography.bodySmall
+ style = MaterialTheme.typography.bodySmall,
+ overflow = TextOverflow.Ellipsis,
+ maxLines = 1
)
},
leadingIcon = {
@@ -50,40 +54,39 @@ fun KwNoticeDropdownMenu(
contentDescription = null,
modifier = Modifier.size(12.dp)
)
- },
- selectedIcon = {
- Icon(
- painter = painterResource(id = leadingIconRes),
- contentDescription = null,
- modifier = Modifier.size(12.dp)
- )
}
)
DropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false }
) {
- DropdownMenuItem(text = {
- Text(
- text = initialItem,
- style = MaterialTheme.typography.bodySmall
- )
- }, onClick = {
- selectedItem = initialItem
- expanded = false
- onSelectItem(null)
- })
- items.forEach {
- DropdownMenuItem(text = {
+ DropdownMenuItem(
+ text = {
Text(
- text = it,
+ text = initialItem,
style = MaterialTheme.typography.bodySmall
)
- }, onClick = {
- selectedItem = it
+ },
+ onClick = {
+ selectedItem = initialItem
expanded = false
- onSelectItem(it)
- })
+ onSelectItem(null)
+ }
+ )
+ items.forEach {
+ DropdownMenuItem(
+ text = {
+ Text(
+ text = it,
+ style = MaterialTheme.typography.bodySmall
+ )
+ },
+ onClick = {
+ selectedItem = it
+ expanded = false
+ onSelectItem(it)
+ }
+ )
}
}
}
diff --git a/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/component/KwNoticeSearchBar.kt b/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/component/KwNoticeSearchBar.kt
index 6ee59aa..a8d6444 100644
--- a/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/component/KwNoticeSearchBar.kt
+++ b/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/component/KwNoticeSearchBar.kt
@@ -2,30 +2,33 @@ package dev.yjyoon.kwnotice.presentation.ui.component
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.Search
import androidx.compose.material3.Icon
-import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
import androidx.compose.material3.Text
-import androidx.compose.material3.TextField
-import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction
@@ -33,6 +36,7 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import dev.yjyoon.kwnotice.presentation.R
import dev.yjyoon.kwnotice.presentation.ui.theme.KwNoticeTheme
+import dev.yjyoon.kwnotice.presentation.ui.theme.KwNoticeTypography
@OptIn(ExperimentalComposeUiApi::class)
@Composable
@@ -47,51 +51,46 @@ fun KwNoticeSearchBar(
val keyboardController = LocalSoftwareKeyboardController.current
val focusRequester = remember { FocusRequester() }
- Box {
- TextField(
- value = text,
- onValueChange = { text = it },
- modifier = modifier
- .padding(horizontal = 18.dp, vertical = 6.dp)
- .focusRequester(focusRequester),
- textStyle = MaterialTheme.typography.bodyMedium,
- placeholder = {
- Text(
- stringResource(id = R.string.searchbar_placeholder),
- color = color.copy(alpha = 0.15f),
- style = MaterialTheme.typography.bodyMedium,
- maxLines = 1
- )
- },
- leadingIcon = { Icon(imageVector = Icons.Default.Search, contentDescription = null) },
- trailingIcon = {
- IconButton(onClick = {
- keyboardController?.hide()
- onClose()
- }) {
- Icon(
- imageVector = Icons.Default.Close,
- contentDescription = null
- )
+ BasicTextField(
+ value = text,
+ onValueChange = { text = it },
+ modifier = Modifier.focusRequester(focusRequester),
+ textStyle = KwNoticeTypography.bodyMedium,
+ keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
+ keyboardActions = KeyboardActions(
+ onSearch = {
+ onSearch(text)
+ keyboardController?.hide()
+ }
+ ),
+ singleLine = true,
+ cursorBrush = SolidColor(MaterialTheme.colorScheme.primary),
+ decorationBox = { innerTextField ->
+ Surface(
+ shape = RoundedCornerShape(12.dp),
+ color = color.copy(alpha = 0.05f)
+ ) {
+ Row(
+ modifier = modifier.padding(horizontal = 12.dp, vertical = 8.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Icon(imageVector = Icons.Default.Search, contentDescription = null)
+ Spacer(modifier = Modifier.width(8.dp))
+ Box(modifier = Modifier.weight(1f)) {
+ if(text.isEmpty()) {
+ Text(
+ stringResource(id = R.string.searchbar_placeholder),
+ color = color.copy(alpha = 0.15f),
+ style = KwNoticeTypography.bodyMedium,
+ maxLines = 1
+ )
+ }
+ innerTextField()
+ }
}
- },
- keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
- keyboardActions = KeyboardActions(
- onSearch = {
- onSearch(text)
- keyboardController?.hide()
- }
- ),
- singleLine = true,
- shape = RoundedCornerShape(12.dp),
- colors = TextFieldDefaults.textFieldColors(
- containerColor = color.copy(alpha = 0.05f),
- focusedIndicatorColor = Color.Transparent,
- unfocusedIndicatorColor = Color.Transparent,
- disabledIndicatorColor = Color.Transparent
- )
- )
- }
+ }
+ }
+ )
LaunchedEffect(Unit) {
focusRequester.requestFocus()
diff --git a/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/component/KwNoticeSwitchBar.kt b/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/component/KwNoticeSwitchBar.kt
index 1a5adfc..39502db 100644
--- a/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/component/KwNoticeSwitchBar.kt
+++ b/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/component/KwNoticeSwitchBar.kt
@@ -27,19 +27,6 @@ fun KwNoticeSwitchBar(
onTap: (Boolean) -> Unit,
modifier: Modifier = Modifier
) {
- val icon: (@Composable () -> Unit)? = if (checked) {
- {
- Icon(
- imageVector = Icons.Rounded.Check,
- contentDescription = null,
- tint = MaterialTheme.colorScheme.primary,
- modifier = Modifier.size(SwitchDefaults.IconSize),
- )
- }
- } else {
- null
- }
-
Row(
modifier.then(Modifier
.height(56.dp)
@@ -51,8 +38,7 @@ fun KwNoticeSwitchBar(
Text(title)
Switch(
checked = checked,
- onCheckedChange = { onTap(it) },
- thumbContent = icon
+ onCheckedChange = { onTap(it) }
)
}
}
@@ -67,4 +53,4 @@ private fun KwNoticeSwitchBarPreview() {
onTap = { }
)
}
-}
\ No newline at end of file
+}
diff --git a/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/component/KwNoticeTopAppBar.kt b/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/component/KwNoticeTopAppBar.kt
index 302d1c1..1c53520 100644
--- a/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/component/KwNoticeTopAppBar.kt
+++ b/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/component/KwNoticeTopAppBar.kt
@@ -7,35 +7,71 @@ import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.animation.with
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.Search
-import androidx.compose.material.icons.outlined.Search
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
-import androidx.compose.material3.SmallTopAppBar
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.tooling.preview.Preview
-import dev.yjyoon.kwnotice.presentation.R
-import dev.yjyoon.kwnotice.presentation.ui.theme.KwNoticeTheme
+import androidx.compose.ui.unit.dp
import dev.yjyoon.kwnotice.presentation.ui.theme.KwNoticeTypography
+
@Composable
fun KwNoticeTopAppBar(
+ title: @Composable() (RowScope.() -> Unit),
+ actions: @Composable() (RowScope.() -> Unit)
+) {
+ Column {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(top = 8.dp, bottom = 0.dp, start = 4.dp, end = 4.dp),
+ horizontalArrangement = Arrangement.End,
+ content = actions
+ )
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = 16.dp, vertical = 0.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.Start,
+ content = title
+ )
+ }
+}
+
+@Composable
+fun KwNoticeSimpleTopAppBar(
titleText: String,
actionIcon: ImageVector,
onActionClick: () -> Unit
) {
- SmallTopAppBar(
- title = { Text(text = titleText, style = KwNoticeTypography.titleLarge) },
+ KwNoticeTopAppBar(
+ title = {
+ Text(
+ modifier = Modifier.padding(vertical = 8.dp),
+ text = titleText,
+ style = KwNoticeTypography.titleLarge,
+ maxLines = 1
+ )
+ },
actions = {
IconButton(onClick = onActionClick) {
Icon(imageVector = actionIcon, contentDescription = null)
@@ -48,19 +84,19 @@ fun KwNoticeTopAppBar(
fun KwNoticeSearchTopAppBar(
titleText: String,
onSearch: (String) -> Unit,
- onCloseSearh: () -> Unit
+ onCloseSearch: () -> Unit
) {
var showSearchBar by remember { mutableStateOf(false) }
- SmallTopAppBar(
+ KwNoticeTopAppBar(
title = {
Text(
+ modifier = Modifier.padding(vertical = 8.dp),
text = titleText,
style = KwNoticeTypography.titleLarge,
maxLines = 1
)
- },
- actions = {
+ Spacer(modifier = Modifier.width(16.dp))
AnimatedContent(
targetState = showSearchBar,
transitionSpec = {
@@ -78,30 +114,22 @@ fun KwNoticeSearchTopAppBar(
Modifier.fillMaxWidth(),
onSearch = onSearch,
onClose = {
- onCloseSearh()
+ onCloseSearch()
showSearchBar = false
}
)
- } else {
- IconButton(onClick = {
- showSearchBar = true
- }) {
- Icon(imageVector = Icons.Default.Search, contentDescription = null)
- }
}
}
+ },
+ actions = {
+ IconButton(
+ onClick = { showSearchBar = !showSearchBar }
+ ) {
+ Icon(
+ imageVector = if (showSearchBar) Icons.Default.Close else Icons.Default.Search,
+ contentDescription = null
+ )
+ }
}
)
}
-
-@Preview
-@Composable
-private fun KwNoticeTopAppBarPreview() {
- KwNoticeTheme {
- KwNoticeTopAppBar(
- titleText = stringResource(id = R.string.navigation_notice),
- actionIcon = Icons.Outlined.Search,
- onActionClick = {}
- )
- }
-}
\ No newline at end of file
diff --git a/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/favorite/FavoriteCard.kt b/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/favorite/FavoriteCard.kt
index 5bcb1a1..0d69d16 100644
--- a/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/favorite/FavoriteCard.kt
+++ b/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/favorite/FavoriteCard.kt
@@ -26,6 +26,7 @@ import dev.yjyoon.kwnotice.domain.model.Favorite
import dev.yjyoon.kwnotice.presentation.R
import dev.yjyoon.kwnotice.presentation.ui.component.KwNoticeBadge
import dev.yjyoon.kwnotice.presentation.ui.component.KwNoticeRoundRect
+import dev.yjyoon.kwnotice.presentation.ui.util.DateDisplayUtil.toRelativeDateString
import java.time.LocalDate
import java.time.format.DateTimeFormatter
@@ -95,7 +96,7 @@ fun NoticeTitle(title: String) {
fun NoticeDateBadge(date: LocalDate) {
KwNoticeBadge(
leadingIconRes = R.drawable.ic_calendar,
- label = date.format(DateTimeFormatter.ofPattern(stringResource(id = R.string.notice_date_format)))
+ label = date.toRelativeDateString()
)
}
@@ -104,6 +105,8 @@ fun NoticeTypeBadge(type: Favorite.Type) {
@StringRes val typeStringRes = when (type) {
Favorite.Type.KwHome -> R.string.kw_home
Favorite.Type.SwCentral -> R.string.sw_central
+ Favorite.Type.KwDorm -> R.string.kw_dorm
+ else -> R.string.unknown
}
KwNoticeBadge(leadingIconRes = R.drawable.ic_tag, label = stringResource(id = typeStringRes))
diff --git a/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/favorite/FavoriteScreen.kt b/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/favorite/FavoriteScreen.kt
index b2db156..623a8e9 100644
--- a/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/favorite/FavoriteScreen.kt
+++ b/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/favorite/FavoriteScreen.kt
@@ -30,6 +30,7 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
@@ -76,6 +77,7 @@ fun FavoriteScreen(
onInitFilter: () -> Unit,
addToFavorite: (Favorite) -> Unit
) {
+ val context = LocalContext.current
val scope = rememberCoroutineScope()
var job: Job? by remember { mutableStateOf(null) }
val snackbarHostState = remember { SnackbarHostState() }
@@ -84,8 +86,8 @@ fun FavoriteScreen(
job?.cancel()
job = scope.launch {
val snackbarResult = snackbarHostState.showSnackbar(
- message = "즐겨찾기에서 삭제했습니다",
- actionLabel = "되돌리기"
+ message = context.getString(R.string.unbookmarked),
+ actionLabel = context.getString(R.string.undo)
)
when (snackbarResult) {
SnackbarResult.ActionPerformed -> {
@@ -111,7 +113,7 @@ fun FavoriteScreen(
KwNoticeSearchTopAppBar(
titleText = stringResource(id = R.string.navigation_favorite),
onSearch = onSearch,
- onCloseSearh = onInitFilter
+ onCloseSearch = onInitFilter
)
Box(
Modifier.weight(1f)
diff --git a/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/favorite/FavoriteViewModel.kt b/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/favorite/FavoriteViewModel.kt
index ffec77e..5b060a6 100644
--- a/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/favorite/FavoriteViewModel.kt
+++ b/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/favorite/FavoriteViewModel.kt
@@ -3,6 +3,7 @@ package dev.yjyoon.kwnotice.presentation.ui.favorite
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import dev.yjyoon.kwnotice.domain.model.Favorite
+import dev.yjyoon.kwnotice.domain.model.toFavoriteType
import dev.yjyoon.kwnotice.domain.usecase.favorite.AddFavoriteUseCase
import dev.yjyoon.kwnotice.domain.usecase.favorite.DeleteFavoriteUseCase
import dev.yjyoon.kwnotice.domain.usecase.favorite.GetAllFavoriteListUseCase
@@ -63,7 +64,7 @@ class FavoriteViewModel @Inject constructor(
fun setTypeFilter(type: String?) {
_filterState.update {
- it.copy(type = type?.let { type -> Favorite.Companion.stringToType(type) })
+ it.copy(type = type?.toFavoriteType())
}
}
diff --git a/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/model/FcmTopicModel.kt b/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/model/FcmTopicModel.kt
index 6a6969a..d63713b 100644
--- a/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/model/FcmTopicModel.kt
+++ b/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/model/FcmTopicModel.kt
@@ -10,5 +10,7 @@ data class FcmTopicModel(val topic: FcmTopic) {
FcmTopic.KwHomeNew -> R.string.settings_notification_new
FcmTopic.KwHomeEdit -> R.string.settings_notification_edit
FcmTopic.SwCentralNew -> R.string.settings_notification_new
+ FcmTopic.KwDormCommon -> R.string.settings_notification_common
+ FcmTopic.KwDormRecruitment -> R.string.settings_notification_recruit
}
}
diff --git a/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/notice/FailureScreen.kt b/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/notice/FailureScreen.kt
index d4bc633..e9dc6f0 100644
--- a/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/notice/FailureScreen.kt
+++ b/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/notice/FailureScreen.kt
@@ -1,8 +1,15 @@
package dev.yjyoon.kwnotice.presentation.ui.notice
+import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Refresh
+import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
@@ -18,21 +25,35 @@ import dev.yjyoon.kwnotice.presentation.R
import dev.yjyoon.kwnotice.presentation.ui.theme.KwNoticeTheme
@Composable
-fun FailureScreen() {
- Column(
- horizontalAlignment = Alignment.CenterHorizontally
+fun FailureScreen(
+ onRefresh: () -> Unit
+) {
+ Box(
+ Modifier.fillMaxSize().padding(24.dp)
) {
- Icon(
- painter = painterResource(id = R.drawable.ic_wifi_off),
- contentDescription = null,
- tint = MaterialTheme.colorScheme.onSurfaceVariant
- )
- Spacer(Modifier.height(12.dp))
- Text(
- text = stringResource(id = R.string.notice_network_fail),
- color = MaterialTheme.colorScheme.onSurfaceVariant,
- textAlign = TextAlign.Center
- )
+ Column(
+ Modifier.align(Alignment.Center),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_wifi_off),
+ contentDescription = null,
+ tint = MaterialTheme.colorScheme.onSurfaceVariant,
+ modifier = Modifier.size(32.dp)
+ )
+ Spacer(Modifier.height(12.dp))
+ Text(
+ text = stringResource(id = R.string.notice_network_fail),
+ color = MaterialTheme.colorScheme.onSurfaceVariant,
+ textAlign = TextAlign.Center
+ )
+ }
+ FloatingActionButton(
+ onClick = onRefresh,
+ modifier = Modifier.align(Alignment.BottomEnd)
+ ) {
+ Icon(imageVector = Icons.Default.Refresh, contentDescription = null)
+ }
}
}
@@ -40,6 +61,6 @@ fun FailureScreen() {
@Composable
private fun FailureScreenPreview() {
KwNoticeTheme {
- FailureScreen()
+ FailureScreen(onRefresh = {})
}
}
diff --git a/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/notice/KwDormContent.kt b/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/notice/KwDormContent.kt
new file mode 100644
index 0000000..27cc0e6
--- /dev/null
+++ b/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/notice/KwDormContent.kt
@@ -0,0 +1,145 @@
+package dev.yjyoon.kwnotice.presentation.ui.notice
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.material.ExperimentalMaterialApi
+import androidx.compose.material.pullrefresh.PullRefreshIndicator
+import androidx.compose.material.pullrefresh.pullRefresh
+import androidx.compose.material.pullrefresh.rememberPullRefreshState
+import androidx.compose.material3.CircularProgressIndicator
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import dev.yjyoon.kwnotice.domain.model.Favorite
+import dev.yjyoon.kwnotice.domain.model.Notice
+import dev.yjyoon.kwnotice.domain.model.toFavorite
+import dev.yjyoon.kwnotice.presentation.R
+import dev.yjyoon.kwnotice.presentation.ui.component.KwNoticeDropdownMenu
+
+@Composable
+fun KwDormContent(
+ uiState: KwDormNoticeUiState,
+ filterState: NoticeFilterState,
+ favoriteNotices: List,
+ refreshing: Boolean,
+ onClickNotice: (String) -> Unit,
+ onAddToFavorite: (Notice) -> Unit,
+ onDeleteFromFavorite: (Notice) -> Unit,
+ onMonthFilterChange: (String?) -> Unit,
+ onRefresh: () -> Unit
+) {
+ when (uiState) {
+ is KwDormNoticeUiState.Success -> {
+ Column(Modifier.fillMaxSize()) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = 18.dp, vertical = 8.dp),
+ horizontalArrangement = Arrangement.End
+ ) {
+ KwNoticeDropdownMenu(
+ leadingIconRes = R.drawable.ic_tag,
+ initialItem = stringResource(id = R.string.filter_tag_all),
+ items = listOf(),
+ onSelectItem = { }
+ )
+ Spacer(Modifier.width(8.dp))
+ KwNoticeDropdownMenu(
+ leadingIconRes = R.drawable.ic_group,
+ initialItem = stringResource(id = R.string.filter_department_all),
+ items = listOf(),
+ onSelectItem = { }
+ )
+ Spacer(Modifier.weight(1f))
+ KwNoticeDropdownMenu(
+ leadingIconRes = R.drawable.ic_calendar,
+ initialItem = stringResource(id = R.string.filter_month_all),
+ items = uiState.months.map { "$it${stringResource(id = R.string.month)}" },
+ onSelectItem = onMonthFilterChange
+ )
+ }
+ KwDormNoticeColumn(
+ uiState = uiState,
+ filterState = filterState,
+ favoriteNotices = favoriteNotices,
+ refreshing = refreshing,
+ onClickNotice = onClickNotice,
+ onAddToFavorite = onAddToFavorite,
+ onDeleteFromFavorite = onDeleteFromFavorite,
+ onRefresh = onRefresh
+ )
+ }
+ }
+ KwDormNoticeUiState.Loading -> {
+ CircularProgressIndicator()
+ }
+ KwDormNoticeUiState.Failure -> {
+ FailureScreen(onRefresh = onRefresh)
+ }
+ }
+}
+
+@OptIn(ExperimentalMaterialApi::class)
+@Composable
+fun KwDormNoticeColumn(
+ uiState: KwDormNoticeUiState.Success,
+ filterState: NoticeFilterState,
+ favoriteNotices: List,
+ refreshing: Boolean,
+ onClickNotice: (String) -> Unit,
+ onAddToFavorite: (Notice) -> Unit,
+ onDeleteFromFavorite: (Notice) -> Unit,
+ onRefresh: () -> Unit
+) {
+ val pullRefreshState = rememberPullRefreshState(
+ refreshing = refreshing,
+ onRefresh = onRefresh
+ )
+
+ Box(
+ modifier = Modifier.pullRefresh(pullRefreshState)
+ ) {
+ LazyColumn(
+ Modifier.fillMaxSize(),
+ contentPadding = PaddingValues(horizontal = 18.dp, vertical = 0.dp),
+ verticalArrangement = Arrangement.spacedBy(18.dp, Alignment.Top)
+ ) {
+ items(uiState.notices.filter { filterState.filtering(it) }) {
+ NoticeCard(
+ notice = it,
+ onClickNotice = onClickNotice,
+ bookmarked = favoriteNotices.contains(it.toFavorite()),
+ onToggleBookmark = { notice, bookmarked ->
+ if (bookmarked) {
+ onAddToFavorite(notice)
+ } else {
+ onDeleteFromFavorite(notice)
+ }
+ }
+ )
+ }
+ item { Spacer(modifier = Modifier.height(4.dp)) }
+ }
+ PullRefreshIndicator(
+ modifier = Modifier.align(Alignment.TopCenter),
+ refreshing = refreshing,
+ state = pullRefreshState,
+ contentColor = MaterialTheme.colorScheme.primary,
+ scale = true
+ )
+ }
+}
\ No newline at end of file
diff --git a/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/notice/KwHomeContent.kt b/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/notice/KwHomeContent.kt
index 16f8d7b..7413140 100644
--- a/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/notice/KwHomeContent.kt
+++ b/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/notice/KwHomeContent.kt
@@ -1,16 +1,23 @@
package dev.yjyoon.kwnotice.presentation.ui.notice
import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
+import androidx.compose.material.ExperimentalMaterialApi
+import androidx.compose.material.pullrefresh.PullRefreshIndicator
+import androidx.compose.material.pullrefresh.pullRefresh
+import androidx.compose.material.pullrefresh.rememberPullRefreshState
import androidx.compose.material3.CircularProgressIndicator
+import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -27,12 +34,14 @@ fun KwHomeContent(
uiState: KwHomeNoticeUiState,
filterState: NoticeFilterState,
favoriteNotices: List,
+ refreshing: Boolean,
onClickNotice: (String) -> Unit,
onAddToFavorite: (Notice) -> Unit,
onDeleteFromFavorite: (Notice) -> Unit,
onTagFilterChange: (String?) -> Unit,
onDepartmentFilterChange: (String?) -> Unit,
- onMonthFilterChange: (String?) -> Unit
+ onMonthFilterChange: (String?) -> Unit,
+ onRefresh: () -> Unit
) {
when (uiState) {
is KwHomeNoticeUiState.Success -> {
@@ -48,6 +57,7 @@ fun KwHomeContent(
)
Spacer(Modifier.width(8.dp))
KwNoticeDropdownMenu(
+ modifier = Modifier.padding(end = 8.dp),
leadingIconRes = R.drawable.ic_group,
initialItem = stringResource(id = R.string.filter_department_all),
items = uiState.departments,
@@ -64,10 +74,12 @@ fun KwHomeContent(
KwHomeNoticeColumn(
uiState = uiState,
filterState = filterState,
+ favoriteNotices = favoriteNotices,
+ refreshing = refreshing,
onClickNotice = onClickNotice,
onAddToFavorite = onAddToFavorite,
onDeleteFromFavorite = onDeleteFromFavorite,
- favoriteNotices = favoriteNotices
+ onRefresh = onRefresh
)
}
@@ -76,36 +88,57 @@ fun KwHomeContent(
CircularProgressIndicator()
}
KwHomeNoticeUiState.Failure -> {
- FailureScreen()
+ FailureScreen(onRefresh = onRefresh)
}
}
}
+@OptIn(ExperimentalMaterialApi::class)
@Composable
fun KwHomeNoticeColumn(
uiState: KwHomeNoticeUiState.Success,
filterState: NoticeFilterState,
favoriteNotices: List,
+ refreshing: Boolean,
onClickNotice: (String) -> Unit,
onAddToFavorite: (Notice) -> Unit,
- onDeleteFromFavorite: (Notice) -> Unit
+ onDeleteFromFavorite: (Notice) -> Unit,
+ onRefresh: () -> Unit
) {
- LazyColumn(
- contentPadding = PaddingValues(horizontal = 18.dp, vertical = 0.dp),
- verticalArrangement = Arrangement.spacedBy(18.dp, Alignment.Top),
+ val pullRefreshState = rememberPullRefreshState(
+ refreshing = refreshing,
+ onRefresh = onRefresh
+ )
+
+ Box(
+ modifier = Modifier.pullRefresh(pullRefreshState)
) {
- items(uiState.notices.filter { filterState.filtering(it) }) {
- NoticeCard(
- notice = it,
- onClickNotice = onClickNotice,
- bookmarked = favoriteNotices.contains(it.toFavorite()),
- onToggleBookmark = { notice, bookmarked ->
- if (bookmarked) {
- onAddToFavorite(notice)
- } else {
- onDeleteFromFavorite(notice)
+ LazyColumn(
+ contentPadding = PaddingValues(horizontal = 18.dp, vertical = 0.dp),
+ verticalArrangement = Arrangement.spacedBy(18.dp, Alignment.Top),
+ ) {
+ items(uiState.notices.filter { filterState.filtering(it) }) {
+ NoticeCard(
+ notice = it,
+ onClickNotice = onClickNotice,
+ bookmarked = favoriteNotices.contains(it.toFavorite()),
+ onToggleBookmark = { notice, bookmarked ->
+ if (bookmarked) {
+ onAddToFavorite(notice)
+ } else {
+ onDeleteFromFavorite(notice)
+ }
}
- })
+ )
+ }
+ item { Spacer(modifier = Modifier.height(4.dp)) }
}
+ PullRefreshIndicator(
+ modifier = Modifier.align(Alignment.TopCenter),
+ refreshing = refreshing,
+ state = pullRefreshState,
+ contentColor = MaterialTheme.colorScheme.primary,
+ scale = true
+ )
}
}
diff --git a/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/notice/NoticeCard.kt b/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/notice/NoticeCard.kt
index 3d7fbb0..9fc8fa3 100644
--- a/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/notice/NoticeCard.kt
+++ b/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/notice/NoticeCard.kt
@@ -17,7 +17,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
-import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
@@ -27,8 +26,8 @@ import dev.yjyoon.kwnotice.presentation.R
import dev.yjyoon.kwnotice.presentation.ui.component.KwNoticeBadge
import dev.yjyoon.kwnotice.presentation.ui.component.KwNoticeRoundRect
import dev.yjyoon.kwnotice.presentation.ui.theme.KwNoticeTheme
+import dev.yjyoon.kwnotice.presentation.ui.util.DateDisplayUtil.toRelativeDateString
import java.time.LocalDate
-import java.time.format.DateTimeFormatter
@Composable
fun NoticeCard(
@@ -89,7 +88,7 @@ fun NoticeTitle(notice: Notice) {
is Notice.KwHome -> {
notice.title.substring(notice.tag.length + 3)
}
- is Notice.SwCentral -> {
+ else -> {
notice.title
}
}
@@ -109,14 +108,14 @@ fun NoticeDateBadge(notice: Notice) {
is Notice.KwHome -> {
notice.modifiedDate
}
- is Notice.SwCentral -> {
+ else -> {
notice.postedDate
}
}
KwNoticeBadge(
leadingIconRes = R.drawable.ic_calendar,
- label = date.format(DateTimeFormatter.ofPattern(stringResource(id = R.string.notice_date_format)))
+ label = date.toRelativeDateString()
)
}
diff --git a/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/notice/NoticeFilterState.kt b/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/notice/NoticeFilterState.kt
index 069fa65..a409114 100644
--- a/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/notice/NoticeFilterState.kt
+++ b/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/notice/NoticeFilterState.kt
@@ -16,7 +16,7 @@ data class NoticeFilterState(
.and(department == null || department == notice.department)
.and(month == null || month.dropLast(1).toInt() == notice.modifiedDate.monthValue)
}
- is Notice.SwCentral -> {
+ else -> {
(title == null || title in notice.title)
.and(month == null || month.dropLast(1).toInt() == notice.postedDate.monthValue)
}
diff --git a/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/notice/NoticeScreen.kt b/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/notice/NoticeScreen.kt
index 8eb2e17..53b2323 100644
--- a/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/notice/NoticeScreen.kt
+++ b/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/notice/NoticeScreen.kt
@@ -40,6 +40,7 @@ fun NoticeScreen(
uiState = uiState,
filterState = filterState,
favoriteNotices = uiState.favoriteNotices,
+ refreshing = viewModel.refreshing,
onClickNotice = onClickNotice,
onAddToFavorite = viewModel::addFavorite,
onDeleteFromFavorite = viewModel::deleteFavorite,
@@ -47,7 +48,8 @@ fun NoticeScreen(
onTagFilterChange = viewModel::setTagFilter,
onDepartmentFilterChange = viewModel::setDepartmentFilter,
onMonthFilterChange = viewModel::setMonthFilter,
- onInitFilter = viewModel::initFilter
+ onInitFilter = viewModel::initFilter,
+ onRefresh = viewModel::refresh
)
}
@@ -56,6 +58,7 @@ fun NoticeScreen(
fun NoticeScreen(
uiState: NoticeUiState,
filterState: NoticeFilterState,
+ refreshing: Boolean,
favoriteNotices: List,
onClickNotice: (String) -> Unit,
onAddToFavorite: (Notice) -> Unit,
@@ -64,7 +67,8 @@ fun NoticeScreen(
onTagFilterChange: (String?) -> Unit,
onDepartmentFilterChange: (String?) -> Unit,
onMonthFilterChange: (String?) -> Unit,
- onInitFilter: () -> Unit
+ onInitFilter: () -> Unit,
+ onRefresh: (NoticeTab) -> Unit,
) {
val pagerState = rememberPagerState()
val coroutineScope = rememberCoroutineScope()
@@ -78,7 +82,7 @@ fun NoticeScreen(
KwNoticeSearchTopAppBar(
titleText = stringResource(id = R.string.navigation_notice),
onSearch = onSearch,
- onCloseSearh = onInitFilter
+ onCloseSearch = onInitFilter
)
TabRow(
selectedTabIndex = pagerState.currentPage,
@@ -111,7 +115,9 @@ fun NoticeScreen(
filterState = filterState,
onTagFilterChange = onTagFilterChange,
onDepartmentFilterChange = onDepartmentFilterChange,
- onMonthFilterChange = onMonthFilterChange
+ onMonthFilterChange = onMonthFilterChange,
+ refreshing = refreshing,
+ onRefresh = { onRefresh(NoticeTab.KwHome) }
)
}
NoticeTab.SwCentral.ordinal -> {
@@ -122,7 +128,22 @@ fun NoticeScreen(
onDeleteFromFavorite = onDeleteFromFavorite,
favoriteNotices = favoriteNotices,
filterState = filterState,
- onMonthFilterChange = onMonthFilterChange
+ onMonthFilterChange = onMonthFilterChange,
+ refreshing = refreshing,
+ onRefresh = { onRefresh(NoticeTab.SwCentral) }
+ )
+ }
+ NoticeTab.KwDorm.ordinal -> {
+ KwDormContent(
+ uiState = uiState.kwDormNoticeUiState,
+ onClickNotice = onClickNotice,
+ onAddToFavorite = onAddToFavorite,
+ onDeleteFromFavorite = onDeleteFromFavorite,
+ favoriteNotices = favoriteNotices,
+ filterState = filterState,
+ onMonthFilterChange = onMonthFilterChange,
+ refreshing = refreshing,
+ onRefresh = { onRefresh(NoticeTab.KwDorm) }
)
}
}
@@ -154,10 +175,12 @@ private fun NoticeScreenPreview() {
months = listOf(1)
),
swCentralNoticeUiState = SwCentralNoticeUiState.Failure,
+ kwDormNoticeUiState = KwDormNoticeUiState.Failure,
favoriteNotices = emptyList()
),
filterState = NoticeFilterState.Unspecified,
favoriteNotices = emptyList(),
+ refreshing = false,
onClickNotice = {},
onAddToFavorite = {},
onDeleteFromFavorite = {},
@@ -165,7 +188,8 @@ private fun NoticeScreenPreview() {
onTagFilterChange = {},
onSearch = {},
onMonthFilterChange = {},
- onInitFilter = {}
+ onInitFilter = {},
+ onRefresh = {}
)
}
}
\ No newline at end of file
diff --git a/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/notice/NoticeTab.kt b/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/notice/NoticeTab.kt
index abfea17..970b5fb 100644
--- a/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/notice/NoticeTab.kt
+++ b/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/notice/NoticeTab.kt
@@ -7,5 +7,6 @@ enum class NoticeTab(
@StringRes val textRes: Int
) {
KwHome(textRes = R.string.kw_home),
- SwCentral(textRes = R.string.sw_central)
+ SwCentral(textRes = R.string.sw_central_short),
+ KwDorm(textRes = R.string.kw_dorm_short)
}
diff --git a/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/notice/NoticeUiState.kt b/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/notice/NoticeUiState.kt
index 6751eea..6915a5a 100644
--- a/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/notice/NoticeUiState.kt
+++ b/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/notice/NoticeUiState.kt
@@ -4,15 +4,17 @@ import dev.yjyoon.kwnotice.domain.model.Favorite
import dev.yjyoon.kwnotice.domain.model.Notice
data class NoticeUiState(
- val kwHomeNoticeUiState: KwHomeNoticeUiState,
- val swCentralNoticeUiState: SwCentralNoticeUiState,
- val favoriteNotices: List
+ var kwHomeNoticeUiState: KwHomeNoticeUiState,
+ var swCentralNoticeUiState: SwCentralNoticeUiState,
+ var kwDormNoticeUiState: KwDormNoticeUiState,
+ var favoriteNotices: List
) {
companion object {
val Loading = NoticeUiState(
kwHomeNoticeUiState = KwHomeNoticeUiState.Loading,
swCentralNoticeUiState = SwCentralNoticeUiState.Loading,
+ kwDormNoticeUiState = KwDormNoticeUiState.Loading,
favoriteNotices = emptyList()
)
}
@@ -39,3 +41,13 @@ sealed interface SwCentralNoticeUiState {
object Loading : SwCentralNoticeUiState
object Failure : SwCentralNoticeUiState
}
+
+sealed interface KwDormNoticeUiState {
+ data class Success(
+ val notices: List,
+ val months: List
+ ) : KwDormNoticeUiState
+
+ object Loading : KwDormNoticeUiState
+ object Failure : KwDormNoticeUiState
+}
diff --git a/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/notice/NoticeViewModel.kt b/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/notice/NoticeViewModel.kt
index 277c0e4..d7e7a3e 100644
--- a/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/notice/NoticeViewModel.kt
+++ b/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/notice/NoticeViewModel.kt
@@ -1,5 +1,8 @@
package dev.yjyoon.kwnotice.presentation.ui.notice
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import dev.yjyoon.kwnotice.domain.model.Notice
@@ -7,9 +10,11 @@ import dev.yjyoon.kwnotice.domain.model.toFavorite
import dev.yjyoon.kwnotice.domain.usecase.favorite.AddFavoriteUseCase
import dev.yjyoon.kwnotice.domain.usecase.favorite.DeleteFavoriteUseCase
import dev.yjyoon.kwnotice.domain.usecase.favorite.GetAllFavoriteListUseCase
+import dev.yjyoon.kwnotice.domain.usecase.notice.GetKwDormNoticeListUseCase
import dev.yjyoon.kwnotice.domain.usecase.notice.GetKwHomeNoticeListUseCase
import dev.yjyoon.kwnotice.domain.usecase.notice.GetSwCentralNoticeListUseCase
import dev.yjyoon.kwnotice.presentation.ui.base.BaseViewModel
+import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
@@ -18,6 +23,7 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
+import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
@@ -25,11 +31,12 @@ class NoticeViewModel @Inject constructor(
getAllFavoriteListUseCase: GetAllFavoriteListUseCase,
private val getKwHomeNoticeListUseCase: GetKwHomeNoticeListUseCase,
private val getSwCentralNoticeListUseCase: GetSwCentralNoticeListUseCase,
+ private val getKwDormNoticeListUseCase: GetKwDormNoticeListUseCase,
private val addFavoriteUseCase: AddFavoriteUseCase,
private val deleteFavoriteUseCase: DeleteFavoriteUseCase
) : BaseViewModel() {
- val uiState: StateFlow = combine(
+ var uiState: StateFlow = combine(
flow {
getKwHomeNoticeListUseCase()
.onSuccess { emit(it) }
@@ -40,8 +47,13 @@ class NoticeViewModel @Inject constructor(
.onSuccess { emit(it) }
.onFailure { emit(null) }
},
+ flow {
+ getKwDormNoticeListUseCase()
+ .onSuccess { emit(it) }
+ .onFailure { emit(null) }
+ },
getAllFavoriteListUseCase()
- ) { kwHomeNotices, swCentralNotices, favoriteNotices ->
+ ) { kwHomeNotices, swCentralNotices, kwDormNotices, favoriteNotices ->
NoticeUiState(
kwHomeNoticeUiState = if (kwHomeNotices != null) {
KwHomeNoticeUiState.Success(
@@ -61,6 +73,14 @@ class NoticeViewModel @Inject constructor(
} else {
SwCentralNoticeUiState.Failure
},
+ kwDormNoticeUiState = if (kwDormNotices != null) {
+ KwDormNoticeUiState.Success(
+ notices = kwDormNotices,
+ months = kwDormNotices.map { it.postedDate.monthValue }.distinct()
+ )
+ } else {
+ KwDormNoticeUiState.Failure
+ },
favoriteNotices = favoriteNotices
)
}.stateIn(viewModelScope, SharingStarted.Eagerly, NoticeUiState.Loading)
@@ -68,6 +88,8 @@ class NoticeViewModel @Inject constructor(
private val _filterState = MutableStateFlow(NoticeFilterState.Unspecified)
val filterState: StateFlow = _filterState.asStateFlow()
+ var refreshing by mutableStateOf(false)
+
fun addFavorite(notice: Notice) {
launch {
addFavoriteUseCase(notice.toFavorite()).getOrThrow()
@@ -107,4 +129,66 @@ class NoticeViewModel @Inject constructor(
fun initFilter() {
_filterState.value = NoticeFilterState.Unspecified
}
+
+ fun refresh(tab: NoticeTab) {
+ when (tab) {
+ NoticeTab.KwHome -> {
+ viewModelScope.launch {
+ refreshing = true
+ getKwHomeNoticeListUseCase()
+ .onSuccess { kwHomeNotices ->
+ uiState.value.kwHomeNoticeUiState = KwHomeNoticeUiState.Success(
+ notices = kwHomeNotices,
+ tags = kwHomeNotices.map { it.tag }.distinct(),
+ departments = kwHomeNotices.map { it.department }.distinct(),
+ months = kwHomeNotices.map { it.modifiedDate.monthValue }.distinct()
+ )
+ }
+ .onFailure {
+ uiState.value.kwHomeNoticeUiState = KwHomeNoticeUiState.Failure
+ }
+ delay(250L)
+ refreshing = false
+ }
+ }
+ NoticeTab.SwCentral -> {
+ viewModelScope.launch {
+ refreshing = true
+ getSwCentralNoticeListUseCase()
+ .onSuccess { swCentralNotices ->
+ uiState.value.swCentralNoticeUiState = SwCentralNoticeUiState.Success(
+ notices = swCentralNotices,
+ months = swCentralNotices
+ .map { it.postedDate.monthValue }
+ .distinct()
+ )
+ }
+ .onFailure {
+ uiState.value.swCentralNoticeUiState = SwCentralNoticeUiState.Failure
+ }
+ delay(250L)
+ refreshing = false
+ }
+ }
+ NoticeTab.KwDorm -> {
+ viewModelScope.launch {
+ refreshing = true
+ getKwDormNoticeListUseCase()
+ .onSuccess { kwDormNotices ->
+ uiState.value.kwDormNoticeUiState = KwDormNoticeUiState.Success(
+ notices = kwDormNotices,
+ months = kwDormNotices
+ .map { it.postedDate.monthValue }
+ .distinct()
+ )
+ }
+ .onFailure {
+ uiState.value.kwDormNoticeUiState = KwDormNoticeUiState.Failure
+ }
+ delay(250L)
+ refreshing = false
+ }
+ }
+ }
+ }
}
diff --git a/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/notice/SwCentralContent.kt b/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/notice/SwCentralContent.kt
index 3281d61..d003adb 100644
--- a/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/notice/SwCentralContent.kt
+++ b/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/notice/SwCentralContent.kt
@@ -1,15 +1,24 @@
package dev.yjyoon.kwnotice.presentation.ui.notice
import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
+import androidx.compose.material.ExperimentalMaterialApi
+import androidx.compose.material.pullrefresh.PullRefreshIndicator
+import androidx.compose.material.pullrefresh.pullRefresh
+import androidx.compose.material.pullrefresh.rememberPullRefreshState
import androidx.compose.material3.CircularProgressIndicator
+import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -26,10 +35,12 @@ fun SwCentralContent(
uiState: SwCentralNoticeUiState,
filterState: NoticeFilterState,
favoriteNotices: List,
+ refreshing: Boolean,
onClickNotice: (String) -> Unit,
onAddToFavorite: (Notice) -> Unit,
onDeleteFromFavorite: (Notice) -> Unit,
- onMonthFilterChange: (String?) -> Unit
+ onMonthFilterChange: (String?) -> Unit,
+ onRefresh: () -> Unit
) {
when (uiState) {
is SwCentralNoticeUiState.Success -> {
@@ -40,6 +51,20 @@ fun SwCentralContent(
.padding(horizontal = 18.dp, vertical = 8.dp),
horizontalArrangement = Arrangement.End
) {
+ KwNoticeDropdownMenu(
+ leadingIconRes = R.drawable.ic_tag,
+ initialItem = stringResource(id = R.string.filter_tag_all),
+ items = listOf(),
+ onSelectItem = { }
+ )
+ Spacer(Modifier.width(8.dp))
+ KwNoticeDropdownMenu(
+ leadingIconRes = R.drawable.ic_group,
+ initialItem = stringResource(id = R.string.filter_department_all),
+ items = listOf(),
+ onSelectItem = { }
+ )
+ Spacer(Modifier.weight(1f))
KwNoticeDropdownMenu(
leadingIconRes = R.drawable.ic_calendar,
initialItem = stringResource(id = R.string.filter_month_all),
@@ -50,10 +75,12 @@ fun SwCentralContent(
SwCentralNoticeColumn(
uiState = uiState,
filterState = filterState,
+ favoriteNotices = favoriteNotices,
+ refreshing = refreshing,
onClickNotice = onClickNotice,
onAddToFavorite = onAddToFavorite,
onDeleteFromFavorite = onDeleteFromFavorite,
- favoriteNotices = favoriteNotices,
+ onRefresh = onRefresh
)
}
}
@@ -61,37 +88,57 @@ fun SwCentralContent(
CircularProgressIndicator()
}
SwCentralNoticeUiState.Failure -> {
- FailureScreen()
+ FailureScreen(onRefresh = onRefresh)
}
}
}
+@OptIn(ExperimentalMaterialApi::class)
@Composable
fun SwCentralNoticeColumn(
uiState: SwCentralNoticeUiState.Success,
filterState: NoticeFilterState,
favoriteNotices: List,
+ refreshing: Boolean,
onClickNotice: (String) -> Unit,
onAddToFavorite: (Notice) -> Unit,
- onDeleteFromFavorite: (Notice) -> Unit
+ onDeleteFromFavorite: (Notice) -> Unit,
+ onRefresh: () -> Unit
) {
- LazyColumn(
- Modifier.fillMaxSize(),
- contentPadding = PaddingValues(horizontal = 18.dp, vertical = 0.dp),
- verticalArrangement = Arrangement.spacedBy(18.dp, Alignment.Top)
+ val pullRefreshState = rememberPullRefreshState(
+ refreshing = refreshing,
+ onRefresh = onRefresh
+ )
+ Box(
+ modifier = Modifier.pullRefresh(pullRefreshState)
) {
- items(uiState.notices.filter { filterState.filtering(it) }) {
- NoticeCard(
- notice = it,
- onClickNotice = onClickNotice,
- bookmarked = favoriteNotices.contains(it.toFavorite()),
- onToggleBookmark = { notice, bookmarked ->
- if (bookmarked) {
- onAddToFavorite(notice)
- } else {
- onDeleteFromFavorite(notice)
+ LazyColumn(
+ Modifier.fillMaxSize(),
+ contentPadding = PaddingValues(horizontal = 18.dp, vertical = 0.dp),
+ verticalArrangement = Arrangement.spacedBy(18.dp, Alignment.Top)
+ ) {
+ items(uiState.notices.filter { filterState.filtering(it) }) {
+ NoticeCard(
+ notice = it,
+ onClickNotice = onClickNotice,
+ bookmarked = favoriteNotices.contains(it.toFavorite()),
+ onToggleBookmark = { notice, bookmarked ->
+ if (bookmarked) {
+ onAddToFavorite(notice)
+ } else {
+ onDeleteFromFavorite(notice)
+ }
}
- })
+ )
+ }
+ item { Spacer(modifier = Modifier.height(4.dp)) }
}
+ PullRefreshIndicator(
+ modifier = Modifier.align(Alignment.TopCenter),
+ refreshing = refreshing,
+ state = pullRefreshState,
+ contentColor = MaterialTheme.colorScheme.primary,
+ scale = true
+ )
}
-}
\ No newline at end of file
+}
diff --git a/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/osl/OslScreen.kt b/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/osl/OslScreen.kt
index 186f127..c83a750 100644
--- a/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/osl/OslScreen.kt
+++ b/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/osl/OslScreen.kt
@@ -9,7 +9,11 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Close
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalUriHandler
@@ -17,7 +21,6 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import dev.yjyoon.kwnotice.presentation.R
import dev.yjyoon.kwnotice.presentation.ui.component.KwNoticeOslBar
-import dev.yjyoon.kwnotice.presentation.ui.component.KwNoticeTopAppBar
import dev.yjyoon.kwnotice.presentation.ui.model.OpenSourceLicense
@Composable
@@ -31,10 +34,16 @@ fun OslScreen(
.fillMaxSize()
.background(color = MaterialTheme.colorScheme.background)
) {
- KwNoticeTopAppBar(
- titleText = stringResource(id = R.string.settings_osl),
- actionIcon = Icons.Default.Close,
- onActionClick = onClose
+ TopAppBar(
+ title = {},
+ actions = {
+ IconButton(onClick = onClose) {
+ Icon(
+ imageVector = Icons.Default.Close,
+ contentDescription = null
+ )
+ }
+ }
)
LazyColumn(
Modifier.padding(horizontal = 18.dp),
diff --git a/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/settings/SettingsScreen.kt b/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/settings/SettingsScreen.kt
index cadf8c1..6aa581e 100644
--- a/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/settings/SettingsScreen.kt
+++ b/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/settings/SettingsScreen.kt
@@ -7,6 +7,8 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Info
import androidx.compose.material3.AlertDialog
@@ -33,8 +35,8 @@ import dev.yjyoon.kwnotice.domain.model.VersionName
import dev.yjyoon.kwnotice.presentation.R
import dev.yjyoon.kwnotice.presentation.ui.component.KwNoticeDivider
import dev.yjyoon.kwnotice.presentation.ui.component.KwNoticeLoading
+import dev.yjyoon.kwnotice.presentation.ui.component.KwNoticeSimpleTopAppBar
import dev.yjyoon.kwnotice.presentation.ui.component.KwNoticeSwitchBar
-import dev.yjyoon.kwnotice.presentation.ui.component.KwNoticeTopAppBar
import dev.yjyoon.kwnotice.presentation.ui.component.KwNoticeTouchBar
import dev.yjyoon.kwnotice.presentation.ui.model.FcmTopicModel
import dev.yjyoon.kwnotice.presentation.ui.theme.KwNoticeTheme
@@ -103,11 +105,14 @@ fun SettingsContent(
versionName: VersionName
) {
val uriHandler = LocalUriHandler.current
+ val scrollState = rememberScrollState()
Column(
- Modifier.fillMaxSize()
+ Modifier
+ .fillMaxSize()
+ .verticalScroll(scrollState)
) {
- KwNoticeTopAppBar(
+ KwNoticeSimpleTopAppBar(
titleText = stringResource(id = R.string.navigation_settings),
actionIcon = Icons.Outlined.Info,
onActionClick = onOpenDialog
@@ -142,6 +147,24 @@ fun SettingsContent(
onUnsubscribe = onUnsubscribe
)
KwNoticeDivider()
+ SettingsTitle(
+ Modifier.padding(horizontal = 16.dp),
+ text = stringResource(id = R.string.kw_dorm)
+ )
+ Spacer(Modifier.height(4.dp))
+ FcmTopicSwitchBar(
+ uiState = uiState,
+ fcmTopicModel = FcmTopicModel(FcmTopic.KwDormCommon),
+ onSubscribe = onSubscribe,
+ onUnsubscribe = onUnsubscribe
+ )
+ FcmTopicSwitchBar(
+ uiState = uiState,
+ fcmTopicModel = FcmTopicModel(FcmTopic.KwDormRecruitment),
+ onSubscribe = onSubscribe,
+ onUnsubscribe = onUnsubscribe
+ )
+ KwNoticeDivider()
SettingsTitle(
Modifier.padding(horizontal = 16.dp),
text = stringResource(id = R.string.settings_app_info)
diff --git a/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/splash/SplashActivity.kt b/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/splash/SplashActivity.kt
index 59656e9..6b126b3 100644
--- a/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/splash/SplashActivity.kt
+++ b/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/splash/SplashActivity.kt
@@ -1,9 +1,16 @@
package dev.yjyoon.kwnotice.presentation.ui.splash
+import android.Manifest
+import android.content.pm.PackageManager
+import android.os.Build
import android.os.Bundle
+import android.widget.Toast
+import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
+import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope
import dagger.hilt.android.AndroidEntryPoint
+import dev.yjyoon.kwnotice.presentation.R
import dev.yjyoon.kwnotice.presentation.ui.base.BaseActivity
import dev.yjyoon.kwnotice.presentation.ui.main.MainActivity
import kotlinx.coroutines.delay
@@ -25,11 +32,48 @@ class SplashActivity : BaseActivity() {
}
}
- private fun handleState(state: SplashState) =
+ private fun handleState(state: SplashState) {
when (state) {
- SplashState.Done -> startMainActivity()
- SplashState.Waiting -> Unit
+ SplashState.Done -> requestNotificationPermission()
+ SplashState.Waiting -> {}
}
+ }
+
+ private fun requestNotificationPermission() {
+ if (ContextCompat.checkSelfPermission(
+ this,
+ Manifest.permission.POST_NOTIFICATIONS
+ ) != PackageManager.PERMISSION_GRANTED
+ ) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ notificationPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
+ } else {
+ Toast.makeText(
+ this,
+ R.string.on_disable_notification,
+ Toast.LENGTH_LONG
+ ).show()
+ }
+ } else {
+ startMainActivity()
+ }
+ }
+
+ private val notificationPermissionLauncher =
+ registerForActivityResult(
+ ActivityResultContracts.RequestPermission()
+ ) {
+ if (!it) onDenyNotificationPermission()
+ startMainActivity()
+ }
+
+ private fun onDenyNotificationPermission() =
+ Toast.makeText(
+ this,
+ R.string.on_deny_notification_permission,
+ Toast.LENGTH_LONG
+ ).show()
+
private fun startMainActivity() {
MainActivity.startActivity(this)
diff --git a/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/theme/Type.kt b/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/theme/Type.kt
index 1cd56c6..33c2dad 100644
--- a/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/theme/Type.kt
+++ b/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/theme/Type.kt
@@ -98,7 +98,7 @@ val KwNoticeTypography = Typography(
fontWeight = FontWeight.Bold,
letterSpacing = 0.sp,
lineHeight = 28.sp,
- fontSize = 22.sp
+ fontSize = 26.sp
),
titleMedium = TextStyle(
fontFamily = Roboto,
diff --git a/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/util/DateDisplayUtil.kt b/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/util/DateDisplayUtil.kt
new file mode 100644
index 0000000..d0c3131
--- /dev/null
+++ b/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/util/DateDisplayUtil.kt
@@ -0,0 +1,30 @@
+package dev.yjyoon.kwnotice.presentation.ui.util
+
+import java.time.LocalDate
+import java.time.temporal.ChronoUnit
+
+object DateDisplayUtil {
+ fun LocalDate.toRelativeDateString(): String {
+ val today = LocalDate.now()
+
+ return when {
+ plusYears(1).isBeforeOrEqual(today) -> {
+ "${ChronoUnit.YEARS.between(this, today)}년 전"
+ }
+ plusMonths(1).isBeforeOrEqual(today) -> {
+ "${ChronoUnit.MONTHS.between(this, today)}달 전"
+ }
+ plusWeeks(1).isBeforeOrEqual(today) -> {
+ "${ChronoUnit.WEEKS.between(this, today)}주 전"
+ }
+ plusDays(1).isBeforeOrEqual(today) -> {
+ "${ChronoUnit.DAYS.between(this, today)}일 전"
+ }
+ else -> {
+ "오늘"
+ }
+ }
+ }
+
+ private fun LocalDate.isBeforeOrEqual(other: LocalDate) = isEqual(other) || isBefore(other)
+}
diff --git a/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/webview/WebViewScreen.kt b/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/webview/WebViewScreen.kt
index c2f9f5e..f949ef6 100644
--- a/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/webview/WebViewScreen.kt
+++ b/presentation/src/main/java/dev/yjyoon/kwnotice/presentation/ui/webview/WebViewScreen.kt
@@ -9,6 +9,7 @@ import androidx.compose.foundation.layout.size
import androidx.compose.material3.ExtendedFloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.LinearProgressIndicator
+import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@@ -80,6 +81,8 @@ fun WebViewFloatingActionButton(
modifier = Modifier.size(28.dp)
)
},
- onClick = onClick
+ onClick = onClick,
+ containerColor = MaterialTheme.colorScheme.primary,
+ contentColor = MaterialTheme.colorScheme.onPrimary
)
}
diff --git a/presentation/src/main/res/values/strings.xml b/presentation/src/main/res/values/strings.xml
index c693ed3..7e259a6 100644
--- a/presentation/src/main/res/values/strings.xml
+++ b/presentation/src/main/res/values/strings.xml
@@ -2,13 +2,19 @@
KW 알리미
광운대학교
+ 광운대
SW중심대학사업단
+ SW중심대
+ 빛솔재 공공기숙사
+ 빛솔재
공지사항
즐겨찾기
환경설정
- M월 d일
+ 공지사항 알림 수신을 위해 알림 권한을 허용해주세요
+ 공지사항 알림 수신을 위해 앱 정보에서 알림 권한을 허용해주세요
+
공지사항을 불러올 수 없습니다.\n네트워크 연결 상태를 확인해주세요!
아직 등록된 즐겨찾기가 없습니다.\n공지사항에서 즐겨찾기를 추가해보세요!
@@ -21,10 +27,17 @@
전체 날짜
월
+ 즐겨찾기에서 삭제하였습니다
+ 되돌리기
+
웹으로 열기
새로운 공지사항 등록 알림
기존 공지사항 수정 알림
+
+ 일반 공지사항 등록 알림
+ 인원 모집 공지사항 등록 알림
+
앱 정보
개발 정보
yjyoon.dev@gmail.com
@@ -33,4 +46,6 @@
"Copyright ⓒ %s All rights reserved."
본 애플리케이션은 광운대학교 재학생들을 위해 재학생이 비영리 목적으로 개발한 공지사항 알림 애플리케이션입니다.
ver %s
+
+ Unknown
\ No newline at end of file