Skip to content

Commit 207b101

Browse files
authored
Merge pull request #192 from YAPP-Github/BOOK-355-feature/#191
feat: 알림 권한 요청 및 설정 연동 구현
2 parents 3d7bc4e + c0a83f1 commit 207b101

File tree

14 files changed

+198
-38
lines changed

14 files changed

+198
-38
lines changed

app/src/main/AndroidManifest.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
<uses-permission android:name="android.permission.INTERNET" />
1010
<uses-permission android:name="android.permission.CAMERA" />
11+
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
1112

1213
<application
1314
android:name=".BooketApplication"

core/data/api/src/main/kotlin/com/ninecraft/booket/core/data/api/repository/UserRepository.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,8 @@ interface UserRepository {
1313
val onboardingState: Flow<OnboardingState>
1414

1515
suspend fun setOnboardingCompleted(isCompleted: Boolean)
16+
17+
val isNotificationEnabled: Flow<Boolean>
18+
19+
suspend fun setNotificationEnabled(isEnabled: Boolean)
1620
}

core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/repository/DefaultUserRepository.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package com.ninecraft.booket.core.data.impl.repository
33
import com.ninecraft.booket.core.common.utils.runSuspendCatching
44
import com.ninecraft.booket.core.data.api.repository.UserRepository
55
import com.ninecraft.booket.core.data.impl.mapper.toModel
6+
import com.ninecraft.booket.core.datastore.api.datasource.NotificationDataSource
67
import com.ninecraft.booket.core.datastore.api.datasource.OnboardingDataSource
78
import com.ninecraft.booket.core.network.request.TermsAgreementRequest
89
import com.ninecraft.booket.core.network.service.ReedService
@@ -11,6 +12,7 @@ import javax.inject.Inject
1112
internal class DefaultUserRepository @Inject constructor(
1213
private val service: ReedService,
1314
private val onboardingDataSource: OnboardingDataSource,
15+
private val notificationDataSource: NotificationDataSource,
1416
) : UserRepository {
1517
override suspend fun agreeTerms(termsAgreed: Boolean) = runSuspendCatching {
1618
service.agreeTerms(TermsAgreementRequest(termsAgreed)).toModel()
@@ -25,4 +27,10 @@ internal class DefaultUserRepository @Inject constructor(
2527
override suspend fun setOnboardingCompleted(isCompleted: Boolean) {
2628
onboardingDataSource.setOnboardingCompleted(isCompleted)
2729
}
30+
31+
override val isNotificationEnabled = notificationDataSource.isNotificationEnabled
32+
33+
override suspend fun setNotificationEnabled(isEnabled: Boolean) {
34+
notificationDataSource.setNotificationEnabled(isEnabled)
35+
}
2836
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.ninecraft.booket.core.datastore.api.datasource
2+
3+
import kotlinx.coroutines.flow.Flow
4+
5+
interface NotificationDataSource {
6+
val isNotificationEnabled: Flow<Boolean>
7+
suspend fun setNotificationEnabled(isEnabled: Boolean)
8+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package com.ninecraft.booket.core.datastore.impl.datasource
2+
3+
import androidx.datastore.core.DataStore
4+
import androidx.datastore.preferences.core.Preferences
5+
import androidx.datastore.preferences.core.booleanPreferencesKey
6+
import androidx.datastore.preferences.core.edit
7+
import com.ninecraft.booket.core.datastore.api.datasource.NotificationDataSource
8+
import com.ninecraft.booket.core.datastore.impl.di.NotificationDataStore
9+
import com.ninecraft.booket.core.datastore.impl.util.handleIOException
10+
import kotlinx.coroutines.flow.Flow
11+
import kotlinx.coroutines.flow.map
12+
import javax.inject.Inject
13+
14+
class DefaultNotificationDataSource @Inject constructor(
15+
@NotificationDataStore private val dataStore: DataStore<Preferences>,
16+
) : NotificationDataSource {
17+
override val isNotificationEnabled: Flow<Boolean> = dataStore.data
18+
.handleIOException()
19+
.map { prefs ->
20+
prefs[NOTIFICATION_ENABLED] ?: true
21+
}
22+
23+
override suspend fun setNotificationEnabled(isEnabled: Boolean) {
24+
dataStore.edit { prefs ->
25+
prefs[NOTIFICATION_ENABLED] = isEnabled
26+
}
27+
}
28+
29+
companion object Companion {
30+
private val NOTIFICATION_ENABLED = booleanPreferencesKey("NOTIFICATION_ENABLED")
31+
}
32+
}

core/datastore/impl/src/main/kotlin/com/ninecraft/booket/core/datastore/impl/di/DataStoreModule.kt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@ import androidx.datastore.preferences.core.Preferences
66
import androidx.datastore.preferences.preferencesDataStore
77
import com.ninecraft.booket.core.datastore.api.datasource.BookRecentSearchDataSource
88
import com.ninecraft.booket.core.datastore.api.datasource.LibraryRecentSearchDataSource
9+
import com.ninecraft.booket.core.datastore.api.datasource.NotificationDataSource
910
import com.ninecraft.booket.core.datastore.api.datasource.OnboardingDataSource
1011
import com.ninecraft.booket.core.datastore.api.datasource.TokenDataSource
1112
import com.ninecraft.booket.core.datastore.impl.datasource.DefaultLibraryRecentSearchDataSource
1213
import com.ninecraft.booket.core.datastore.impl.datasource.DefaultOnboardingDataSource
1314
import com.ninecraft.booket.core.datastore.impl.datasource.DefaultBookRecentSearchDataSource
15+
import com.ninecraft.booket.core.datastore.impl.datasource.DefaultNotificationDataSource
1416
import com.ninecraft.booket.core.datastore.impl.datasource.DefaultTokenDataSource
1517
import dagger.Binds
1618
import dagger.Module
@@ -27,11 +29,13 @@ object DataStoreModule {
2729
private const val BOOK_RECENT_SEARCH_DATASTORE_NAME = "BOOK_RECENT_SEARCH_DATASTORE"
2830
private const val LIBRARY_RECENT_SEARCH_DATASTORE_NAME = "LIBRARY_RECENT_SEARCH_DATASTORE"
2931
private const val ONBOARDING_DATASTORE_NAME = "ONBOARDING_DATASTORE"
32+
private const val NOTIFICATION_DATASTORE_NAME = "NOTIFICATION_DATASTORE"
3033

3134
private val Context.tokenDataStore by preferencesDataStore(name = TOKEN_DATASTORE_NAME)
3235
private val Context.bookRecentSearchDataStore by preferencesDataStore(name = BOOK_RECENT_SEARCH_DATASTORE_NAME)
3336
private val Context.libraryRecentSearchDataStore by preferencesDataStore(name = LIBRARY_RECENT_SEARCH_DATASTORE_NAME)
3437
private val Context.onboardingDataStore by preferencesDataStore(name = ONBOARDING_DATASTORE_NAME)
38+
private val Context.notificationDataStore by preferencesDataStore(name = NOTIFICATION_DATASTORE_NAME)
3539

3640
@TokenDataStore
3741
@Provides
@@ -60,6 +64,13 @@ object DataStoreModule {
6064
fun provideOnboardingDataStore(
6165
@ApplicationContext context: Context,
6266
): DataStore<Preferences> = context.onboardingDataStore
67+
68+
@NotificationDataStore
69+
@Provides
70+
@Singleton
71+
fun provideNotificationDataStore(
72+
@ApplicationContext context: Context,
73+
): DataStore<Preferences> = context.notificationDataStore
6374
}
6475

6576
@Module
@@ -89,4 +100,10 @@ abstract class DataStoreBindModule {
89100
abstract fun bindOnboardingDataSource(
90101
defaultOnboardingDataSource: DefaultOnboardingDataSource,
91102
): OnboardingDataSource
103+
104+
@Binds
105+
@Singleton
106+
abstract fun bindNotificationDataSource(
107+
defaultNotificationDataSource: DefaultNotificationDataSource,
108+
): NotificationDataSource
92109
}

core/datastore/impl/src/main/kotlin/com/ninecraft/booket/core/datastore/impl/di/DataStoreQualifier.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,7 @@ annotation class LibraryRecentSearchDataStore
1717
@Qualifier
1818
@Retention(AnnotationRetention.BINARY)
1919
annotation class OnboardingDataStore
20+
21+
@Qualifier
22+
@Retention(AnnotationRetention.BINARY)
23+
annotation class NotificationDataStore

feature/home/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ dependencies {
1818
implementations(
1919
libs.logger,
2020

21+
libs.androidx.activity.compose,
2122
libs.lottie.compose,
2223
)
2324
}

feature/home/src/main/kotlin/com/ninecraft/booket/feature/home/HomePresenter.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ import com.ninecraft.booket.core.data.api.repository.BookRepository
1111
import com.ninecraft.booket.core.model.RecentBookModel
1212
import com.ninecraft.booket.core.model.UserState
1313
import com.ninecraft.booket.feature.screens.BookDetailScreen
14+
import com.ninecraft.booket.feature.screens.BookSearchScreen
1415
import com.ninecraft.booket.feature.screens.HomeScreen
1516
import com.ninecraft.booket.feature.screens.RecordScreen
16-
import com.ninecraft.booket.feature.screens.BookSearchScreen
1717
import com.ninecraft.booket.feature.screens.SettingsScreen
1818
import com.skydoves.compose.effects.RememberedEffect
1919
import com.slack.circuit.codegen.annotations.CircuitInject
@@ -89,6 +89,10 @@ class HomePresenter @AssistedInject constructor(
8989
restoreState = true,
9090
)
9191
}
92+
93+
is HomeUiEvent.OnNotificationPermissionResult -> {
94+
// TODO: 서버 동기화, FCM 토큰 전송
95+
}
9296
}
9397
}
9498

feature/home/src/main/kotlin/com/ninecraft/booket/feature/home/HomeUi.kt

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
package com.ninecraft.booket.feature.home
22

3+
import android.content.pm.PackageManager
4+
import android.os.Build
5+
import androidx.activity.compose.rememberLauncherForActivityResult
6+
import androidx.activity.result.contract.ActivityResultContracts
37
import androidx.compose.foundation.background
48
import androidx.compose.foundation.layout.Arrangement
59
import androidx.compose.foundation.layout.Box
@@ -19,10 +23,13 @@ import androidx.compose.foundation.shape.CircleShape
1923
import androidx.compose.foundation.verticalScroll
2024
import androidx.compose.material3.Text
2125
import androidx.compose.runtime.Composable
26+
import androidx.compose.runtime.LaunchedEffect
2227
import androidx.compose.ui.Modifier
2328
import androidx.compose.ui.draw.clip
29+
import androidx.compose.ui.platform.LocalContext
2430
import androidx.compose.ui.res.stringResource
2531
import androidx.compose.ui.unit.dp
32+
import androidx.core.content.ContextCompat
2633
import com.ninecraft.booket.core.designsystem.DevicePreview
2734
import com.ninecraft.booket.core.designsystem.theme.HomeBg
2835
import com.ninecraft.booket.core.designsystem.theme.ReedTheme
@@ -48,6 +55,28 @@ internal fun HomeUi(
4855
) {
4956
HandleHomeSideEffects(state = state)
5057

58+
val context = LocalContext.current
59+
val permissionLauncher = rememberLauncherForActivityResult(
60+
contract = ActivityResultContracts.RequestPermission(),
61+
) { granted ->
62+
state.eventSink(HomeUiEvent.OnNotificationPermissionResult(granted))
63+
}
64+
65+
if (!state.isGuestMode) {
66+
LaunchedEffect(Unit) {
67+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
68+
val permission = android.Manifest.permission.POST_NOTIFICATIONS
69+
val isGranted = ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED
70+
71+
if (!isGranted) {
72+
permissionLauncher.launch(permission)
73+
}
74+
} else {
75+
state.eventSink(HomeUiEvent.OnNotificationPermissionResult(true))
76+
}
77+
}
78+
}
79+
5180
ReedScaffold(
5281
modifier = modifier.fillMaxSize(),
5382
bottomBar = {

0 commit comments

Comments
 (0)