diff --git a/AndroidApp/app/src/main/kotlin/me/nya_n/notificationnotifier/App.kt b/AndroidApp/app/src/main/kotlin/me/nya_n/notificationnotifier/App.kt index 5865ba0e..01611226 100644 --- a/AndroidApp/app/src/main/kotlin/me/nya_n/notificationnotifier/App.kt +++ b/AndroidApp/app/src/main/kotlin/me/nya_n/notificationnotifier/App.kt @@ -2,8 +2,10 @@ package me.nya_n.notificationnotifier import android.app.Application import me.nya_n.notificationnotifier.data.repository.AppRepository +import me.nya_n.notificationnotifier.data.repository.BackupRepository import me.nya_n.notificationnotifier.data.repository.UserSettingsRepository import me.nya_n.notificationnotifier.data.repository.impl.AppRepositoryImpl +import me.nya_n.notificationnotifier.data.repository.impl.BackupRepositoryImpl import me.nya_n.notificationnotifier.data.repository.impl.UserSettingsRepositoryImpl import me.nya_n.notificationnotifier.data.repository.source.DB import me.nya_n.notificationnotifier.data.repository.source.UserSettingsDataStore @@ -81,20 +83,21 @@ class App : Application() { // Repository single { UserSettingsRepositoryImpl(get()) } - single { AppRepositoryImpl(get(), get()) } + single { AppRepositoryImpl(applicationContext.packageManager, get(), get()) } + single { BackupRepositoryImpl(applicationContext) } // ViewModel viewModel { AppViewModel(get(), packageName, get(), get()) } - viewModel { SelectionViewModel(get(), get(), get()) } + viewModel { SelectionViewModel(get(), get()) } viewModel { params -> DetailViewModel(get(), get(), get(), get(), params.get()) } - viewModel { TargetViewModel(get(), get()) } + viewModel { TargetViewModel(get()) } viewModel { SettingsViewModel(get(), get(), get(), get(), get(), get()) } // UseCase factory { AddTargetAppUseCaseImpl(get()) } factory { DeleteTargetAppUseCaseImpl(get()) } - factory { ExportDataUseCaseImpl(get(), get()) } - factory { ImportDataUseCaseImpl(get(), get()) } + factory { ExportDataUseCaseImpl(get(), get(), get()) } + factory { ImportDataUseCaseImpl(get(), get(), get()) } factory { LoadAddressUseCaseImpl(get()) } factory { LoadAppUseCaseImpl(get(), get()) } factory { LoadFilterConditionUseCaseImpl(get()) } diff --git a/AndroidApp/data/repository/build.gradle.kts b/AndroidApp/data/repository/build.gradle.kts index 8548beb8..58e4edd7 100644 --- a/AndroidApp/data/repository/build.gradle.kts +++ b/AndroidApp/data/repository/build.gradle.kts @@ -20,6 +20,12 @@ dependencies { // room api(libs.androidx.room.runtime) ksp(libs.androidx.room.compiler) + + // test + androidTestImplementation(libs.junit) + androidTestImplementation(libs.com.google.truth) + androidTestImplementation(libs.androidx.test.ext.junit) + androidTestImplementation(libs.kotlinx.coroutines.test) } ksp { diff --git a/AndroidApp/data/repository/src/androidTest/kotlin/me/nya_n/notificationnotifier/AppRepositoryTest.kt b/AndroidApp/data/repository/src/androidTest/kotlin/me/nya_n/notificationnotifier/AppRepositoryTest.kt new file mode 100644 index 00000000..7393fe78 --- /dev/null +++ b/AndroidApp/data/repository/src/androidTest/kotlin/me/nya_n/notificationnotifier/AppRepositoryTest.kt @@ -0,0 +1,82 @@ +package me.nya_n.notificationnotifier + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest +import me.nya_n.notificationnotifier.data.repository.AppRepository +import me.nya_n.notificationnotifier.data.repository.impl.AppRepositoryImpl +import me.nya_n.notificationnotifier.data.repository.source.DB +import me.nya_n.notificationnotifier.model.FilterCondition +import me.nya_n.notificationnotifier.model.InstalledApp +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@Suppress("NonAsciiCharacters") +@RunWith(AndroidJUnit4::class) +class AppRepositoryTest { + @OptIn(ExperimentalCoroutinesApi::class) + private val testDispatcher = UnconfinedTestDispatcher() + private lateinit var appRepository: AppRepository + + @Before + fun setUp() { + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + val db = DB.get(appContext, isInMemory = true).apply { + clearAllTables() + } + appRepository = AppRepositoryImpl( + appContext.packageManager, + db.filterConditionDao(), + db.targetAppDao(), + testDispatcher + ) + } + + @Test + fun `通知対象アプリの追加、取得、削除`() { + runTest(testDispatcher) { + val app = InstalledApp("sample", "com.sample.www") + appRepository.addTargetApp(app) + + val added = appRepository.getTargetAppList() + assertThat(added).hasSize(1) + assertThat(added.first()).isEqualTo(app) + + appRepository.deleteTargetApp(app) + val deleted = appRepository.getTargetAppList() + assertThat(deleted).isEmpty() + } + } + + + @Test + fun `通知条件の追加、取得、更新`() { + runTest(testDispatcher) { + val packageName = "com.sample.www" + + // データなし + assertThat(appRepository.getFilterCondition(packageName)).isNull() + assertThat(appRepository.getFilterConditionOrDefault(packageName)) + .isEqualTo(FilterCondition.default(packageName)) + + // 追加 + val added = FilterCondition(packageName, false, "test") + appRepository.saveFilterCondition(added) + assertThat(appRepository.getFilterCondition(packageName)).isEqualTo(added) + + // メッセージ条件の更新 + val updatedCondition = added.copy(condition = "updated") + appRepository.saveFilterCondition(updatedCondition) + assertThat(appRepository.getFilterCondition(packageName)).isEqualTo(updatedCondition) + + // サマリー条件の更新 + val updatedSummary = updatedCondition.copy(isIgnoreSummary = true) + appRepository.saveFilterCondition(updatedSummary) + assertThat(appRepository.getFilterCondition(packageName)).isEqualTo(updatedSummary) + } + } +} \ No newline at end of file diff --git a/AndroidApp/data/repository/src/androidTest/kotlin/me/nya_n/notificationnotifier/UserSettingsRepositoryTest.kt b/AndroidApp/data/repository/src/androidTest/kotlin/me/nya_n/notificationnotifier/UserSettingsRepositoryTest.kt new file mode 100644 index 00000000..18eab7d4 --- /dev/null +++ b/AndroidApp/data/repository/src/androidTest/kotlin/me/nya_n/notificationnotifier/UserSettingsRepositoryTest.kt @@ -0,0 +1,55 @@ +package me.nya_n.notificationnotifier + +import androidx.core.content.edit +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest +import me.nya_n.notificationnotifier.data.repository.UserSettingsRepository +import me.nya_n.notificationnotifier.data.repository.impl.UserSettingsRepositoryImpl +import me.nya_n.notificationnotifier.data.repository.source.UserSettingsDataStore +import me.nya_n.notificationnotifier.data.repository.util.SharedPreferenceProvider +import me.nya_n.notificationnotifier.model.UserSettings +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@Suppress("NonAsciiCharacters") +@RunWith(AndroidJUnit4::class) +class UserSettingsRepositoryTest { + @OptIn(ExperimentalCoroutinesApi::class) + private val testDispatcher = UnconfinedTestDispatcher() + private lateinit var userSettingsRepository: UserSettingsRepository + + @Before + fun setUp() { + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + userSettingsRepository = UserSettingsRepositoryImpl( + UserSettingsDataStore( + SharedPreferenceProvider.create( + appContext, + UserSettingsDataStore.DATA_STORE_NAME + ).apply { + edit { + clear() + } + } + ) + ) + } + + @Test + fun `ユーザー設定の保存、取得、更新`() { + runTest(testDispatcher) { + val data = UserSettings("192.168.10.18", 8484, false) + userSettingsRepository.saveUserSettings(data) + assertThat(userSettingsRepository.getUserSettings()).isEqualTo(data) + + val updated = data.copy(port = 2525, isPackageVisibilityGranted = true) + userSettingsRepository.saveUserSettings(updated) + assertThat(userSettingsRepository.getUserSettings()).isEqualTo(updated) + } + } +} \ No newline at end of file diff --git a/AndroidApp/data/repository/src/main/kotlin/me/nya_n/notificationnotifier/data/repository/AppRepository.kt b/AndroidApp/data/repository/src/main/kotlin/me/nya_n/notificationnotifier/data/repository/AppRepository.kt index 83aed6ad..681372f2 100644 --- a/AndroidApp/data/repository/src/main/kotlin/me/nya_n/notificationnotifier/data/repository/AppRepository.kt +++ b/AndroidApp/data/repository/src/main/kotlin/me/nya_n/notificationnotifier/data/repository/AppRepository.kt @@ -1,6 +1,5 @@ package me.nya_n.notificationnotifier.data.repository -import android.content.pm.PackageManager import me.nya_n.notificationnotifier.model.FilterCondition import me.nya_n.notificationnotifier.model.InstalledApp @@ -23,5 +22,5 @@ interface AppRepository { suspend fun deleteTargetApp(target: InstalledApp) - fun loadInstalledAppList(pm: PackageManager): List + fun loadInstalledAppList(): List } \ No newline at end of file diff --git a/AndroidApp/data/repository/src/main/kotlin/me/nya_n/notificationnotifier/data/repository/BackupRepository.kt b/AndroidApp/data/repository/src/main/kotlin/me/nya_n/notificationnotifier/data/repository/BackupRepository.kt new file mode 100644 index 00000000..4591a16b --- /dev/null +++ b/AndroidApp/data/repository/src/main/kotlin/me/nya_n/notificationnotifier/data/repository/BackupRepository.kt @@ -0,0 +1,17 @@ +package me.nya_n.notificationnotifier.data.repository + +import android.net.Uri + +interface BackupRepository { + /** [uri]に通知対象や条件、設定を保存 + * @param uri 保存先 + * @param data 保存するデータ + */ + suspend fun exportToUri(uri: Uri, data: String) + + /** [uri]から通知対象や条件、設定を復元 + * @param uri 読み込み元 + * @return 復元したデータ + */ + suspend fun importFromUri(uri: Uri): String +} \ No newline at end of file diff --git a/AndroidApp/data/repository/src/main/kotlin/me/nya_n/notificationnotifier/data/repository/impl/AppRepositoryImpl.kt b/AndroidApp/data/repository/src/main/kotlin/me/nya_n/notificationnotifier/data/repository/impl/AppRepositoryImpl.kt index 8bd590af..57af1d6a 100644 --- a/AndroidApp/data/repository/src/main/kotlin/me/nya_n/notificationnotifier/data/repository/impl/AppRepositoryImpl.kt +++ b/AndroidApp/data/repository/src/main/kotlin/me/nya_n/notificationnotifier/data/repository/impl/AppRepositoryImpl.kt @@ -11,6 +11,7 @@ import me.nya_n.notificationnotifier.model.FilterCondition import me.nya_n.notificationnotifier.model.InstalledApp class AppRepositoryImpl( + private val packageManager: PackageManager, private val filterConditionDao: FilterConditionDao, private val targetAppDao: TargetAppDao, private val coroutineDispatcher: CoroutineDispatcher = Dispatchers.IO @@ -62,10 +63,10 @@ class AppRepositoryImpl( } } - override fun loadInstalledAppList(pm: PackageManager): List { - return pm.getInstalledApplications(PackageManager.GET_META_DATA) + override fun loadInstalledAppList(): List { + return packageManager.getInstalledApplications(PackageManager.GET_META_DATA) .map { - val label = pm.getApplicationLabel(it).toString() + val label = packageManager.getApplicationLabel(it).toString() InstalledApp( label, it.packageName diff --git a/AndroidApp/data/repository/src/main/kotlin/me/nya_n/notificationnotifier/data/repository/impl/BackupRepositoryImpl.kt b/AndroidApp/data/repository/src/main/kotlin/me/nya_n/notificationnotifier/data/repository/impl/BackupRepositoryImpl.kt new file mode 100644 index 00000000..222487e9 --- /dev/null +++ b/AndroidApp/data/repository/src/main/kotlin/me/nya_n/notificationnotifier/data/repository/impl/BackupRepositoryImpl.kt @@ -0,0 +1,35 @@ +package me.nya_n.notificationnotifier.data.repository.impl + +import android.content.Context +import android.net.Uri +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import me.nya_n.notificationnotifier.data.repository.BackupRepository +import java.io.BufferedReader +import java.io.InputStreamReader + +class BackupRepositoryImpl( + private val context: Context, + private val coroutineDispatcher: CoroutineDispatcher = Dispatchers.IO +) : BackupRepository { + override suspend fun exportToUri(uri: Uri, data: String) { + withContext(coroutineDispatcher) { + context.contentResolver.openOutputStream(uri)?.use { + it.write(data.toByteArray()) + } ?: throw RuntimeException("Failed to open output stream.") + } + } + + override suspend fun importFromUri(uri: Uri): String { + val sb = StringBuilder() + withContext(coroutineDispatcher) { + context.contentResolver.openInputStream(uri)?.use { input -> + BufferedReader(InputStreamReader(input)).use { reader -> + sb.append(reader.readLine()) + } ?: throw RuntimeException("Failed to read input stream.") + } ?: throw RuntimeException("Failed to open input stream.") + } + return sb.toString() + } +} \ No newline at end of file diff --git a/AndroidApp/domain/build.gradle.kts b/AndroidApp/domain/build.gradle.kts index 29f7b8d2..175f61be 100644 --- a/AndroidApp/domain/build.gradle.kts +++ b/AndroidApp/domain/build.gradle.kts @@ -21,11 +21,17 @@ dependencies { api(libs.androidx.compose.runtime) // test + testImplementation(libs.junit) + testImplementation(libs.com.google.truth) + testImplementation(libs.kotlinx.coroutines.test) + testImplementation(libs.mockk) androidTestImplementation(libs.junit) androidTestImplementation(libs.com.google.truth) androidTestImplementation(libs.androidx.test.ext.junit) androidTestImplementation(libs.androidx.test.espresso.core) androidTestImplementation(libs.kotlinx.coroutines.test) + androidTestImplementation(libs.mockk) + androidTestImplementation(libs.mockk.android) // その他 implementation(libs.com.google.code.gson) diff --git a/AndroidApp/domain/src/androidTest/java/me/nya_n/notificationnotifier/UseCaseTest.kt b/AndroidApp/domain/src/androidTest/java/me/nya_n/notificationnotifier/UseCaseTest.kt index 26815640..37027f28 100644 --- a/AndroidApp/domain/src/androidTest/java/me/nya_n/notificationnotifier/UseCaseTest.kt +++ b/AndroidApp/domain/src/androidTest/java/me/nya_n/notificationnotifier/UseCaseTest.kt @@ -1,7 +1,5 @@ package me.nya_n.notificationnotifier -import android.content.Context -import android.content.pm.PackageManager import android.net.Uri import androidx.core.content.edit import androidx.test.ext.junit.runners.AndroidJUnit4 @@ -11,43 +9,36 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest import me.nya_n.notificationnotifier.data.repository.impl.AppRepositoryImpl +import me.nya_n.notificationnotifier.data.repository.impl.BackupRepositoryImpl import me.nya_n.notificationnotifier.data.repository.impl.UserSettingsRepositoryImpl import me.nya_n.notificationnotifier.data.repository.source.DB import me.nya_n.notificationnotifier.data.repository.source.UserSettingsDataStore import me.nya_n.notificationnotifier.data.repository.util.SharedPreferenceProvider import me.nya_n.notificationnotifier.domain.usecase.AddTargetAppUseCase -import me.nya_n.notificationnotifier.domain.usecase.DeleteTargetAppUseCase import me.nya_n.notificationnotifier.domain.usecase.ExportDataUseCase import me.nya_n.notificationnotifier.domain.usecase.ImportDataUseCase import me.nya_n.notificationnotifier.domain.usecase.LoadAddressUseCase import me.nya_n.notificationnotifier.domain.usecase.LoadFilterConditionUseCase -import me.nya_n.notificationnotifier.domain.usecase.NotifyUseCase -import me.nya_n.notificationnotifier.domain.usecase.PackageVisibilityGrantedUseCase import me.nya_n.notificationnotifier.domain.usecase.SaveAddressUseCase import me.nya_n.notificationnotifier.domain.usecase.SaveFilterConditionUseCase import me.nya_n.notificationnotifier.domain.usecase.ToggleIgnoreSummaryUseCase import me.nya_n.notificationnotifier.domain.usecase.impl.AddTargetAppUseCaseImpl -import me.nya_n.notificationnotifier.domain.usecase.impl.DeleteTargetAppUseCaseImpl import me.nya_n.notificationnotifier.domain.usecase.impl.ExportDataUseCaseImpl import me.nya_n.notificationnotifier.domain.usecase.impl.ImportDataUseCaseImpl import me.nya_n.notificationnotifier.domain.usecase.impl.LoadAddressUseCaseImpl import me.nya_n.notificationnotifier.domain.usecase.impl.LoadAppUseCaseImpl import me.nya_n.notificationnotifier.domain.usecase.impl.LoadFilterConditionUseCaseImpl -import me.nya_n.notificationnotifier.domain.usecase.impl.NotifyUseCaseImpl -import me.nya_n.notificationnotifier.domain.usecase.impl.PackageVisibilityGrantedUseCaseImpl import me.nya_n.notificationnotifier.domain.usecase.impl.SaveAddressUseCaseImpl import me.nya_n.notificationnotifier.domain.usecase.impl.SaveFilterConditionUseCaseImpl import me.nya_n.notificationnotifier.domain.usecase.impl.ToggleIgnoreSummaryUseCaseImpl -import me.nya_n.notificationnotifier.model.AppException.PermissionDeniedException import me.nya_n.notificationnotifier.model.FilterCondition import me.nya_n.notificationnotifier.model.InstalledApp -import org.junit.Assume.assumeTrue import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import java.io.File -@Suppress("NonAsciiCharacters", "RemoveRedundantBackticks") +@Suppress("NonAsciiCharacters") @RunWith(AndroidJUnit4::class) class UseCaseTest { companion object { @@ -56,33 +47,27 @@ class UseCaseTest { @OptIn(ExperimentalCoroutinesApi::class) private val testDispatcher = UnconfinedTestDispatcher() - private lateinit var appContext: Context - private lateinit var pm: PackageManager private lateinit var exportFile: File private lateinit var addTargetAppUseCase: AddTargetAppUseCase private lateinit var loadAppUseCase: LoadAppUseCaseImpl - private lateinit var deleteTargetAppUseCase: DeleteTargetAppUseCase - private lateinit var packageVisibilityGrantedUseCase: PackageVisibilityGrantedUseCase private lateinit var saveFilterConditionUseCase: SaveFilterConditionUseCase private lateinit var toggleIgnoreSummaryUseCase: ToggleIgnoreSummaryUseCase private lateinit var loadFilterConditionUseCase: LoadFilterConditionUseCase private lateinit var saveAddressUseCase: SaveAddressUseCase private lateinit var loadAddressUseCase: LoadAddressUseCase - private lateinit var notifyUseCase: NotifyUseCase private lateinit var exportDataUseCase: ExportDataUseCase private lateinit var importDataUseCase: ImportDataUseCase @Before fun setUp() { - appContext = InstrumentationRegistry.getInstrumentation().targetContext + val appContext = InstrumentationRegistry.getInstrumentation().targetContext exportFile = appContext.filesDir File(exportFile, ExportFileName).apply { if (exists()) { delete() } } - pm = appContext.packageManager val userSettingsRepository = UserSettingsRepositoryImpl( UserSettingsDataStore( SharedPreferenceProvider.create( @@ -99,172 +84,23 @@ class UseCaseTest { clearAllTables() } val appRepository = AppRepositoryImpl( + appContext.packageManager, db.filterConditionDao(), db.targetAppDao(), testDispatcher ) + val backupRepository = BackupRepositoryImpl(appContext, testDispatcher) addTargetAppUseCase = AddTargetAppUseCaseImpl(appRepository) loadAppUseCase = LoadAppUseCaseImpl(userSettingsRepository, appRepository, testDispatcher) - deleteTargetAppUseCase = DeleteTargetAppUseCaseImpl(appRepository) - packageVisibilityGrantedUseCase = - PackageVisibilityGrantedUseCaseImpl(userSettingsRepository) saveFilterConditionUseCase = SaveFilterConditionUseCaseImpl(appRepository) toggleIgnoreSummaryUseCase = ToggleIgnoreSummaryUseCaseImpl(appRepository) loadFilterConditionUseCase = LoadFilterConditionUseCaseImpl(appRepository) saveAddressUseCase = SaveAddressUseCaseImpl(userSettingsRepository) loadAddressUseCase = LoadAddressUseCaseImpl(userSettingsRepository) - notifyUseCase = NotifyUseCaseImpl(userSettingsRepository, testDispatcher) exportDataUseCase = - ExportDataUseCaseImpl(userSettingsRepository, appRepository, testDispatcher) + ExportDataUseCaseImpl(userSettingsRepository, appRepository, backupRepository) importDataUseCase = - ImportDataUseCaseImpl(userSettingsRepository, appRepository, testDispatcher) - } - - @Test - fun `通知対象アプリの追加、取得、削除`() { - runTest(testDispatcher) { - val app = InstalledApp("sample", "com.sample.www") - addTargetAppUseCase(app) - - val added = loadAppUseCase.loadTargetList() - assertThat(added).hasSize(1) - assertThat(added.first()).isEqualTo(app) - - deleteTargetAppUseCase(app) - val deleted = loadAppUseCase.loadTargetList() - assertThat(deleted).isEmpty() - } - } - - @Test - fun `インストール済みアプリの取得_成功(ついでにアプリ一覧取得権限許可処理も)`() { - packageVisibilityGrantedUseCase() - val ret = loadAppUseCase.loadInstalledAppList(pm) - assertThat(ret.getOrNull()).apply { - isNotNull() - isNotEmpty() - } - } - - @Test - fun `インストール済みアプリの取得_失敗`() { - val ret = loadAppUseCase.loadInstalledAppList(pm) - assertThat(ret.exceptionOrNull()).apply { - isNotNull() - isInstanceOf(PermissionDeniedException::class.java) - } - } - - @Test - fun `通知条件の追加、取得、更新`() { - runTest(testDispatcher) { - val cond = "test" - val updatedCond = "updated" - val packageName = "com.sample.www" - val app = InstalledApp("sample", packageName) - - // 追加 - saveFilterConditionUseCase(SaveFilterConditionUseCase.Args(app, cond)) - assertThat(loadFilterConditionUseCase(app)).isEqualTo( - FilterCondition( - packageName, - false, - cond - ) - ) - - // メッセージ条件の更新 - saveFilterConditionUseCase(SaveFilterConditionUseCase.Args(app, updatedCond)) - assertThat(loadFilterConditionUseCase(app)).isEqualTo( - FilterCondition( - packageName, - false, - updatedCond - ) - ) - - // サマリー条件の更新 - toggleIgnoreSummaryUseCase.invoke(ToggleIgnoreSummaryUseCase.Args(app)) - assertThat(loadFilterConditionUseCase(app)).isEqualTo( - FilterCondition( - packageName, - true, - updatedCond - ) - ) - } - } - - @Test - fun `IPアドレスの追加、更新_成功、成功`() { - val host = "192.168.11.4" - val port = 5555 - val addr = "$host:$port" - assertThat(saveAddressUseCase(addr).getOrNull()).isNotNull() - assertThat(loadAddressUseCase()).isEqualTo(addr) - - val updatedHost = "192.168.11.2" - val updatedPort = 3456 - val updatedAddr = "$updatedHost:$updatedPort" - assertThat(saveAddressUseCase(updatedAddr).getOrNull()).isNotNull() - assertThat(loadAddressUseCase()).isEqualTo(updatedAddr) - } - - @Test - fun `IPアドレスの追加、更新_成功、失敗`() { - val host = "192.168.11.4" - val port = 5555 - val addr = "$host:$port" - assertThat(saveAddressUseCase(addr).getOrNull()).isNotNull() - assertThat(loadAddressUseCase()).isEqualTo(addr) - - val updatedHost = "192.168.11.2" - val updatedAddr = "$updatedHost:" - assertThat(saveAddressUseCase(updatedAddr).exceptionOrNull()).isNotNull() - assertThat(loadAddressUseCase()).isEqualTo(addr) - } - - @Test - fun `IPアドレスの追加_失敗_hostなし`() { - val port = 5555 - val addr = ":$port" - assertThat(saveAddressUseCase(addr).exceptionOrNull()).isNotNull() - } - - @Test - fun `IPアドレスの追加_失敗_portなし`() { - val host = "192.168.11.4" - val addr = "$host:" - assertThat(saveAddressUseCase(addr).exceptionOrNull()).isNotNull() - } - - @Test - fun `IPアドレスの追加_失敗_portが数値じゃない`() { - val host = "192.168.11.4" - val addr = "$host:test" - assertThat(saveAddressUseCase(addr).exceptionOrNull()).isNotNull() - } - - @Test - fun `通知送信_失敗`() { - runTest(testDispatcher) { - assertThat(notifyUseCase("通知テスト").exceptionOrNull()).isNotNull() - } - } - - @Test - @LocalOnly - fun `通知送信_成功`() { - // CI環境で実行しないようにする - assumeTrue("Local only", System.getenv("CI") == null) - - runTest(testDispatcher) { - val host = "192.168.10.18" // テスト環境のIPアドレスに変更する - val port = 8484 - val addr = "$host:$port" - saveAddressUseCase(addr) - assertThat(notifyUseCase("通知テスト").getOrNull()).isNotNull() - } + ImportDataUseCaseImpl(userSettingsRepository, appRepository, backupRepository) } @Test @@ -285,7 +121,7 @@ class UseCaseTest { saveAddressUseCase(addr) // バックアップ - exportDataUseCase(appContext, uri) + exportDataUseCase(uri) // バックアップ時とは異なるように適当に変更 // ターゲット @@ -295,7 +131,7 @@ class UseCaseTest { toggleIgnoreSummaryUseCase.invoke(ToggleIgnoreSummaryUseCase.Args(app)) // 復元 - importDataUseCase(appContext, uri) + importDataUseCase(uri) // 正常に復元できているか確認 // ターゲット一覧 diff --git a/AndroidApp/domain/src/main/kotlin/me/nya_n/notificationnotifier/domain/usecase/ExportDataUseCase.kt b/AndroidApp/domain/src/main/kotlin/me/nya_n/notificationnotifier/domain/usecase/ExportDataUseCase.kt index 40654349..7b884da7 100644 --- a/AndroidApp/domain/src/main/kotlin/me/nya_n/notificationnotifier/domain/usecase/ExportDataUseCase.kt +++ b/AndroidApp/domain/src/main/kotlin/me/nya_n/notificationnotifier/domain/usecase/ExportDataUseCase.kt @@ -1,9 +1,8 @@ package me.nya_n.notificationnotifier.domain.usecase -import android.content.Context import android.net.Uri /** バックアップのために外部ストレージにデータを保存 */ interface ExportDataUseCase { - suspend operator fun invoke(context: Context, uri: Uri): Result + suspend operator fun invoke(uri: Uri): Result } \ No newline at end of file diff --git a/AndroidApp/domain/src/main/kotlin/me/nya_n/notificationnotifier/domain/usecase/ImportDataUseCase.kt b/AndroidApp/domain/src/main/kotlin/me/nya_n/notificationnotifier/domain/usecase/ImportDataUseCase.kt index 359c5568..7086df0e 100644 --- a/AndroidApp/domain/src/main/kotlin/me/nya_n/notificationnotifier/domain/usecase/ImportDataUseCase.kt +++ b/AndroidApp/domain/src/main/kotlin/me/nya_n/notificationnotifier/domain/usecase/ImportDataUseCase.kt @@ -1,9 +1,8 @@ package me.nya_n.notificationnotifier.domain.usecase -import android.content.Context import android.net.Uri /** 外部ストレージのバックアップからデータを復元 */ interface ImportDataUseCase { - suspend operator fun invoke(context: Context, uri: Uri): Result + suspend operator fun invoke(uri: Uri): Result } \ No newline at end of file diff --git a/AndroidApp/domain/src/main/kotlin/me/nya_n/notificationnotifier/domain/usecase/LoadAppUseCase.kt b/AndroidApp/domain/src/main/kotlin/me/nya_n/notificationnotifier/domain/usecase/LoadAppUseCase.kt index 1d4ac0cf..b6773e8b 100644 --- a/AndroidApp/domain/src/main/kotlin/me/nya_n/notificationnotifier/domain/usecase/LoadAppUseCase.kt +++ b/AndroidApp/domain/src/main/kotlin/me/nya_n/notificationnotifier/domain/usecase/LoadAppUseCase.kt @@ -1,10 +1,9 @@ package me.nya_n.notificationnotifier.domain.usecase -import android.content.pm.PackageManager import me.nya_n.notificationnotifier.model.InstalledApp interface LoadAppUseCase { - suspend operator fun invoke(pm: PackageManager): Result + suspend operator fun invoke(): Result data class Outputs( val notTargets: List, diff --git a/AndroidApp/domain/src/main/kotlin/me/nya_n/notificationnotifier/domain/usecase/dummy/LoadAppUseCaseDummyImpl.kt b/AndroidApp/domain/src/main/kotlin/me/nya_n/notificationnotifier/domain/usecase/dummy/LoadAppUseCaseDummyImpl.kt index 4353f47e..18abcb34 100644 --- a/AndroidApp/domain/src/main/kotlin/me/nya_n/notificationnotifier/domain/usecase/dummy/LoadAppUseCaseDummyImpl.kt +++ b/AndroidApp/domain/src/main/kotlin/me/nya_n/notificationnotifier/domain/usecase/dummy/LoadAppUseCaseDummyImpl.kt @@ -1,11 +1,10 @@ package me.nya_n.notificationnotifier.domain.usecase.dummy -import android.content.pm.PackageManager import me.nya_n.notificationnotifier.domain.usecase.LoadAppUseCase import me.nya_n.notificationnotifier.model.InstalledApp class LoadAppUseCaseDummyImpl : LoadAppUseCase { - override suspend fun invoke(pm: PackageManager): Result { + override suspend fun invoke(): Result { return Result.success( LoadAppUseCase.Outputs( listOf( diff --git a/AndroidApp/domain/src/main/kotlin/me/nya_n/notificationnotifier/domain/usecase/impl/ExportDataUseCaseImpl.kt b/AndroidApp/domain/src/main/kotlin/me/nya_n/notificationnotifier/domain/usecase/impl/ExportDataUseCaseImpl.kt index 6d80df50..b8d5454c 100644 --- a/AndroidApp/domain/src/main/kotlin/me/nya_n/notificationnotifier/domain/usecase/impl/ExportDataUseCaseImpl.kt +++ b/AndroidApp/domain/src/main/kotlin/me/nya_n/notificationnotifier/domain/usecase/impl/ExportDataUseCaseImpl.kt @@ -1,12 +1,9 @@ package me.nya_n.notificationnotifier.domain.usecase.impl -import android.content.Context import android.net.Uri import com.google.gson.Gson -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext import me.nya_n.notificationnotifier.data.repository.AppRepository +import me.nya_n.notificationnotifier.data.repository.BackupRepository import me.nya_n.notificationnotifier.data.repository.UserSettingsRepository import me.nya_n.notificationnotifier.data.repository.source.DB import me.nya_n.notificationnotifier.domain.usecase.ExportDataUseCase @@ -15,9 +12,9 @@ import me.nya_n.notificationnotifier.model.Backup class ExportDataUseCaseImpl( private val userSettingsRepository: UserSettingsRepository, private val appRepository: AppRepository, - private val coroutineDispatcher: CoroutineDispatcher = Dispatchers.IO + private val backupRepository: BackupRepository, ) : ExportDataUseCase { - override suspend operator fun invoke(context: Context, uri: Uri): Result { + override suspend operator fun invoke(uri: Uri): Result { return runCatching { val data = Backup( userSettingsRepository.getUserSettings(), @@ -26,11 +23,7 @@ class ExportDataUseCaseImpl( appRepository.getFilterConditionList() ) val json = Gson().toJson(data) - withContext(coroutineDispatcher) { - context.contentResolver.openOutputStream(uri).use { - it?.write(json.toByteArray()) - } - } + backupRepository.exportToUri(uri, json) } } } \ No newline at end of file diff --git a/AndroidApp/domain/src/main/kotlin/me/nya_n/notificationnotifier/domain/usecase/impl/ImportDataUseCaseImpl.kt b/AndroidApp/domain/src/main/kotlin/me/nya_n/notificationnotifier/domain/usecase/impl/ImportDataUseCaseImpl.kt index d3d3bba2..858c3f3a 100644 --- a/AndroidApp/domain/src/main/kotlin/me/nya_n/notificationnotifier/domain/usecase/impl/ImportDataUseCaseImpl.kt +++ b/AndroidApp/domain/src/main/kotlin/me/nya_n/notificationnotifier/domain/usecase/impl/ImportDataUseCaseImpl.kt @@ -1,35 +1,22 @@ package me.nya_n.notificationnotifier.domain.usecase.impl -import android.content.Context import android.net.Uri import com.google.gson.Gson -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext import me.nya_n.notificationnotifier.data.repository.AppRepository +import me.nya_n.notificationnotifier.data.repository.BackupRepository import me.nya_n.notificationnotifier.data.repository.UserSettingsRepository import me.nya_n.notificationnotifier.data.repository.source.DB import me.nya_n.notificationnotifier.domain.usecase.ImportDataUseCase import me.nya_n.notificationnotifier.model.Backup -import java.io.BufferedReader -import java.io.InputStreamReader class ImportDataUseCaseImpl( private val userSettingsRepository: UserSettingsRepository, private val appRepository: AppRepository, - private val coroutineDispatcher: CoroutineDispatcher = Dispatchers.IO + private val backupRepository: BackupRepository, ) : ImportDataUseCase { - override suspend operator fun invoke(context: Context, uri: Uri): Result { + override suspend operator fun invoke(uri: Uri): Result { return runCatching { - val sb = StringBuilder() - withContext(coroutineDispatcher) { - context.contentResolver.openInputStream(uri).use { input -> - BufferedReader(InputStreamReader(input)).use { reader -> - sb.append(reader.readLine()) - } - } - } - val json = sb.toString() + val json = backupRepository.importFromUri(uri) val backup = Gson().fromJson(json, Backup::class.java) if (backup.version != DB.version()) { throw RuntimeException("bad version.") diff --git a/AndroidApp/domain/src/main/kotlin/me/nya_n/notificationnotifier/domain/usecase/impl/LoadAppUseCaseImpl.kt b/AndroidApp/domain/src/main/kotlin/me/nya_n/notificationnotifier/domain/usecase/impl/LoadAppUseCaseImpl.kt index 9d3a11ce..9d36fa94 100644 --- a/AndroidApp/domain/src/main/kotlin/me/nya_n/notificationnotifier/domain/usecase/impl/LoadAppUseCaseImpl.kt +++ b/AndroidApp/domain/src/main/kotlin/me/nya_n/notificationnotifier/domain/usecase/impl/LoadAppUseCaseImpl.kt @@ -1,6 +1,5 @@ package me.nya_n.notificationnotifier.domain.usecase.impl -import android.content.pm.PackageManager import androidx.annotation.VisibleForTesting import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers @@ -18,9 +17,9 @@ class LoadAppUseCaseImpl( private val coroutineDispatcher: CoroutineDispatcher = Dispatchers.IO ) : LoadAppUseCase { - override suspend operator fun invoke(pm: PackageManager): Result = + override suspend operator fun invoke(): Result = withContext(coroutineDispatcher) { - val apps = loadInstalledAppList(pm).getOrElse { + val apps = loadInstalledAppList().getOrElse { return@withContext Result.failure(it) } val targets = loadTargetList() @@ -28,12 +27,12 @@ class LoadAppUseCaseImpl( } @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) - fun loadInstalledAppList(pm: PackageManager): Result> { + fun loadInstalledAppList(): Result> { val settings = userSettingsRepository.getUserSettings() return if (!settings.isPackageVisibilityGranted) { Result.failure(AppException.PermissionDeniedException()) } else { - Result.success(appRepository.loadInstalledAppList(pm)) + Result.success(appRepository.loadInstalledAppList()) } } diff --git a/AndroidApp/domain/src/test/java/me/nya_n/notificationnotifier/Annotations.kt b/AndroidApp/domain/src/test/java/me/nya_n/notificationnotifier/Annotations.kt new file mode 100644 index 00000000..1f222b1b --- /dev/null +++ b/AndroidApp/domain/src/test/java/me/nya_n/notificationnotifier/Annotations.kt @@ -0,0 +1,5 @@ +package me.nya_n.notificationnotifier + +@Target(AnnotationTarget.FUNCTION) +@Retention(AnnotationRetention.RUNTIME) +annotation class LocalOnly \ 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 new file mode 100644 index 00000000..15492f19 --- /dev/null +++ b/AndroidApp/domain/src/test/java/me/nya_n/notificationnotifier/UseCaseTest.kt @@ -0,0 +1,125 @@ +package me.nya_n.notificationnotifier + +import com.google.common.truth.Truth.assertThat +import io.mockk.Runs +import io.mockk.every +import io.mockk.just +import io.mockk.mockk +import io.mockk.mockkStatic +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest +import me.nya_n.notificationnotifier.data.repository.AppRepository +import me.nya_n.notificationnotifier.data.repository.UserSettingsRepository +import me.nya_n.notificationnotifier.domain.usecase.NotifyUseCase +import me.nya_n.notificationnotifier.domain.usecase.SaveAddressUseCase +import me.nya_n.notificationnotifier.domain.usecase.impl.LoadAppUseCaseImpl +import me.nya_n.notificationnotifier.domain.usecase.impl.NotifyUseCaseImpl +import me.nya_n.notificationnotifier.domain.usecase.impl.SaveAddressUseCaseImpl +import me.nya_n.notificationnotifier.model.AppException.PermissionDeniedException +import me.nya_n.notificationnotifier.model.InstalledApp +import me.nya_n.notificationnotifier.model.UserSettings +import org.junit.Assume.assumeTrue +import org.junit.Before +import org.junit.Test + +@Suppress("NonAsciiCharacters", "RemoveRedundantBackticks") +class UseCaseTest { + @OptIn(ExperimentalCoroutinesApi::class) + private val testDispatcher = UnconfinedTestDispatcher() + private val userSettingsRepository: UserSettingsRepository = mockk() + private val appRepository: AppRepository = mockk() + + private lateinit var loadAppUseCase: LoadAppUseCaseImpl + private lateinit var saveAddressUseCase: SaveAddressUseCase + private lateinit var notifyUseCase: NotifyUseCase + + @Before + fun setUp() { + loadAppUseCase = LoadAppUseCaseImpl(userSettingsRepository, appRepository, testDispatcher) + saveAddressUseCase = SaveAddressUseCaseImpl(userSettingsRepository) + notifyUseCase = NotifyUseCaseImpl(userSettingsRepository, testDispatcher) + + mockkStatic(android.text.TextUtils::class) + every { android.text.TextUtils.isDigitsOnly(any()) } answers { + val str = it.invocation.args[0] as? CharSequence + str?.all { char -> char.isDigit() } == true + } + } + + @Test + fun `インストール済みアプリの取得_許可あり`() { + every { userSettingsRepository.getUserSettings() } returns UserSettings("", 0, true) + every { appRepository.loadInstalledAppList() } returns listOf(InstalledApp("", "")) + + val ret = loadAppUseCase.loadInstalledAppList() + assertThat(ret.getOrNull()).apply { + isNotNull() + isNotEmpty() + } + } + + @Test + fun `インストール済みアプリの取得_許可なし`() { + every { userSettingsRepository.getUserSettings() } returns UserSettings("", 0, false) + every { appRepository.loadInstalledAppList() } throws PermissionDeniedException() + + val ret = loadAppUseCase.loadInstalledAppList() + assertThat(ret.exceptionOrNull()).apply { + isNotNull() + isInstanceOf(PermissionDeniedException::class.java) + } + } + + @Test + fun `IPアドレスの追加_成功`() { + every { userSettingsRepository.getUserSettings() } returns UserSettings( + "192.168.10.18", + 8484, + true + ) + every { userSettingsRepository.saveUserSettings(any()) } just Runs + + assertThat(saveAddressUseCase("192.168.10.18:8484").getOrNull()).isNotNull() + } + + @Test + fun `IPアドレスの追加_失敗_portなし`() { + assertThat(saveAddressUseCase("192.168.11.4").exceptionOrNull()).isNotNull() + } + + @Test + fun `IPアドレスの追加_失敗_hostなし`() { + assertThat(saveAddressUseCase(":8484").exceptionOrNull()).isNotNull() + } + + @Test + fun `IPアドレスの追加_失敗_portが数値じゃない`() { + assertThat(saveAddressUseCase("192.168.10.18:not_number").exceptionOrNull()).isNotNull() + } + + @Test + fun `通知送信_失敗`() { + runTest(testDispatcher) { + assertThat(notifyUseCase("通知テスト").exceptionOrNull()).isNotNull() + } + } + + @Test + @LocalOnly + fun `通知送信_成功`() { + // CI環境で実行しないようにする + assumeTrue("Local only", System.getenv("CI") == null) + + // テスト環境のIPアドレスに変更する + every { userSettingsRepository.getUserSettings() } returns UserSettings( + "192.168.10.18", + 8484, + true + ) + + runTest(testDispatcher) { + assertThat(notifyUseCase("通知テスト").getOrNull()).isNotNull() + } + } +} \ No newline at end of file diff --git a/AndroidApp/gradle/libs.versions.toml b/AndroidApp/gradle/libs.versions.toml index b62c350a..098b780c 100644 --- a/AndroidApp/gradle/libs.versions.toml +++ b/AndroidApp/gradle/libs.versions.toml @@ -32,6 +32,7 @@ androidx-room = "2.8.4" io-insert-koin = "4.1.1" com-google-code-gson = "2.13.2" com-squareup-leakcanary = "2.14" +mockk = "1.14.7" [plugins] com-android-application = { id = "com.android.application", version.ref = "agp" } @@ -68,6 +69,8 @@ kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-t com-google-truth = { module = "com.google.truth:truth", version.ref = "com-google-truth" } androidx-test-ext-junit = { module = "androidx.test.ext:junit", version.ref = "androidx-test-ext" } androidx-test-espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "androidx-test-espresso" } +mockk = { module = "io.mockk:mockk", version.ref = "mockk" } +mockk-android = { module = "io.mockk:mockk-android", version.ref = "mockk" } # for build-logic android-gradle-plugin = { module = "com.android.tools.build:gradle", version.ref = "agp" } diff --git a/AndroidApp/ui/src/main/kotlin/me/nya_n/notificationnotifier/ui/screen/selection/SelectionViewModel.kt b/AndroidApp/ui/src/main/kotlin/me/nya_n/notificationnotifier/ui/screen/selection/SelectionViewModel.kt index 4740881f..6d474865 100644 --- a/AndroidApp/ui/src/main/kotlin/me/nya_n/notificationnotifier/ui/screen/selection/SelectionViewModel.kt +++ b/AndroidApp/ui/src/main/kotlin/me/nya_n/notificationnotifier/ui/screen/selection/SelectionViewModel.kt @@ -1,7 +1,5 @@ package me.nya_n.notificationnotifier.ui.screen.selection -import android.content.Context -import android.content.pm.PackageManager import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import kotlinx.coroutines.flow.MutableStateFlow @@ -16,12 +14,9 @@ import me.nya_n.notificationnotifier.model.Message import me.nya_n.notificationnotifier.ui.R class SelectionViewModel( - context: Context, private val loadAppUseCase: LoadAppUseCase, private val addTargetAppUseCase: AddTargetAppUseCase ) : ViewModel() { - private val pm: PackageManager = context.packageManager - private val _uiState = MutableStateFlow(UiState()) val uiState: StateFlow = _uiState.asStateFlow() @@ -32,7 +27,7 @@ class SelectionViewModel( // 未読込の場合だけプログレスバーを表示 _uiState.update { it.copy(isLoading = true) } } - loadAppUseCase(pm).onSuccess { res -> + loadAppUseCase().onSuccess { res -> val query = uiState.value.query val items = res.notTargets .filter { app -> app.label.contains(query) || app.packageName.contains(query) } 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 f1949c59..20bcdd4e 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 @@ -36,7 +36,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.res.stringResource @@ -63,20 +62,19 @@ fun SettingsScreen( snackbarHostState: SnackbarHostState, viewModel: SettingsViewModel = koinViewModel() ) { - val context = LocalContext.current val uiState by viewModel.uiState.collectAsState() val uiEvents by viewModel.uiEvent.collectAsState() val exportLauncher = rememberLauncherForActivityResult( contract = ActivityResultContracts.StartActivityForResult() ) { if (it.resultCode != AppCompatActivity.RESULT_OK) return@rememberLauncherForActivityResult - viewModel.exportData(context, it.data?.data) + viewModel.exportData(it.data?.data) } val importLauncher = rememberLauncherForActivityResult( contract = ActivityResultContracts.StartActivityForResult() ) { if (it.resultCode != AppCompatActivity.RESULT_OK) return@rememberLauncherForActivityResult - viewModel.importData(context, it.data?.data) + viewModel.importData(it.data?.data) } uiEvents.firstOrNull()?.let { when (it) { 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 02129432..f32fc666 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 @@ -1,6 +1,5 @@ package me.nya_n.notificationnotifier.ui.screen.settings -import android.content.Context import android.net.Uri import androidx.lifecycle.viewModelScope import kotlinx.coroutines.flow.MutableStateFlow @@ -61,13 +60,13 @@ class SettingsViewModel( } /** バックアップのために外部ストレージにデータを保存 */ - fun exportData(context: Context, uri: Uri?) { + fun exportData(uri: Uri?) { if (uri == null) { _uiState.update { it.copy(message = Message.Error(R.string.export_failed)) } return } viewModelScope.launch { - val message = if (exportDataUseCase(context, uri).isSuccess) { + val message = if (exportDataUseCase(uri).isSuccess) { Message.Notice(R.string.export_succeeded) } else { Message.Error(R.string.export_failed) @@ -77,13 +76,13 @@ class SettingsViewModel( } /** 外部ストレージのバックアップからデータを復元 */ - fun importData(context: Context, uri: Uri?) { + fun importData(uri: Uri?) { if (uri == null) { _uiState.update { it.copy(message = Message.Error(R.string.import_failed)) } return } viewModelScope.launch { - val message = if (importDataUseCase(context, uri).isSuccess) { + val message = if (importDataUseCase(uri).isSuccess) { Message.Notice(R.string.import_succeeded) } else { Message.Error(R.string.import_failed) diff --git a/AndroidApp/ui/src/main/kotlin/me/nya_n/notificationnotifier/ui/screen/target/TargetViewModel.kt b/AndroidApp/ui/src/main/kotlin/me/nya_n/notificationnotifier/ui/screen/target/TargetViewModel.kt index 244ab6e2..19374071 100644 --- a/AndroidApp/ui/src/main/kotlin/me/nya_n/notificationnotifier/ui/screen/target/TargetViewModel.kt +++ b/AndroidApp/ui/src/main/kotlin/me/nya_n/notificationnotifier/ui/screen/target/TargetViewModel.kt @@ -1,6 +1,5 @@ package me.nya_n.notificationnotifier.ui.screen.target -import android.content.Context import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import kotlinx.coroutines.flow.MutableStateFlow @@ -12,10 +11,8 @@ import me.nya_n.notificationnotifier.domain.usecase.LoadAppUseCase import me.nya_n.notificationnotifier.model.Message class TargetViewModel( - context: Context, private val useCase: LoadAppUseCase ) : ViewModel() { - private val pm = context.packageManager private val _uiState = MutableStateFlow(UiState()) val uiState: StateFlow = _uiState.asStateFlow() @@ -26,7 +23,7 @@ class TargetViewModel( // 未読込の場合だけプログレスバーを表示 _uiState.update { it.copy(isLoading = true) } } - useCase(pm).onSuccess { res -> + useCase().onSuccess { res -> _uiState.update { it.copy(items = res.targets) } } _uiState.update { it.copy(isLoading = false) }