diff --git a/.github/workflows/android-build.yml b/.github/workflows/android-build.yml index 4b4b39b3..985c39e8 100644 --- a/.github/workflows/android-build.yml +++ b/.github/workflows/android-build.yml @@ -32,3 +32,4 @@ jobs: with: webhook: ${{ secrets.DISCORD_WEBHOOK }} status: ${{ job.status }} + content: "<@${{ secrets.DISCORD_NOTIFY_USER_ID }}> チェック終わったみたい ${{ secrets.DISCORD_NOTIFY_SUFFIX }}" diff --git a/AndroidApp/app/src/main/AndroidManifest.xml b/AndroidApp/app/src/main/AndroidManifest.xml index 4011bff2..291d236c 100644 --- a/AndroidApp/app/src/main/AndroidManifest.xml +++ b/AndroidApp/app/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ + DetailViewModel(get(), get(), get(), get(), params.get()) } viewModel { TargetViewModel(get()) } - viewModel { SettingsViewModel(get(), get(), get(), get(), get(), get()) } + viewModel { SettingsViewModel(get(), get(), get(), get(), get(), get(), get()) } // UseCase factory { AddTargetAppUseCaseImpl(get()) } @@ -102,7 +107,7 @@ class App : Application() { factory { LoadAppUseCaseImpl(get(), get()) } factory { LoadFilterConditionUseCaseImpl(get()) } factory { - NotifyTargetAppNotificationUseCaseImpl(get(), get()) + NotifyTargetAppNotificationUseCaseImpl(get(), get(), get(), get()) } factory { NotifyUseCaseImpl(get()) } factory { PackageVisibilityGrantedUseCaseImpl(get()) } @@ -110,5 +115,10 @@ class App : Application() { factory { SaveAddressUseCaseImpl(get()) } factory { SaveFilterConditionUseCaseImpl(get()) } factory { ToggleIgnoreSummaryUseCaseImpl(get()) } + factory { + SaveWifiOnlyNotificationSettingUseCaseImpl( + get() + ) + } } } \ No newline at end of file diff --git a/AndroidApp/data/repository/src/main/kotlin/me/nya_n/notificationnotifier/data/repository/source/UserSettingsDataStore.kt b/AndroidApp/data/repository/src/main/kotlin/me/nya_n/notificationnotifier/data/repository/source/UserSettingsDataStore.kt index e150b741..544fc043 100644 --- a/AndroidApp/data/repository/src/main/kotlin/me/nya_n/notificationnotifier/data/repository/source/UserSettingsDataStore.kt +++ b/AndroidApp/data/repository/src/main/kotlin/me/nya_n/notificationnotifier/data/repository/source/UserSettingsDataStore.kt @@ -6,12 +6,24 @@ import me.nya_n.notificationnotifier.model.UserSettings class UserSettingsDataStore( pref: SharedPreferences ) : KeyValueDataStore(pref) { + companion object { + const val DATA_STORE_NAME = "settings" + const val KEY_HOST = "host" + const val DEFAULT_HOST = "" + const val KEY_PORT = "port" + const val DEFAULT_PORT = -1 + const val KEY_IS_PACKAGE_VISIBILITY_GRANTED = "isPackageVisibilityGranted" + const val DEFAULT_IS_PACKAGE_VISIBILITY_GRANTED = false + const val KEY_IS_WIFI_ONLY_NOTIFICATION_ENABLED = "isWifiOnlyNotificationEnabled" + const val DEFAULT_IS_WIFI_ONLY_NOTIFICATION_ENABLED = false + } fun get(): UserSettings { return UserSettings( get(KEY_HOST, DEFAULT_HOST), get(KEY_PORT, DEFAULT_PORT), - get(KEY_IS_PACKAGE_VISIBILITY_GRANTED, DEFAULT_IS_PACKAGE_VISIBILITY_GRANTED) + get(KEY_IS_PACKAGE_VISIBILITY_GRANTED, DEFAULT_IS_PACKAGE_VISIBILITY_GRANTED), + get(KEY_IS_WIFI_ONLY_NOTIFICATION_ENABLED, DEFAULT_IS_WIFI_ONLY_NOTIFICATION_ENABLED) ) } @@ -19,15 +31,6 @@ class UserSettingsDataStore( put(KEY_HOST, settings.host) put(KEY_PORT, settings.port) put(KEY_IS_PACKAGE_VISIBILITY_GRANTED, settings.isPackageVisibilityGranted) - } - - companion object { - const val DATA_STORE_NAME = "settings" - const val KEY_HOST = "host" - const val DEFAULT_HOST = "" - const val KEY_PORT = "port" - const val DEFAULT_PORT = -1 - const val KEY_IS_PACKAGE_VISIBILITY_GRANTED = "isPackageVisibilityGranted" - const val DEFAULT_IS_PACKAGE_VISIBILITY_GRANTED = false + put(KEY_IS_WIFI_ONLY_NOTIFICATION_ENABLED, settings.isWifiOnlyNotificationEnabled) } } \ No newline at end of file diff --git a/AndroidApp/domain/src/main/kotlin/me/nya_n/notificationnotifier/domain/usecase/SaveWifiOnlyNotificationSettingUseCase.kt b/AndroidApp/domain/src/main/kotlin/me/nya_n/notificationnotifier/domain/usecase/SaveWifiOnlyNotificationSettingUseCase.kt new file mode 100644 index 00000000..2e057d9f --- /dev/null +++ b/AndroidApp/domain/src/main/kotlin/me/nya_n/notificationnotifier/domain/usecase/SaveWifiOnlyNotificationSettingUseCase.kt @@ -0,0 +1,5 @@ +package me.nya_n.notificationnotifier.domain.usecase + +interface SaveWifiOnlyNotificationSettingUseCase { + operator fun invoke(isWifiOnlyNotificationEnabled: Boolean): Result +} \ No newline at end of file diff --git a/AndroidApp/domain/src/main/kotlin/me/nya_n/notificationnotifier/domain/usecase/impl/NotifyTargetAppNotificationUseCaseImpl.kt b/AndroidApp/domain/src/main/kotlin/me/nya_n/notificationnotifier/domain/usecase/impl/NotifyTargetAppNotificationUseCaseImpl.kt index 0230e740..1cfdf6a6 100644 --- a/AndroidApp/domain/src/main/kotlin/me/nya_n/notificationnotifier/domain/usecase/impl/NotifyTargetAppNotificationUseCaseImpl.kt +++ b/AndroidApp/domain/src/main/kotlin/me/nya_n/notificationnotifier/domain/usecase/impl/NotifyTargetAppNotificationUseCaseImpl.kt @@ -1,14 +1,29 @@ package me.nya_n.notificationnotifier.domain.usecase.impl +import android.Manifest import android.app.Notification +import android.net.ConnectivityManager +import android.net.NetworkCapabilities +import androidx.annotation.RequiresPermission import me.nya_n.notificationnotifier.data.repository.AppRepository +import me.nya_n.notificationnotifier.data.repository.UserSettingsRepository import me.nya_n.notificationnotifier.domain.usecase.NotifyTargetAppNotificationUseCase import me.nya_n.notificationnotifier.domain.usecase.NotifyUseCase class NotifyTargetAppNotificationUseCaseImpl( + private val connectionManager: ConnectivityManager, private val appRepository: AppRepository, + private val userSettingsRepository: UserSettingsRepository, private val notifyUseCase: NotifyUseCase, ) : NotifyTargetAppNotificationUseCase { + @get:RequiresPermission(Manifest.permission.ACCESS_NETWORK_STATE) + private val isConnectedToWifi: Boolean + get() { + val network = connectionManager.activeNetwork ?: return false + val capabilities = connectionManager.getNetworkCapabilities(network) ?: return false + return capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) + } + override suspend operator fun invoke( packageName: String, title: String, @@ -16,6 +31,11 @@ class NotifyTargetAppNotificationUseCaseImpl( flags: Int ): Result { return runCatching { + val settings = userSettingsRepository.getUserSettings() + if (settings.isWifiOnlyNotificationEnabled && !isConnectedToWifi) { + return Result.success(Unit) + } + val targets = appRepository.getTargetAppList() if (!targets.any { t -> t.packageName == packageName }) { return Result.success(Unit) diff --git a/AndroidApp/domain/src/main/kotlin/me/nya_n/notificationnotifier/domain/usecase/impl/SaveWifiOnlyNotificationSettingUseCaseImpl.kt b/AndroidApp/domain/src/main/kotlin/me/nya_n/notificationnotifier/domain/usecase/impl/SaveWifiOnlyNotificationSettingUseCaseImpl.kt new file mode 100644 index 00000000..7c3f15cf --- /dev/null +++ b/AndroidApp/domain/src/main/kotlin/me/nya_n/notificationnotifier/domain/usecase/impl/SaveWifiOnlyNotificationSettingUseCaseImpl.kt @@ -0,0 +1,20 @@ +package me.nya_n.notificationnotifier.domain.usecase.impl + +import me.nya_n.notificationnotifier.data.repository.UserSettingsRepository +import me.nya_n.notificationnotifier.domain.usecase.SaveWifiOnlyNotificationSettingUseCase + +class SaveWifiOnlyNotificationSettingUseCaseImpl( + private val userSettingsRepository: UserSettingsRepository +) : SaveWifiOnlyNotificationSettingUseCase { + override fun invoke(isWifiOnlyNotificationEnabled: Boolean): Result { + return runCatching { + val settings = userSettingsRepository.getUserSettings() + userSettingsRepository.saveUserSettings( + settings.copy( + isWifiOnlyNotificationEnabled = isWifiOnlyNotificationEnabled + ) + ) + return Result.success(Unit) + } + } +} \ No newline at end of file diff --git a/AndroidApp/domain/src/test/java/me/nya_n/notificationnotifier/UseCaseTest.kt b/AndroidApp/domain/src/test/java/me/nya_n/notificationnotifier/UseCaseTest.kt index 15492f19..f43faffa 100644 --- a/AndroidApp/domain/src/test/java/me/nya_n/notificationnotifier/UseCaseTest.kt +++ b/AndroidApp/domain/src/test/java/me/nya_n/notificationnotifier/UseCaseTest.kt @@ -49,7 +49,7 @@ class UseCaseTest { @Test fun `インストール済みアプリの取得_許可あり`() { - every { userSettingsRepository.getUserSettings() } returns UserSettings("", 0, true) + every { userSettingsRepository.getUserSettings() } returns UserSettings("", 0, true, false) every { appRepository.loadInstalledAppList() } returns listOf(InstalledApp("", "")) val ret = loadAppUseCase.loadInstalledAppList() @@ -61,7 +61,7 @@ class UseCaseTest { @Test fun `インストール済みアプリの取得_許可なし`() { - every { userSettingsRepository.getUserSettings() } returns UserSettings("", 0, false) + every { userSettingsRepository.getUserSettings() } returns UserSettings("", 0, false, false) every { appRepository.loadInstalledAppList() } throws PermissionDeniedException() val ret = loadAppUseCase.loadInstalledAppList() @@ -76,7 +76,8 @@ class UseCaseTest { every { userSettingsRepository.getUserSettings() } returns UserSettings( "192.168.10.18", 8484, - true + true, + false ) every { userSettingsRepository.saveUserSettings(any()) } just Runs @@ -115,7 +116,8 @@ class UseCaseTest { every { userSettingsRepository.getUserSettings() } returns UserSettings( "192.168.10.18", 8484, - true + true, + false ) runTest(testDispatcher) { diff --git a/AndroidApp/model/src/main/kotlin/me/nya_n/notificationnotifier/model/UserSettings.kt b/AndroidApp/model/src/main/kotlin/me/nya_n/notificationnotifier/model/UserSettings.kt index 5552b5ea..16b6cca0 100644 --- a/AndroidApp/model/src/main/kotlin/me/nya_n/notificationnotifier/model/UserSettings.kt +++ b/AndroidApp/model/src/main/kotlin/me/nya_n/notificationnotifier/model/UserSettings.kt @@ -6,8 +6,10 @@ data class UserSettings( val host: String, val port: Int, /** - * @see https://support.google.com/googleplay/android-developer/answer/10158779 + * @see パッケージ(アプリ)の広範な一覧取得(QUERY_ALL_PACKAGES)権限の使用 */ @SerializedName("is_package_visibility_granted") - val isPackageVisibilityGranted: Boolean + val isPackageVisibilityGranted: Boolean, + @SerializedName("is_wifi_only_notification_enabled") + val isWifiOnlyNotificationEnabled: Boolean ) \ No newline at end of file diff --git a/AndroidApp/ui/src/main/kotlin/me/nya_n/notificationnotifier/ui/screen/settings/SettingsScreen.kt b/AndroidApp/ui/src/main/kotlin/me/nya_n/notificationnotifier/ui/screen/settings/SettingsScreen.kt index 20bcdd4e..0c6bbbb5 100644 --- a/AndroidApp/ui/src/main/kotlin/me/nya_n/notificationnotifier/ui/screen/settings/SettingsScreen.kt +++ b/AndroidApp/ui/src/main/kotlin/me/nya_n/notificationnotifier/ui/screen/settings/SettingsScreen.kt @@ -7,6 +7,7 @@ import androidx.appcompat.app.AppCompatActivity import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth @@ -23,6 +24,7 @@ import androidx.compose.material.icons.outlined.CloudDownload import androidx.compose.material.icons.outlined.CloudUpload import androidx.compose.material.icons.outlined.Code import androidx.compose.material.icons.outlined.Devices +import androidx.compose.material3.Checkbox import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.SnackbarHostState @@ -115,6 +117,8 @@ fun SettingsScreen( versionName = uiState.appConfig.versionString, onValueChange = { viewModel.updateAddress(it) }, onNotifyTest = { viewModel.notifyTest() }, + isWifiOnlyNotificationEnabled = uiState.isWifiOnlyNotificationEnabled, + onWifiOnlySettingChanged = { viewModel.updateWifiOnlySetting(it) }, onExportData = { viewModel.event(UiEvent.ExportData()) }, onImportData = { viewModel.event(UiEvent.ImportData()) }, onLicense = { navController.navigate(Screen.License.route) }, @@ -130,6 +134,8 @@ fun SettingsContent( versionName: String, onValueChange: (String) -> Unit, onNotifyTest: () -> Unit, + isWifiOnlyNotificationEnabled: Boolean, + onWifiOnlySettingChanged: (Boolean) -> Unit, onExportData: () -> Unit, onImportData: () -> Unit, onLicense: () -> Unit, @@ -147,7 +153,9 @@ fun SettingsContent( NotifySettings( address = address, onValueChange = onValueChange, - onNotifyTest = onNotifyTest + onNotifyTest = onNotifyTest, + isWifiOnlyNotificationEnabled = isWifiOnlyNotificationEnabled, + onWifiOnlySettingChanged = onWifiOnlySettingChanged ) OtherSettings( onExportData = onExportData, @@ -176,7 +184,9 @@ fun SettingsContent( private fun NotifySettings( address: String, onValueChange: (String) -> Unit, - onNotifyTest: () -> Unit + onNotifyTest: () -> Unit, + isWifiOnlyNotificationEnabled: Boolean, + onWifiOnlySettingChanged: (Boolean) -> Unit ) { val keyboardController = LocalSoftwareKeyboardController.current val focusManager = LocalFocusManager.current @@ -216,6 +226,15 @@ private fun NotifySettings( text = stringResource(id = R.string.notify_test), onClick = onNotifyTest ) + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Checkbox( + checked = isWifiOnlyNotificationEnabled, + onCheckedChange = onWifiOnlySettingChanged + ) + Text("Wi-Fi接続時のみ転送する。") + } } /** その他の項目 @@ -293,6 +312,8 @@ private fun SettingsPreview() { versionName = "1.0", onValueChange = { }, onNotifyTest = { }, + isWifiOnlyNotificationEnabled = true, + onWifiOnlySettingChanged = { }, onExportData = { }, onImportData = { }, onLicense = { }, diff --git a/AndroidApp/ui/src/main/kotlin/me/nya_n/notificationnotifier/ui/screen/settings/SettingsViewModel.kt b/AndroidApp/ui/src/main/kotlin/me/nya_n/notificationnotifier/ui/screen/settings/SettingsViewModel.kt index f32fc666..a3639600 100644 --- a/AndroidApp/ui/src/main/kotlin/me/nya_n/notificationnotifier/ui/screen/settings/SettingsViewModel.kt +++ b/AndroidApp/ui/src/main/kotlin/me/nya_n/notificationnotifier/ui/screen/settings/SettingsViewModel.kt @@ -12,6 +12,7 @@ import me.nya_n.notificationnotifier.domain.usecase.ImportDataUseCase import me.nya_n.notificationnotifier.domain.usecase.LoadAddressUseCase import me.nya_n.notificationnotifier.domain.usecase.NotifyUseCase import me.nya_n.notificationnotifier.domain.usecase.SaveAddressUseCase +import me.nya_n.notificationnotifier.domain.usecase.SaveWifiOnlyNotificationSettingUseCase import me.nya_n.notificationnotifier.model.AppConfig import me.nya_n.notificationnotifier.model.Message import me.nya_n.notificationnotifier.ui.R @@ -22,6 +23,7 @@ class SettingsViewModel( loadAddressUseCase: LoadAddressUseCase, private val saveAddressUseCase: SaveAddressUseCase, private val notifyUseCase: NotifyUseCase, + private val saveWifiOnlyNotificationSettingUseCase: SaveWifiOnlyNotificationSettingUseCase, private val exportDataUseCase: ExportDataUseCase, private val importDataUseCase: ImportDataUseCase ) : BaseViewModel() { @@ -59,6 +61,11 @@ class SettingsViewModel( } } + fun updateWifiOnlySetting(isWifiOnlyNotificationEnabled: Boolean) { + saveWifiOnlyNotificationSettingUseCase(isWifiOnlyNotificationEnabled) + _uiState.update { it.copy(isWifiOnlyNotificationEnabled = isWifiOnlyNotificationEnabled) } + } + /** バックアップのために外部ストレージにデータを保存 */ fun exportData(uri: Uri?) { if (uri == null) { diff --git a/AndroidApp/ui/src/main/kotlin/me/nya_n/notificationnotifier/ui/screen/settings/UiState.kt b/AndroidApp/ui/src/main/kotlin/me/nya_n/notificationnotifier/ui/screen/settings/UiState.kt index b322643a..bb3e279b 100644 --- a/AndroidApp/ui/src/main/kotlin/me/nya_n/notificationnotifier/ui/screen/settings/UiState.kt +++ b/AndroidApp/ui/src/main/kotlin/me/nya_n/notificationnotifier/ui/screen/settings/UiState.kt @@ -6,5 +6,6 @@ import me.nya_n.notificationnotifier.model.Message data class UiState( val appConfig: AppConfig, val address: String = "", - val message: Message? = null + val message: Message? = null, + val isWifiOnlyNotificationEnabled: Boolean = false ) \ No newline at end of file diff --git a/README.md b/README.md index 7f35fca2..eb57434f 100644 --- a/README.md +++ b/README.md @@ -47,8 +47,10 @@ Google Playからインストールしてください。 ###### 権限の説明 - インターネット接続 - 指定したアドレスに通知データを送信するために必要です。 +- ネットワーク状態の取得 + - Wi-Fiに接続しているか確認するために必要です。 - インストール済みアプリの取得 - - 通知対象のアプリを選択するための必要です。 + - 通知対象のアプリを選択するために必要です。 - 通知へのアクセス - PCに通知する内容を知るために、通知へアクセスする必要があります。