diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9e49256c5..eed75ff06 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,12 +34,13 @@ jobs: - name: Grant execute permission for gradlew run: chmod +x gradlew - - name: Generate google-services.json + - name: Create google-services.json run: | - printf "%s" "${{ secrets.GOOGLE_SERVICES }}" | base64 --decode > ./app/google-services.json - + mkdir -p ${{ github.workspace }}/app + echo '${{ secrets.GOOGLE_SERVICES_JSON }}' > ${{ github.workspace }}/app/google-services.json + - name: Build with Gradle - run: ./gradlew build + run: ./gradlew assembleDebug --parallel - name: Run test run: ./gradlew test diff --git a/app/src/dev/AndroidManifest.xml b/app/src/dev/AndroidManifest.xml index 3d0dd1c6b..a480689de 100644 --- a/app/src/dev/AndroidManifest.xml +++ b/app/src/dev/AndroidManifest.xml @@ -11,18 +11,22 @@ + android:name=".android.app.DevApplication" + tools:replace="android:name" + android:allowBackup="true" + android:enableOnBackInvokedCallback="true" + android:icon="@mipmap/ic_launcher" + android:label="@string/app_name" + android:roundIcon="@mipmap/ic_launcher_round" + android:supportsRtl="true" + android:theme="@style/Theme.DMS" + android:usesCleartextTraffic="true" + tools:targetApi="tiramisu"> + android:screenOrientation="portrait"> CompositionLocalProvider(LocalResultStore provides resultStore) { NavDisplay( modifier = Modifier - .background(DmsTheme.colorScheme.surfaceTint) .padding(paddingValues), backStack = backStack, onBack = { backStack.removeLastOrNull() }, diff --git a/app/src/dev/res/drawable/ic_launcher_background.xml b/app/src/dev/res/drawable/ic_launcher_background.xml new file mode 100644 index 000000000..e93e11ade --- /dev/null +++ b/app/src/dev/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/dev/res/drawable/ic_launcher_foreground.xml b/app/src/dev/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 000000000..bd5df3f76 --- /dev/null +++ b/app/src/dev/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/dev/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/dev/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 000000000..7353dbd1f --- /dev/null +++ b/app/src/dev/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/dev/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/dev/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 000000000..7353dbd1f --- /dev/null +++ b/app/src/dev/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/dev/res/mipmap-hdpi/ic_launcher.webp b/app/src/dev/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 000000000..583d0ec80 Binary files /dev/null and b/app/src/dev/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/app/src/dev/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/dev/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 000000000..3c9fac50a Binary files /dev/null and b/app/src/dev/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/app/src/dev/res/mipmap-mdpi/ic_launcher.webp b/app/src/dev/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 000000000..55b51f4f8 Binary files /dev/null and b/app/src/dev/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/app/src/dev/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/dev/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 000000000..9d9e9145d Binary files /dev/null and b/app/src/dev/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/app/src/dev/res/mipmap-xhdpi/ic_launcher.webp b/app/src/dev/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 000000000..395a97c04 Binary files /dev/null and b/app/src/dev/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/app/src/dev/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/dev/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 000000000..aa7ac79ab Binary files /dev/null and b/app/src/dev/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/app/src/dev/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/dev/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 000000000..b2b77aeab Binary files /dev/null and b/app/src/dev/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/app/src/dev/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/dev/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 000000000..e23d28e3b Binary files /dev/null and b/app/src/dev/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/app/src/dev/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/dev/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 000000000..fafb3427d Binary files /dev/null and b/app/src/dev/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/app/src/dev/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/dev/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 000000000..65581e360 Binary files /dev/null and b/app/src/dev/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/app/src/dev/res/values/ic_launcher_background.xml b/app/src/dev/res/values/ic_launcher_background.xml new file mode 100644 index 000000000..c5d5899fd --- /dev/null +++ b/app/src/dev/res/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #FFFFFF + \ No newline at end of file diff --git a/app/src/dev/res/values/strings.xml b/app/src/dev/res/values/strings.xml new file mode 100644 index 000000000..5b0c78e41 --- /dev/null +++ b/app/src/dev/res/values/strings.xml @@ -0,0 +1,3 @@ + + DMS + \ No newline at end of file diff --git a/core/design-system/src/dev/res/drawable-night/ic_equal.xml b/core/design-system/src/dev/res/drawable-night/ic_equal.xml new file mode 100644 index 000000000..e566f5b20 --- /dev/null +++ b/core/design-system/src/dev/res/drawable-night/ic_equal.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/core/design-system/src/dev/res/drawable-night/ic_minus.xml b/core/design-system/src/dev/res/drawable-night/ic_minus.xml new file mode 100644 index 000000000..8fea3687d --- /dev/null +++ b/core/design-system/src/dev/res/drawable-night/ic_minus.xml @@ -0,0 +1,12 @@ + + + + diff --git a/core/design-system/src/dev/res/drawable-night/ic_plus.xml b/core/design-system/src/dev/res/drawable-night/ic_plus.xml new file mode 100644 index 000000000..677d42886 --- /dev/null +++ b/core/design-system/src/dev/res/drawable-night/ic_plus.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/core/design-system/src/dev/res/drawable-night/img_morning.png b/core/design-system/src/dev/res/drawable-night/img_morning.png new file mode 100644 index 000000000..b0c3bd9a3 Binary files /dev/null and b/core/design-system/src/dev/res/drawable-night/img_morning.png differ diff --git a/core/device/build.gradle.kts b/core/device/build.gradle.kts index 137dd4116..254a632c6 100644 --- a/core/device/build.gradle.kts +++ b/core/device/build.gradle.kts @@ -35,6 +35,17 @@ android { kotlinOptions { jvmTarget = Versions.java.toString() } + + flavorDimensions += "environment" + + productFlavors { + create("dev") { + dimension = "environment" + } + create("prod") { + dimension = "environment" + } + } } dependencies { diff --git a/core/device/src/main/java/team/aliens/dms/android/core/device/datastore/DeviceDataStoreDataSource.kt b/core/device/src/dev/kotlin/team/aliens/dms/android/core/device/datastore/DeviceDataStoreDataSource.kt similarity index 100% rename from core/device/src/main/java/team/aliens/dms/android/core/device/datastore/DeviceDataStoreDataSource.kt rename to core/device/src/dev/kotlin/team/aliens/dms/android/core/device/datastore/DeviceDataStoreDataSource.kt diff --git a/core/device/src/main/java/team/aliens/dms/android/core/device/datastore/DeviceDataStoreDataSourceImpl.kt b/core/device/src/dev/kotlin/team/aliens/dms/android/core/device/datastore/DeviceDataStoreDataSourceImpl.kt similarity index 100% rename from core/device/src/main/java/team/aliens/dms/android/core/device/datastore/DeviceDataStoreDataSourceImpl.kt rename to core/device/src/dev/kotlin/team/aliens/dms/android/core/device/datastore/DeviceDataStoreDataSourceImpl.kt diff --git a/core/device/src/main/java/team/aliens/dms/android/core/device/datastore/store/DeviceStore.kt b/core/device/src/dev/kotlin/team/aliens/dms/android/core/device/datastore/store/DeviceStore.kt similarity index 100% rename from core/device/src/main/java/team/aliens/dms/android/core/device/datastore/store/DeviceStore.kt rename to core/device/src/dev/kotlin/team/aliens/dms/android/core/device/datastore/store/DeviceStore.kt diff --git a/core/device/src/main/java/team/aliens/dms/android/core/device/datastore/store/DeviceStoreImpl.kt b/core/device/src/dev/kotlin/team/aliens/dms/android/core/device/datastore/store/DeviceStoreImpl.kt similarity index 100% rename from core/device/src/main/java/team/aliens/dms/android/core/device/datastore/store/DeviceStoreImpl.kt rename to core/device/src/dev/kotlin/team/aliens/dms/android/core/device/datastore/store/DeviceStoreImpl.kt diff --git a/core/device/src/main/java/team/aliens/dms/android/core/device/datastore/store/exception/CannotStoreDeviceTokenException.kt b/core/device/src/dev/kotlin/team/aliens/dms/android/core/device/datastore/store/exception/CannotStoreDeviceTokenException.kt similarity index 100% rename from core/device/src/main/java/team/aliens/dms/android/core/device/datastore/store/exception/CannotStoreDeviceTokenException.kt rename to core/device/src/dev/kotlin/team/aliens/dms/android/core/device/datastore/store/exception/CannotStoreDeviceTokenException.kt diff --git a/core/device/src/main/java/team/aliens/dms/android/core/device/datastore/store/exception/TokenNotFoundException.kt b/core/device/src/dev/kotlin/team/aliens/dms/android/core/device/datastore/store/exception/TokenNotFoundException.kt similarity index 100% rename from core/device/src/main/java/team/aliens/dms/android/core/device/datastore/store/exception/TokenNotFoundException.kt rename to core/device/src/dev/kotlin/team/aliens/dms/android/core/device/datastore/store/exception/TokenNotFoundException.kt diff --git a/core/device/src/main/java/team/aliens/dms/android/core/device/di/DataSourceModule.kt b/core/device/src/dev/kotlin/team/aliens/dms/android/core/device/di/DataSourceModule.kt similarity index 100% rename from core/device/src/main/java/team/aliens/dms/android/core/device/di/DataSourceModule.kt rename to core/device/src/dev/kotlin/team/aliens/dms/android/core/device/di/DataSourceModule.kt diff --git a/core/device/src/main/java/team/aliens/dms/android/core/device/di/StoreModule.kt b/core/device/src/dev/kotlin/team/aliens/dms/android/core/device/di/StoreModule.kt similarity index 100% rename from core/device/src/main/java/team/aliens/dms/android/core/device/di/StoreModule.kt rename to core/device/src/dev/kotlin/team/aliens/dms/android/core/device/di/StoreModule.kt diff --git a/core/device/src/main/AndroidManifest.xml b/core/device/src/main/AndroidManifest.xml deleted file mode 100644 index a5918e68a..000000000 --- a/core/device/src/main/AndroidManifest.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/core/device/src/prod/kotlin/team/aliens/dms/android/core/device/datastore/DeviceDataStoreDataSource.kt b/core/device/src/prod/kotlin/team/aliens/dms/android/core/device/datastore/DeviceDataStoreDataSource.kt new file mode 100644 index 000000000..71ba259fc --- /dev/null +++ b/core/device/src/prod/kotlin/team/aliens/dms/android/core/device/datastore/DeviceDataStoreDataSource.kt @@ -0,0 +1,10 @@ +package team.aliens.dms.android.core.device.datastore + +abstract class DeviceDataStoreDataSource { + + abstract suspend fun loadDeviceToken(): String + + abstract suspend fun storeDeviceToken(deviceToken: String) + + abstract suspend fun clearDeviceToken() +} diff --git a/core/device/src/prod/kotlin/team/aliens/dms/android/core/device/datastore/DeviceDataStoreDataSourceImpl.kt b/core/device/src/prod/kotlin/team/aliens/dms/android/core/device/datastore/DeviceDataStoreDataSourceImpl.kt new file mode 100644 index 000000000..08b2633e7 --- /dev/null +++ b/core/device/src/prod/kotlin/team/aliens/dms/android/core/device/datastore/DeviceDataStoreDataSourceImpl.kt @@ -0,0 +1,17 @@ +package team.aliens.dms.android.core.device.datastore + +import team.aliens.dms.android.core.device.datastore.store.DeviceStore +import team.aliens.dms.android.shared.exception.util.suspendRunCatching +import javax.inject.Inject + +internal class DeviceDataStoreDataSourceImpl @Inject constructor( + private val deviceStore: DeviceStore, +) : DeviceDataStoreDataSource() { + override suspend fun loadDeviceToken(): String = deviceStore.loadDeviceToken() + + override suspend fun storeDeviceToken(deviceToken: String) = + deviceStore.storeDeviceToken(deviceToken) + + override suspend fun clearDeviceToken() = + deviceStore.clearDeviceToken() +} diff --git a/core/device/src/prod/kotlin/team/aliens/dms/android/core/device/datastore/store/DeviceStore.kt b/core/device/src/prod/kotlin/team/aliens/dms/android/core/device/datastore/store/DeviceStore.kt new file mode 100644 index 000000000..ac641c0e9 --- /dev/null +++ b/core/device/src/prod/kotlin/team/aliens/dms/android/core/device/datastore/store/DeviceStore.kt @@ -0,0 +1,10 @@ +package team.aliens.dms.android.core.device.datastore.store + +internal abstract class DeviceStore { + + abstract suspend fun loadDeviceToken(): String + + abstract suspend fun storeDeviceToken(deviceToken: String) + + abstract suspend fun clearDeviceToken() +} diff --git a/core/device/src/prod/kotlin/team/aliens/dms/android/core/device/datastore/store/DeviceStoreImpl.kt b/core/device/src/prod/kotlin/team/aliens/dms/android/core/device/datastore/store/DeviceStoreImpl.kt new file mode 100644 index 000000000..32fb861ea --- /dev/null +++ b/core/device/src/prod/kotlin/team/aliens/dms/android/core/device/datastore/store/DeviceStoreImpl.kt @@ -0,0 +1,42 @@ +package team.aliens.dms.android.core.device.datastore.store + +import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.core.stringPreferencesKey +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.runBlocking +import team.aliens.dms.android.core.datastore.DeviceDataStore +import team.aliens.dms.android.core.datastore.PreferencesDataStore +import team.aliens.dms.android.core.datastore.util.transform +import team.aliens.dms.android.core.device.datastore.store.exception.CannotStoreDeviceTokenException +import team.aliens.dms.android.core.device.datastore.store.exception.DeviceTokenNotFoundException +import javax.inject.Inject + +internal class DeviceStoreImpl @Inject constructor( + @DeviceDataStore private val deviceDataStore: PreferencesDataStore, +) : DeviceStore() { + + override suspend fun loadDeviceToken(): String = deviceDataStore.data.map { preferences -> + preferences[DEVICE_TOKEN] ?: throw DeviceTokenNotFoundException() + }.first() + + override suspend fun storeDeviceToken(deviceToken: String) { + transform( + onFailure = { throw CannotStoreDeviceTokenException() }, + ) { + deviceDataStore.edit { preferences -> + preferences[DEVICE_TOKEN] = deviceToken + } + } + } + + override suspend fun clearDeviceToken() { + transform { + deviceDataStore.edit { preferences -> preferences.clear() } + } + } + + private companion object { + val DEVICE_TOKEN = stringPreferencesKey("device-token") + } +} diff --git a/core/device/src/prod/kotlin/team/aliens/dms/android/core/device/datastore/store/exception/CannotStoreDeviceTokenException.kt b/core/device/src/prod/kotlin/team/aliens/dms/android/core/device/datastore/store/exception/CannotStoreDeviceTokenException.kt new file mode 100644 index 000000000..0a51c63e9 --- /dev/null +++ b/core/device/src/prod/kotlin/team/aliens/dms/android/core/device/datastore/store/exception/CannotStoreDeviceTokenException.kt @@ -0,0 +1,5 @@ +package team.aliens.dms.android.core.device.datastore.store.exception + +import team.aliens.dms.android.core.datastore.exception.TransformFailureException + +class CannotStoreDeviceTokenException : TransformFailureException("Cannot store deviceToken") diff --git a/core/device/src/prod/kotlin/team/aliens/dms/android/core/device/datastore/store/exception/TokenNotFoundException.kt b/core/device/src/prod/kotlin/team/aliens/dms/android/core/device/datastore/store/exception/TokenNotFoundException.kt new file mode 100644 index 000000000..10583752b --- /dev/null +++ b/core/device/src/prod/kotlin/team/aliens/dms/android/core/device/datastore/store/exception/TokenNotFoundException.kt @@ -0,0 +1,7 @@ +package team.aliens.dms.android.core.device.datastore.store.exception + +import team.aliens.dms.android.core.datastore.exception.LoadFailureException + +sealed class TokenNotFoundException(message: String?) : LoadFailureException(message) + +class DeviceTokenNotFoundException : TokenNotFoundException("Device token not found") diff --git a/core/device/src/prod/kotlin/team/aliens/dms/android/core/device/di/DataSourceModule.kt b/core/device/src/prod/kotlin/team/aliens/dms/android/core/device/di/DataSourceModule.kt new file mode 100644 index 000000000..3ecf4ae4d --- /dev/null +++ b/core/device/src/prod/kotlin/team/aliens/dms/android/core/device/di/DataSourceModule.kt @@ -0,0 +1,18 @@ +package team.aliens.dms.android.core.device.di + +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import team.aliens.dms.android.core.device.datastore.DeviceDataStoreDataSource +import team.aliens.dms.android.core.device.datastore.DeviceDataStoreDataSourceImpl +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal abstract class DataSourceModule { + + @Binds + @Singleton + abstract fun bindDeviceDataStoreDataSource(impl: DeviceDataStoreDataSourceImpl): DeviceDataStoreDataSource +} diff --git a/core/device/src/prod/kotlin/team/aliens/dms/android/core/device/di/StoreModule.kt b/core/device/src/prod/kotlin/team/aliens/dms/android/core/device/di/StoreModule.kt new file mode 100644 index 000000000..f5311733c --- /dev/null +++ b/core/device/src/prod/kotlin/team/aliens/dms/android/core/device/di/StoreModule.kt @@ -0,0 +1,18 @@ +package team.aliens.dms.android.core.device.di + +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import team.aliens.dms.android.core.device.datastore.store.DeviceStore +import team.aliens.dms.android.core.device.datastore.store.DeviceStoreImpl +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal abstract class StoreModule { + + @Binds + @Singleton + abstract fun bindDeviceStore(impl: DeviceStoreImpl): DeviceStore +} diff --git a/data/src/dev/kotlin/team.aliens.dms.android.data/notice/mapper/NoticeMapper.kt b/data/src/dev/kotlin/team.aliens.dms.android.data/notice/mapper/NoticeMapper.kt index 6b13be2aa..0a4ae8e2e 100644 --- a/data/src/dev/kotlin/team.aliens.dms.android.data/notice/mapper/NoticeMapper.kt +++ b/data/src/dev/kotlin/team.aliens.dms.android.data/notice/mapper/NoticeMapper.kt @@ -1,7 +1,9 @@ package team.aliens.dms.android.data.notice.mapper import team.aliens.dms.android.core.database.entity.NoticeEntity +import team.aliens.dms.android.data.notice.model.LatestNotice import team.aliens.dms.android.data.notice.model.Notice +import team.aliens.dms.android.network.notice.model.FetchLatestNoticeResponse import team.aliens.dms.android.network.notice.model.FetchNoticeDetailsResponse import team.aliens.dms.android.network.notice.model.FetchNoticesResponse import team.aliens.dms.android.shared.date.toLocalDateTime @@ -31,3 +33,8 @@ internal fun FetchNoticeDetailsResponse.toModel(): Notice = Notice( content = this.content, createdAt = this.createdAt.toLocalDateTime(), ) + +internal fun FetchLatestNoticeResponse.toModel(): LatestNotice = LatestNotice( + id = this.id, + title = this.title, +) diff --git a/data/src/dev/kotlin/team.aliens.dms.android.data/notice/model/LatestNotice.kt b/data/src/dev/kotlin/team.aliens.dms.android.data/notice/model/LatestNotice.kt new file mode 100644 index 000000000..61a980129 --- /dev/null +++ b/data/src/dev/kotlin/team.aliens.dms.android.data/notice/model/LatestNotice.kt @@ -0,0 +1,8 @@ +package team.aliens.dms.android.data.notice.model + +import java.util.UUID + +data class LatestNotice( + val id: UUID? = null, + val title: String = "", +) diff --git a/data/src/dev/kotlin/team.aliens.dms.android.data/notice/repository/NoticeRepository.kt b/data/src/dev/kotlin/team.aliens.dms.android.data/notice/repository/NoticeRepository.kt index 6fd773934..4a7b4528a 100644 --- a/data/src/dev/kotlin/team.aliens.dms.android.data/notice/repository/NoticeRepository.kt +++ b/data/src/dev/kotlin/team.aliens.dms.android.data/notice/repository/NoticeRepository.kt @@ -1,6 +1,8 @@ package team.aliens.dms.android.data.notice.repository +import team.aliens.dms.android.data.notice.model.LatestNotice import team.aliens.dms.android.data.notice.model.Notice +import team.aliens.dms.android.network.notice.model.FetchLatestNoticeResponse import team.aliens.dms.android.shared.model.Order import java.util.UUID @@ -11,4 +13,6 @@ abstract class NoticeRepository { abstract suspend fun fetchNoticeDetails(noticeId: UUID): Result abstract suspend fun fetchNotices(order: Order = Order.NEW): Result> + + abstract suspend fun fetchLatestNotice(): Result } diff --git a/data/src/dev/kotlin/team.aliens.dms.android.data/notice/repository/NoticeRepositoryImpl.kt b/data/src/dev/kotlin/team.aliens.dms.android.data/notice/repository/NoticeRepositoryImpl.kt index 9c44720cd..b707316fa 100644 --- a/data/src/dev/kotlin/team.aliens.dms.android.data/notice/repository/NoticeRepositoryImpl.kt +++ b/data/src/dev/kotlin/team.aliens.dms.android.data/notice/repository/NoticeRepositoryImpl.kt @@ -1,6 +1,7 @@ package team.aliens.dms.android.data.notice.repository import team.aliens.dms.android.data.notice.mapper.toModel +import team.aliens.dms.android.data.notice.model.LatestNotice import team.aliens.dms.android.data.notice.model.Notice import team.aliens.dms.android.network.notice.datasource.NetworkNoticeDataSource import team.aliens.dms.android.shared.model.Order @@ -22,4 +23,8 @@ internal class NoticeRepositoryImpl @Inject constructor( override suspend fun fetchNotices(order: Order): Result> = networkNoticeDataSource.fetchNotices(order.name) .map { it.toModel() } + + override suspend fun fetchLatestNotice(): Result = + networkNoticeDataSource.fetchLatestNotice() + .map { it.toModel() } } diff --git a/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/ui/ApplicationScreen.kt b/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/ui/ApplicationScreen.kt index 1420fb31b..7d125ac2c 100644 --- a/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/ui/ApplicationScreen.kt +++ b/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/ui/ApplicationScreen.kt @@ -1,13 +1,17 @@ package team.aliens.dms.android.feature.main.application.ui +import android.R import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement +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.navigationBarsPadding import androidx.compose.foundation.layout.systemBarsPadding import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -15,10 +19,12 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextAlign import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import kotlinx.coroutines.launch import team.aliens.dms.android.core.designsystem.DmsTheme +import team.aliens.dms.android.core.designsystem.bodyM import team.aliens.dms.android.core.designsystem.snackbar.DmsSnackBarType import team.aliens.dms.android.core.designsystem.tab.DmsTab import team.aliens.dms.android.core.designsystem.tab.DmsTabRow @@ -120,6 +126,17 @@ private fun ApplicationScreen( onNavigateVolunteerApplication = onNavigateVolunteerApplication, ) } else { + if (state.votes.isEmpty()) { + Text( + text = """ + 예정된 투표가 없습니다. + 결과를 확인하시고 싶으시면 알림함을 확인해주세요! + """.trimIndent(), + style = DmsTheme.typography.bodyM, + color = DmsTheme.colorScheme.inverseSurface, + textAlign = TextAlign.Center, + ) + } VoteContent( votes = state.votes, onNavigateVote = onNavigateVote, diff --git a/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/home/model/AnnouncementButton.kt b/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/home/model/AnnouncementButton.kt index e5f1ec07a..c610228a4 100644 --- a/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/home/model/AnnouncementButton.kt +++ b/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/home/model/AnnouncementButton.kt @@ -23,6 +23,7 @@ import team.aliens.dms.android.core.designsystem.util.clickable @Composable internal fun AnnouncementButton( modifier: Modifier = Modifier, + title: String, onClick: () -> Unit, ) { Row( @@ -47,7 +48,7 @@ internal fun AnnouncementButton( ) Text( modifier = Modifier.padding(start = 8.dp), - text = "새로운 공지사항을 확인하세요", + text = title, style = DmsTheme.typography.labelM, color = DmsTheme.colorScheme.inverseSurface, ) diff --git a/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/home/model/DmsPointCotent.kt b/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/home/model/DmsPointCotent.kt index 71368be15..151182c6e 100644 --- a/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/home/model/DmsPointCotent.kt +++ b/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/home/model/DmsPointCotent.kt @@ -25,7 +25,7 @@ import team.aliens.dms.android.core.designsystem.foundation.DmsIcon @Composable fun DmsPointContent( modifier: Modifier = Modifier, - plusPoint: Int, + bonusPoint: Int, minusPoint: Int, ) { Column( @@ -49,7 +49,7 @@ fun DmsPointContent( buttonColor = DmsTheme.colorScheme.onSurface, icon = DmsIcon.Equal, title = "총점", - point = plusPoint - minusPoint, + point = bonusPoint - minusPoint, ) PointItem( modifier = Modifier.padding(top = 12.dp), @@ -57,7 +57,7 @@ fun DmsPointContent( buttonColor = DmsTheme.colorScheme.primary, icon = DmsIcon.Plus, title = "상점", - point = plusPoint, + point = bonusPoint, ) PointItem( modifier = Modifier.padding(top = 12.dp), diff --git a/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/home/ui/HomeScreen.kt b/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/home/ui/HomeScreen.kt index ed78a6b77..2c540a99c 100644 --- a/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/home/ui/HomeScreen.kt +++ b/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/home/ui/HomeScreen.kt @@ -6,6 +6,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.foundation.layout.systemBarsPadding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll @@ -56,6 +57,14 @@ internal fun Home( DmsSnackBarType.SUCCESS, "개발중인 기능이에요", ) + is HomeSideEffect.FailFetchMyPage -> onShowSnackBar( + DmsSnackBarType.ERROR, + effect.message, + ) + is HomeSideEffect.FailFetchLatestNotice -> onShowSnackBar( + DmsSnackBarType.ERROR, + effect.message, + ) } } } @@ -85,8 +94,7 @@ private fun HomeScreen( modifier = Modifier .fillMaxSize() .background(DmsTheme.colorScheme.background) - .systemBarsPadding() - .navigationBarsPadding(), + .statusBarsPadding(), ) { HomeTopAppBar( onOutingPassClick = onOutingPassClick, @@ -102,7 +110,8 @@ private fun HomeScreen( modifier = Modifier .fillMaxWidth() .padding(start = 4.dp), - onClick = { onNavigateNoticeDetail(state.noticeId) }, + title = state.latestNotice.title, + onClick = { onNavigateNoticeDetail(state.latestNotice.id) }, ) MealContent( modifier = Modifier @@ -115,7 +124,7 @@ private fun HomeScreen( modifier = Modifier .fillMaxWidth() .padding(top = 20.dp), - plusPoint = state.myPage.bonusPoint, + bonusPoint = state.myPage.bonusPoint, minusPoint = state.myPage.minusPoint, ) DmsItemButton( diff --git a/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/home/viewmodel/HomeViewModel.kt b/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/home/viewmodel/HomeViewModel.kt index 94fdbeca9..1e6108b88 100644 --- a/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/home/viewmodel/HomeViewModel.kt +++ b/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/home/viewmodel/HomeViewModel.kt @@ -5,6 +5,8 @@ import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import team.aliens.dms.android.core.ui.viewmodel.BaseStateViewModel +import team.aliens.dms.android.data.notice.model.LatestNotice +import team.aliens.dms.android.data.notice.repository.NoticeRepository import team.aliens.dms.android.data.student.model.MyPage import team.aliens.dms.android.data.student.repository.StudentRepository import java.util.UUID @@ -13,10 +15,12 @@ import javax.inject.Inject @HiltViewModel internal class HomeViewModel @Inject constructor( val studentRepository: StudentRepository, + val noticeRepository: NoticeRepository ) : BaseStateViewModel(HomeState()) { init { getMyPage() + getLatestNotice() } private fun getMyPage() { @@ -24,7 +28,19 @@ internal class HomeViewModel @Inject constructor( studentRepository.fetchMyPage().onSuccess { myPage -> setState { it.copy(myPage = myPage) } }.onFailure { - throw it + sendEffect(HomeSideEffect.FailFetchMyPage("내 정보 조회를 실패했어요")) + } + } + } + + private fun getLatestNotice() { + viewModelScope.launch(Dispatchers.IO) { + noticeRepository.fetchLatestNotice().onSuccess { latestNotice -> + setState { + it.copy(latestNotice = latestNotice) + } + }.onFailure { + sendEffect(HomeSideEffect.FailFetchLatestNotice("공지를 가져오는데 실패했어요")) } } } @@ -35,11 +51,14 @@ internal class HomeViewModel @Inject constructor( } internal data class HomeState( - val newNoticesExist: Boolean = false, val myPage: MyPage = MyPage(), - val noticeId: UUID? = null + val latestNotice: LatestNotice = LatestNotice(), ) internal sealed interface HomeSideEffect { data object ShowOutingPassDialog : HomeSideEffect + + data class FailFetchLatestNotice(val message: String) : HomeSideEffect + + data class FailFetchMyPage(val message: String) : HomeSideEffect } diff --git a/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/mypage/ui/MyPageScreen.kt b/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/mypage/ui/MyPageScreen.kt index f6ee823bb..e29aff389 100644 --- a/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/mypage/ui/MyPageScreen.kt +++ b/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/mypage/ui/MyPageScreen.kt @@ -101,7 +101,7 @@ private fun MyPageScreen( ) DmsPointContent( modifier = Modifier, - plusPoint = state.myPage.bonusPoint, + bonusPoint = state.myPage.bonusPoint, minusPoint = state.myPage.minusPoint, ) DmsItemButton( diff --git a/feature/src/dev/kotlin/team/aliens/dms/android/feature/notice/ui/NoticeDetailScreen.kt b/feature/src/dev/kotlin/team/aliens/dms/android/feature/notice/ui/NoticeDetailScreen.kt index aba82e61c..96fdba7c3 100644 --- a/feature/src/dev/kotlin/team/aliens/dms/android/feature/notice/ui/NoticeDetailScreen.kt +++ b/feature/src/dev/kotlin/team/aliens/dms/android/feature/notice/ui/NoticeDetailScreen.kt @@ -9,6 +9,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.systemBarsPadding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -105,7 +106,7 @@ private fun NotificationDetailContent( .horizontalPadding(10.dp) .background(color = DmsTheme.colorScheme.surfaceTint, shape = RoundedCornerShape(32.dp)) .padding(vertical = 24.dp, horizontal = 32.dp) - .horizontalScroll(rememberScrollState()), + .verticalScroll(rememberScrollState()), ) { Text( text = notice.title, diff --git a/feature/src/dev/kotlin/team/aliens/dms/android/feature/notification/navigation/NotificationRoute.kt b/feature/src/dev/kotlin/team/aliens/dms/android/feature/notification/navigation/NotificationRoute.kt index 7064b9fb8..ac1ee1899 100644 --- a/feature/src/dev/kotlin/team/aliens/dms/android/feature/notification/navigation/NotificationRoute.kt +++ b/feature/src/dev/kotlin/team/aliens/dms/android/feature/notification/navigation/NotificationRoute.kt @@ -3,7 +3,7 @@ package team.aliens.dms.android.feature.notification.navigation import androidx.compose.runtime.Composable import team.aliens.dms.android.core.designsystem.snackbar.DmsSnackBarType import team.aliens.dms.android.data.point.model.PointType -import team.aliens.dms.android.feature.notification.ui.NotificationScreen +import team.aliens.dms.android.feature.notification.ui.Notification import java.util.UUID @Composable @@ -13,10 +13,10 @@ fun NotificationRoute( onNavigatePointHistory: (PointType) -> Unit, onShowSnackBar: (DmsSnackBarType, String) -> Unit, ) { - NotificationScreen( + Notification( onBackClick = onBackClick, onNavigateNotificationDetailClick = onNavigateNotificationDetailClick, onNavigatePointHistory = onNavigatePointHistory, - onShowSnackBar = onShowSnackBar + onShowSnackBar = onShowSnackBar, ) } diff --git a/feature/src/dev/kotlin/team/aliens/dms/android/feature/notification/ui/NotificationScreen.kt b/feature/src/dev/kotlin/team/aliens/dms/android/feature/notification/ui/NotificationScreen.kt index 635d1a722..0dbaf980b 100644 --- a/feature/src/dev/kotlin/team/aliens/dms/android/feature/notification/ui/NotificationScreen.kt +++ b/feature/src/dev/kotlin/team/aliens/dms/android/feature/notification/ui/NotificationScreen.kt @@ -44,6 +44,7 @@ import team.aliens.dms.android.core.designsystem.tab.DmsTabRow import team.aliens.dms.android.core.designsystem.topPadding import team.aliens.dms.android.core.designsystem.util.clickable import team.aliens.dms.android.data.point.model.PointType +import team.aliens.dms.android.feature.notification.ui.component.NoticeItem import team.aliens.dms.android.feature.notification.viewmodel.NotificationSideEffect import team.aliens.dms.android.feature.notification.viewmodel.NotificationViewModel import team.aliens.dms.android.feature.notification.viewmodel.NotificationState @@ -51,7 +52,7 @@ import team.aliens.dms.android.feature.notification.viewmodel.NotificationUi import java.util.UUID @Composable -internal fun NotificationScreen( +internal fun Notification( onBackClick: () -> Unit, onNavigateNotificationDetailClick: (UUID) -> Unit, onNavigatePointHistory: (PointType) -> Unit, @@ -83,7 +84,7 @@ internal fun NotificationScreen( } } - NotificationScreenContent( + NotificationScreen( state = state, tabData = tabData.toPersistentList(), pagerState = pagerState, @@ -106,7 +107,7 @@ internal fun NotificationScreen( } @Composable -private fun NotificationScreenContent( +private fun NotificationScreen( state: NotificationState, tabData: ImmutableList, pagerState: PagerState, @@ -185,7 +186,7 @@ private fun NoticeItems( onNotificationDetailClick: (UUID, UUID) -> Unit, ) { LazyColumn( - modifier = modifier.fillMaxWidth(), + modifier = modifier.fillMaxSize(), ) { items( items = notices, @@ -256,63 +257,3 @@ internal fun NotificationItem( ) } } - - -@Composable -internal fun NoticeItem( - modifier: Modifier = Modifier, - notice: NotificationUi, - onNotificationDetailClick: (UUID, UUID) -> Unit, -) { - Row( - modifier = modifier - .fillMaxWidth() - .clickable(onClick = { onNotificationDetailClick(notice.linkId, notice.id) }) - .padding(horizontal = 24.dp, vertical = 22.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - Icon( - painter = painterResource(DmsIcon.Notice), - tint = DmsTheme.colorScheme.scrim, - contentDescription = null, - ) - Column( - modifier = modifier.startPadding(12.dp), - ) { - Text( - text = notice.title, - style = DmsTheme.typography.bodyM, - ) - Row( - modifier = modifier.topPadding(6.dp) - ) { - if (!notice.isRead) { - Icon( - modifier = modifier.size(4.dp), - imageVector = Icons.Filled.Circle, - contentDescription = null, - tint = DmsTheme.colorScheme.primaryContainer, - ) - } - Text( - modifier = modifier - .startPadding(4.dp), - text = notice.content, - style = DmsTheme.typography.labelM, - ) - } - } - Spacer(modifier = modifier.weight(1f)) - Text( - modifier = Modifier.padding(horizontal = 10.dp), - text = notice.elapsedText, - style = DmsTheme.typography.bodyM, - color = DmsTheme.colorScheme.inverseSurface, - ) - Icon( - painter = painterResource(DmsIcon.Forward), - tint = DmsTheme.colorScheme.scrim, - contentDescription = null, - ) - } -} diff --git a/feature/src/dev/kotlin/team/aliens/dms/android/feature/notification/ui/component/NoticeItem.kt b/feature/src/dev/kotlin/team/aliens/dms/android/feature/notification/ui/component/NoticeItem.kt new file mode 100644 index 000000000..05aaaef6d --- /dev/null +++ b/feature/src/dev/kotlin/team/aliens/dms/android/feature/notification/ui/component/NoticeItem.kt @@ -0,0 +1,89 @@ +package team.aliens.dms.android.feature.notification.ui.component + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Circle +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +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.unit.dp +import team.aliens.dms.android.core.designsystem.DmsTheme +import team.aliens.dms.android.core.designsystem.bodyM +import team.aliens.dms.android.core.designsystem.foundation.DmsIcon +import team.aliens.dms.android.core.designsystem.labelM +import team.aliens.dms.android.core.designsystem.startPadding +import team.aliens.dms.android.core.designsystem.topPadding +import team.aliens.dms.android.core.designsystem.util.clickable +import team.aliens.dms.android.feature.notification.viewmodel.NotificationUi +import java.util.UUID + +@Composable +internal fun NoticeItem( + modifier: Modifier = Modifier, + notice: NotificationUi, + onNotificationDetailClick: (UUID, UUID) -> Unit, +) { + Column( + modifier = modifier + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .clickable(onClick = { onNotificationDetailClick(notice.linkId, notice.id) }) + .padding(horizontal = 24.dp, vertical = 22.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + painter = painterResource(DmsIcon.Notice), + tint = DmsTheme.colorScheme.scrim, + contentDescription = null, + ) + Column( + modifier = Modifier.startPadding(12.dp), + ) { + Text( + text = notice.title, + style = DmsTheme.typography.bodyM, + ) + Row( + modifier = Modifier.topPadding(6.dp) + ) { + if (!notice.isRead) { + Icon( + modifier = Modifier.size(4.dp), + imageVector = Icons.Filled.Circle, + contentDescription = null, + tint = DmsTheme.colorScheme.primaryContainer, + ) + } + Text( + modifier = Modifier + .startPadding(4.dp), + text = notice.content, + style = DmsTheme.typography.labelM, + ) + } + } + Spacer(modifier = Modifier.weight(1f)) + Text( + modifier = Modifier.padding(horizontal = 10.dp), + text = notice.elapsedText, + style = DmsTheme.typography.bodyM, + color = DmsTheme.colorScheme.inverseSurface, + ) + Icon( + painter = painterResource(DmsIcon.Forward), + tint = DmsTheme.colorScheme.scrim, + contentDescription = null, + ) + } + } +} diff --git a/feature/src/dev/kotlin/team/aliens/dms/android/feature/point/ui/PointHistoryScreen.kt b/feature/src/dev/kotlin/team/aliens/dms/android/feature/point/ui/PointHistoryScreen.kt index 26d4c95de..8ff542d4b 100644 --- a/feature/src/dev/kotlin/team/aliens/dms/android/feature/point/ui/PointHistoryScreen.kt +++ b/feature/src/dev/kotlin/team/aliens/dms/android/feature/point/ui/PointHistoryScreen.kt @@ -62,7 +62,7 @@ private fun PointHistoryScreen( ) val tabData = listOf( PointTab.All, - PointTab.BONUS, + PointTab.Bonus, PointTab.Minus, ) val pagerState = rememberPagerState( @@ -99,7 +99,7 @@ private fun PointHistoryScreen( val pointHistoryList = remember(page, state.allPointList) { when (tabData[page]) { PointTab.All -> state.allPointList - PointTab.BONUS -> state.plusPointList + PointTab.Bonus -> state.bonusPointList PointTab.Minus -> state.minusPointList } } @@ -129,6 +129,6 @@ internal sealed class PointTab( val pointType: PointType, ) { data object All : PointTab(title = "전체", pointType = PointType.ALL) - data object BONUS : PointTab(title = "상점", pointType = PointType.BONUS) + data object Bonus : PointTab(title = "상점", pointType = PointType.BONUS) data object Minus : PointTab(title = "벌점", pointType = PointType.MINUS) } diff --git a/feature/src/dev/kotlin/team/aliens/dms/android/feature/point/viewmodel/PointHistoryViewModel.kt b/feature/src/dev/kotlin/team/aliens/dms/android/feature/point/viewmodel/PointHistoryViewModel.kt index ec6240fef..31b9e58e2 100644 --- a/feature/src/dev/kotlin/team/aliens/dms/android/feature/point/viewmodel/PointHistoryViewModel.kt +++ b/feature/src/dev/kotlin/team/aliens/dms/android/feature/point/viewmodel/PointHistoryViewModel.kt @@ -32,7 +32,7 @@ internal class PointHistoryViewModel @Inject constructor( setState { it.copy( allPointList = pointHistories, - plusPointList = bonusPoints, + bonusPointList = bonusPoints, minusPointList = minusPoints, ) } @@ -47,7 +47,7 @@ internal class PointHistoryViewModel @Inject constructor( internal data class PointHistoryState( val allPointList: List = emptyList(), - val plusPointList: List = emptyList(), + val bonusPointList: List = emptyList(), val minusPointList: List = emptyList(), val initialTab: Int = 0, ) diff --git a/feature/src/dev/kotlin/team/aliens/dms/android/feature/resetpassword/viewmodel/ResetPasswordViewModel.kt b/feature/src/dev/kotlin/team/aliens/dms/android/feature/resetpassword/viewmodel/ResetPasswordViewModel.kt index 6fe40c90a..6c063cfb6 100644 --- a/feature/src/dev/kotlin/team/aliens/dms/android/feature/resetpassword/viewmodel/ResetPasswordViewModel.kt +++ b/feature/src/dev/kotlin/team/aliens/dms/android/feature/resetpassword/viewmodel/ResetPasswordViewModel.kt @@ -10,7 +10,7 @@ import javax.inject.Inject @HiltViewModel class ResetPasswordViewModel @Inject constructor( - private val userRepository: UserRepository + private val userRepository: UserRepository, ): BaseStateViewModel(ResetPasswordState()) { internal fun setNewPassword(password: String) { diff --git a/feature/src/dev/kotlin/team/aliens/dms/android/feature/setting/ui/SettingScreen.kt b/feature/src/dev/kotlin/team/aliens/dms/android/feature/setting/ui/SettingScreen.kt index 5f76b40a5..61fc97c1e 100644 --- a/feature/src/dev/kotlin/team/aliens/dms/android/feature/setting/ui/SettingScreen.kt +++ b/feature/src/dev/kotlin/team/aliens/dms/android/feature/setting/ui/SettingScreen.kt @@ -27,8 +27,6 @@ import team.aliens.dms.android.core.designsystem.button.DmsItemButton import team.aliens.dms.android.core.designsystem.dialog.AlertDialog import team.aliens.dms.android.core.designsystem.sTitleM import team.aliens.dms.android.core.designsystem.snackbar.DmsSnackBarType -import team.aliens.dms.android.core.designsystem.titleB -import team.aliens.dms.android.core.designsystem.titleM import team.aliens.dms.android.feature.setting.ui.component.SettingRotateContent import team.aliens.dms.android.feature.setting.viewmodel.SettingSideEffect import team.aliens.dms.android.feature.setting.viewmodel.SettingViewModel @@ -46,14 +44,19 @@ internal fun Setting( val (shouldShowSignOutDialog, onShouldShowSignOutDialogChange) = remember { mutableStateOf(false) } + val (shouldShowWithdrawDialog, onShouldShowWithdrawDialogChange) = remember { + mutableStateOf(false) + } LaunchedEffect(Unit) { viewModel.sideEffect.collect { when (it) { - SettingSideEffect.CannotFetchNotificationStatus -> { + is SettingSideEffect.CannotFetchNotificationStatus -> onShowSnackBar(DmsSnackBarType.ERROR, "알림 상태 조회를 실패했어요") - } - SettingSideEffect.SignOutSuccess -> onNavigateSignIn() + is SettingSideEffect.SignOutSuccess -> onNavigateSignIn() + is SettingSideEffect.WithdrawSuccess -> onNavigateSignIn() + is SettingSideEffect.WithdrawFailed -> + onShowSnackBar(DmsSnackBarType.ERROR, "회원 탈퇴에 실패했어요") } } } @@ -82,12 +85,37 @@ internal fun Setting( ) } + if (shouldShowWithdrawDialog) { + AlertDialog( + title = { Text(text = "회원 탈퇴", style = DmsTheme.typography.sTitleM) }, + text = { Text(text = "회원 탈퇴 시 복구할 수 없습니다. 정말 탈퇴하시겠습니까?", style = DmsTheme.typography.bodyM) }, + onDismissRequest = { onShouldShowWithdrawDialogChange(false) }, + confirmButton = { + DmsButton( + text = "확인", + buttonType = ButtonType.Text, + buttonColor = ButtonColor.Primary, + onClick = viewModel::withdraw, + ) + }, + dismissButton = { + DmsButton( + text = "취소", + buttonType = ButtonType.Text, + buttonColor = ButtonColor.Primary, + onClick = { onShouldShowWithdrawDialogChange(false) }, + ) + }, + ) + } + SettingScreen( rotated = state.isOnNotification, onNavigateResetPassword = onNavigateResetPassword, onNavigateSelectProfile = onNavigateSelectProfile, onNotificationClick = { viewModel.updateNotificationStatus(state.isOnNotification) }, onShowSignOutDialogChange = { onShouldShowSignOutDialogChange(true) }, + onShowWithdrawDialogChange = { onShouldShowWithdrawDialogChange(true) }, onBackPressed = onBackPressed, ) } @@ -99,6 +127,7 @@ private fun SettingScreen( onNavigateSelectProfile: () -> Unit, onNotificationClick: () -> Unit, onShowSignOutDialogChange: () -> Unit, + onShowWithdrawDialogChange: () -> Unit, onBackPressed: () -> Unit, ) { Column( @@ -137,6 +166,11 @@ private fun SettingScreen( text = "로그아웃", onClick = onShowSignOutDialogChange, ) + DmsItemButton( + iconRes = R.drawable.img_3d_out, + text = "회원 탈퇴", + onClick = onShowWithdrawDialogChange, + ) } } } diff --git a/feature/src/dev/kotlin/team/aliens/dms/android/feature/setting/viewmodel/SettingViewModel.kt b/feature/src/dev/kotlin/team/aliens/dms/android/feature/setting/viewmodel/SettingViewModel.kt index 7a54eb85d..7da6bae6b 100644 --- a/feature/src/dev/kotlin/team/aliens/dms/android/feature/setting/viewmodel/SettingViewModel.kt +++ b/feature/src/dev/kotlin/team/aliens/dms/android/feature/setting/viewmodel/SettingViewModel.kt @@ -8,6 +8,7 @@ import team.aliens.dms.android.core.device.datastore.DeviceDataStoreDataSource import team.aliens.dms.android.core.ui.viewmodel.BaseStateViewModel import team.aliens.dms.android.data.auth.repository.AuthRepository import team.aliens.dms.android.data.notification.model.NotificationTopicGroup +import team.aliens.dms.android.data.student.repository.StudentRepository import team.aliens.dms.android.data.notification.repository.NotificationRepository import javax.inject.Inject @@ -16,6 +17,7 @@ class SettingViewModel @Inject constructor( val notificationRepository: NotificationRepository, val authRepository: AuthRepository, val deviceDataStoreDataSource: DeviceDataStoreDataSource, + val studentRepository: StudentRepository, ): BaseStateViewModel(SettingState()) { init { @@ -43,6 +45,14 @@ class SettingViewModel @Inject constructor( } } + internal fun withdraw() { + viewModelScope.launch(Dispatchers.IO) { + studentRepository.withdraw() + .onSuccess { sendEffect(SettingSideEffect.WithdrawSuccess) } + .onFailure { sendEffect(SettingSideEffect.WithdrawFailed) } + } + } + private fun fetchNotificationStatus() { viewModelScope.launch { val deviceToken = uiState.value.deviceToken ?: return@launch @@ -80,4 +90,6 @@ data class SettingState( sealed class SettingSideEffect { object CannotFetchNotificationStatus : SettingSideEffect() object SignOutSuccess : SettingSideEffect() + object WithdrawSuccess : SettingSideEffect() + object WithdrawFailed : SettingSideEffect() } diff --git a/network/src/dev/kotlin/team.aliens.dms.android.network/notice/apiservice/NoticeApiService.kt b/network/src/dev/kotlin/team.aliens.dms.android.network/notice/apiservice/NoticeApiService.kt index c4267c523..99c51114b 100644 --- a/network/src/dev/kotlin/team.aliens.dms.android.network/notice/apiservice/NoticeApiService.kt +++ b/network/src/dev/kotlin/team.aliens.dms.android.network/notice/apiservice/NoticeApiService.kt @@ -4,6 +4,7 @@ import retrofit2.http.GET import retrofit2.http.Path import retrofit2.http.Query import team.aliens.dms.android.core.jwt.RequiresAccessToken +import team.aliens.dms.android.network.notice.model.FetchLatestNoticeResponse import team.aliens.dms.android.network.notice.model.FetchNoticeDetailsResponse import team.aliens.dms.android.network.notice.model.FetchNoticesResponse import team.aliens.dms.android.network.notice.model.FetchWhetherNewNoticesExistResponse @@ -22,4 +23,8 @@ internal interface NoticeApiService { @GET("/notices") @RequiresAccessToken suspend fun fetchNotices(@Query("order") order: String): FetchNoticesResponse + + @GET("/notices/latest") + @RequiresAccessToken + suspend fun fetchLatestNotice(): FetchLatestNoticeResponse } diff --git a/network/src/dev/kotlin/team.aliens.dms.android.network/notice/datasource/NetworkNoticeDataSource.kt b/network/src/dev/kotlin/team.aliens.dms.android.network/notice/datasource/NetworkNoticeDataSource.kt index fe5133329..f114c7f16 100644 --- a/network/src/dev/kotlin/team.aliens.dms.android.network/notice/datasource/NetworkNoticeDataSource.kt +++ b/network/src/dev/kotlin/team.aliens.dms.android.network/notice/datasource/NetworkNoticeDataSource.kt @@ -1,5 +1,6 @@ package team.aliens.dms.android.network.notice.datasource +import team.aliens.dms.android.network.notice.model.FetchLatestNoticeResponse import team.aliens.dms.android.network.notice.model.FetchNoticeDetailsResponse import team.aliens.dms.android.network.notice.model.FetchNoticesResponse import team.aliens.dms.android.network.notice.model.FetchWhetherNewNoticesExistResponse @@ -12,4 +13,6 @@ abstract class NetworkNoticeDataSource { abstract suspend fun fetchNotices(order: String): Result abstract suspend fun fetchNoticeDetails(noticeId: UUID): Result + + abstract suspend fun fetchLatestNotice(): Result } diff --git a/network/src/dev/kotlin/team.aliens.dms.android.network/notice/datasource/NetworkNoticeDataSourceImpl.kt b/network/src/dev/kotlin/team.aliens.dms.android.network/notice/datasource/NetworkNoticeDataSourceImpl.kt index c43e4120b..a4916b1a0 100644 --- a/network/src/dev/kotlin/team.aliens.dms.android.network/notice/datasource/NetworkNoticeDataSourceImpl.kt +++ b/network/src/dev/kotlin/team.aliens.dms.android.network/notice/datasource/NetworkNoticeDataSourceImpl.kt @@ -1,5 +1,6 @@ package team.aliens.dms.android.network.notice.datasource import team.aliens.dms.android.network.notice.apiservice.NoticeApiService +import team.aliens.dms.android.network.notice.model.FetchLatestNoticeResponse import team.aliens.dms.android.network.notice.model.FetchNoticeDetailsResponse import team.aliens.dms.android.network.notice.model.FetchNoticesResponse import team.aliens.dms.android.network.notice.model.FetchWhetherNewNoticesExistResponse @@ -18,4 +19,7 @@ internal class NetworkNoticeDataSourceImpl @Inject constructor( override suspend fun fetchNoticeDetails(noticeId: UUID): Result = suspendRunCatching { noticeApiService.fetchNoticeDetails(noticeId) } + + override suspend fun fetchLatestNotice(): Result = + suspendRunCatching { noticeApiService.fetchLatestNotice() } } diff --git a/network/src/dev/kotlin/team.aliens.dms.android.network/notice/model/FetchLatestNoticeResponse.kt b/network/src/dev/kotlin/team.aliens.dms.android.network/notice/model/FetchLatestNoticeResponse.kt new file mode 100644 index 000000000..2cad70e7c --- /dev/null +++ b/network/src/dev/kotlin/team.aliens.dms.android.network/notice/model/FetchLatestNoticeResponse.kt @@ -0,0 +1,9 @@ +package team.aliens.dms.android.network.notice.model + +import com.google.gson.annotations.SerializedName +import java.util.UUID + +data class FetchLatestNoticeResponse( + @SerializedName("id") val id: UUID, + @SerializedName("title") val title: String, +) diff --git a/network/src/dev/kotlin/team.aliens.dms.android.network/notification/model/NotificationTopicGroup.kt b/network/src/dev/kotlin/team.aliens.dms.android.network/notification/model/NotificationTopicGroup.kt deleted file mode 100644 index c144aafe5..000000000 --- a/network/src/dev/kotlin/team.aliens.dms.android.network/notification/model/NotificationTopicGroup.kt +++ /dev/null @@ -1,6 +0,0 @@ -package team.aliens.dms.android.network.notification.model - -enum class NotificationTopicGroup { - NOTICE, STUDY_ROOM, - ; -}