diff --git a/apps/experimentarium/androidApp/build.gradle.kts b/apps/experimentarium/androidApp/build.gradle.kts
index 372f29e..30d3b62 100644
--- a/apps/experimentarium/androidApp/build.gradle.kts
+++ b/apps/experimentarium/androidApp/build.gradle.kts
@@ -1,4 +1,3 @@
-
import com.android.build.api.dsl.ApplicationExtension
plugins {
diff --git a/apps/vbox/backend/src/main/kotlin/ru/shadowsparky/vbox/backend/presentation/routing/SearchRouting.kt b/apps/vbox/backend/src/main/kotlin/ru/shadowsparky/vbox/backend/presentation/routing/SearchRouting.kt
index d5ca0ad..7428a32 100644
--- a/apps/vbox/backend/src/main/kotlin/ru/shadowsparky/vbox/backend/presentation/routing/SearchRouting.kt
+++ b/apps/vbox/backend/src/main/kotlin/ru/shadowsparky/vbox/backend/presentation/routing/SearchRouting.kt
@@ -20,7 +20,11 @@ fun Route.setupSearch(searchFactory: SearchRepositoryFactory) {
val p = call.parameters
val userId = call.obtainUserId()
val search = searchFactory.create(userId)
- call.respond(SearchQueryResponse(search.search(p[SearchRepository.QUERY_ARG] ?: "").first()))
+ call.respond(
+ SearchQueryResponse(
+ search.search(p[SearchRepository.QUERY_ARG] ?: "").first()
+ )
+ )
}
post(SearchRepository.ADD_TO_SEARCH) {
val userId = call.obtainUserId()
diff --git a/apps/vbox/client/androidApp/src/debug/res/xml/network_security_config.xml b/apps/vbox/client/androidApp/src/debug/res/xml/network_security_config.xml
index cc5d657..134bc5f 100644
--- a/apps/vbox/client/androidApp/src/debug/res/xml/network_security_config.xml
+++ b/apps/vbox/client/androidApp/src/debug/res/xml/network_security_config.xml
@@ -3,7 +3,8 @@
-
diff --git a/apps/vbox/client/androidApp/src/main/AndroidManifest.xml b/apps/vbox/client/androidApp/src/main/AndroidManifest.xml
index 659a4ba..7d62610 100644
--- a/apps/vbox/client/androidApp/src/main/AndroidManifest.xml
+++ b/apps/vbox/client/androidApp/src/main/AndroidManifest.xml
@@ -18,9 +18,9 @@
android:enableOnBackInvokedCallback="true"
android:icon="@mipmap/pig_launcher"
android:label="@string/app_name_pig"
+ android:networkSecurityConfig="@xml/network_security_config"
android:supportsRtl="true"
android:theme="@style/Theme.Videobox"
- android:networkSecurityConfig="@xml/network_security_config"
tools:ignore="UnusedAttribute">
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
\ No newline at end of file
diff --git a/apps/vbox/client/androidApp/src/main/res/drawable/pig_launcher_foreground.xml b/apps/vbox/client/androidApp/src/main/res/drawable/pig_launcher_foreground.xml
index 4f76ef5..3c6a012 100644
--- a/apps/vbox/client/androidApp/src/main/res/drawable/pig_launcher_foreground.xml
+++ b/apps/vbox/client/androidApp/src/main/res/drawable/pig_launcher_foreground.xml
@@ -4,106 +4,118 @@
android:height="108dp"
android:viewportWidth="471"
android:viewportHeight="458">
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/vbox/client/androidApp/src/main/res/mipmap-anydpi-v26/ic_pig_banner.xml b/apps/vbox/client/androidApp/src/main/res/mipmap-anydpi-v26/ic_pig_banner.xml
index 753c0b6..e6e49eb 100644
--- a/apps/vbox/client/androidApp/src/main/res/mipmap-anydpi-v26/ic_pig_banner.xml
+++ b/apps/vbox/client/androidApp/src/main/res/mipmap-anydpi-v26/ic_pig_banner.xml
@@ -1,5 +1,5 @@
-
-
+
+
\ No newline at end of file
diff --git a/apps/vbox/client/androidApp/src/main/res/mipmap-anydpi-v26/pig_launcher.xml b/apps/vbox/client/androidApp/src/main/res/mipmap-anydpi-v26/pig_launcher.xml
index 9c4e317..9821c73 100644
--- a/apps/vbox/client/androidApp/src/main/res/mipmap-anydpi-v26/pig_launcher.xml
+++ b/apps/vbox/client/androidApp/src/main/res/mipmap-anydpi-v26/pig_launcher.xml
@@ -1,5 +1,5 @@
-
-
+
+
\ No newline at end of file
diff --git a/apps/vbox/client/shared/src/androidMain/kotlin/ru/shadowsparky/vbox/shared/data/AndroidOfflineStorage.kt b/apps/vbox/client/shared/src/androidMain/kotlin/ru/shadowsparky/vbox/shared/data/AndroidOfflineStorage.kt
index 5d7a37d..49efae4 100644
--- a/apps/vbox/client/shared/src/androidMain/kotlin/ru/shadowsparky/vbox/shared/data/AndroidOfflineStorage.kt
+++ b/apps/vbox/client/shared/src/androidMain/kotlin/ru/shadowsparky/vbox/shared/data/AndroidOfflineStorage.kt
@@ -1,4 +1,3 @@
-
package ru.shadowsparky.vbox.shared.data
import app.cash.sqldelight.Query
diff --git a/apps/vbox/client/shared/src/androidMain/kotlin/ru/shadowsparky/vbox/shared/data/OfflineDownloader.kt b/apps/vbox/client/shared/src/androidMain/kotlin/ru/shadowsparky/vbox/shared/data/OfflineDownloader.kt
index 4662e54..9459350 100644
--- a/apps/vbox/client/shared/src/androidMain/kotlin/ru/shadowsparky/vbox/shared/data/OfflineDownloader.kt
+++ b/apps/vbox/client/shared/src/androidMain/kotlin/ru/shadowsparky/vbox/shared/data/OfflineDownloader.kt
@@ -78,12 +78,14 @@ class OfflineDownloader(private val context: Context) {
}
}.getOrDefault(OfflineState.Error("File not found"))
}
+
DownloadManager.STATUS_FAILED -> {
val reason = cursor.getInt(
cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_REASON)
)
OfflineState.Error("Download failed: reason=$reason")
}
+
else -> OfflineState.Error("Unknown status")
}
}
diff --git a/apps/vbox/client/shared/src/commonMain/composeResources/drawable/filter_list.xml b/apps/vbox/client/shared/src/commonMain/composeResources/drawable/filter_list.xml
index 7cfe99b..8615b7b 100644
--- a/apps/vbox/client/shared/src/commonMain/composeResources/drawable/filter_list.xml
+++ b/apps/vbox/client/shared/src/commonMain/composeResources/drawable/filter_list.xml
@@ -1,10 +1,10 @@
-
+ android:viewportHeight="960">
+
diff --git a/apps/vbox/client/shared/src/commonMain/composeResources/drawable/share.xml b/apps/vbox/client/shared/src/commonMain/composeResources/drawable/share.xml
index 67efdda..e63bcb7 100644
--- a/apps/vbox/client/shared/src/commonMain/composeResources/drawable/share.xml
+++ b/apps/vbox/client/shared/src/commonMain/composeResources/drawable/share.xml
@@ -1,10 +1,10 @@
-
+ android:viewportHeight="960">
+
diff --git a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/data/api/FallbackVideoApi.kt b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/data/api/FallbackVideoApi.kt
index d8d2a7c..f212266 100644
--- a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/data/api/FallbackVideoApi.kt
+++ b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/data/api/FallbackVideoApi.kt
@@ -62,10 +62,11 @@ class FallbackVideoApi(
return json.decodeFromString(value)
}
- private suspend inline fun writeByKey(key: String, value: T) = withContext(dispatcherProvider.io) {
- getCache().insert(
- key,
- json.encodeToString(value)
- )
- }
+ private suspend inline fun writeByKey(key: String, value: T) =
+ withContext(dispatcherProvider.io) {
+ getCache().insert(
+ key,
+ json.encodeToString(value)
+ )
+ }
}
diff --git a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/data/recent/SqlRecentlyWatchedRepository.kt b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/data/recent/SqlRecentlyWatchedRepository.kt
index 7745a4f..d879c7d 100644
--- a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/data/recent/SqlRecentlyWatchedRepository.kt
+++ b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/data/recent/SqlRecentlyWatchedRepository.kt
@@ -24,21 +24,23 @@ class SqlRecentlyWatchedRepository(
private val dispatcherProvider: DispatcherProvider
) : RecentlyWatchedRepository {
- override suspend fun write(info: RecentlyWatchedInfo): Unit = withContext(dispatcherProvider.io) {
- dbProvider.get().recentlyWatchedQueries.insertRecentlyWatched(
- info.movieId,
- info.episode ?: UNKNOWN,
- info.season ?: UNKNOWN
- )
- }
+ override suspend fun write(info: RecentlyWatchedInfo): Unit =
+ withContext(dispatcherProvider.io) {
+ dbProvider.get().recentlyWatchedQueries.insertRecentlyWatched(
+ info.movieId,
+ info.episode ?: UNKNOWN,
+ info.season ?: UNKNOWN
+ )
+ }
- override suspend fun remove(info: RecentlyWatchedInfo): Unit = withContext(dispatcherProvider.io) {
- dbProvider.get().recentlyWatchedQueries.deleteRwByIds(
- info.movieId,
- info.episode ?: UNKNOWN,
- info.season ?: UNKNOWN
- )
- }
+ override suspend fun remove(info: RecentlyWatchedInfo): Unit =
+ withContext(dispatcherProvider.io) {
+ dbProvider.get().recentlyWatchedQueries.deleteRwByIds(
+ info.movieId,
+ info.episode ?: UNKNOWN,
+ info.season ?: UNKNOWN
+ )
+ }
override fun queryRecentlyWatched(
movieId: Long,
diff --git a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/data/recent/SyncClientServerRecentlyWatchedRepository.kt b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/data/recent/SyncClientServerRecentlyWatchedRepository.kt
index 30b985f..9ba0892 100644
--- a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/data/recent/SyncClientServerRecentlyWatchedRepository.kt
+++ b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/data/recent/SyncClientServerRecentlyWatchedRepository.kt
@@ -31,7 +31,10 @@ class SyncClientServerRecentlyWatchedRepository(
private val pendingOperationSynchronizer: PendingOperationSynchronizer
) : RecentlyWatchedRepository {
- override fun queryRecentlyWatched(movieId: Long, seasonId: Long?): Flow> {
+ override fun queryRecentlyWatched(
+ movieId: Long,
+ seasonId: Long?
+ ): Flow> {
return authTokenCache.queryFlow(
remote.queryRecentlyWatched(movieId, seasonId),
local.queryRecentlyWatched(movieId, seasonId)
diff --git a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/data/search/SqlSearchRepository.kt b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/data/search/SqlSearchRepository.kt
index 53fbb43..55dfa55 100644
--- a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/data/search/SqlSearchRepository.kt
+++ b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/data/search/SqlSearchRepository.kt
@@ -49,9 +49,10 @@ class SqlSearchRepository(
getQueries().deleteAll()
}
- override suspend fun deleteFromSearch(query: String): Unit = withContext(dispatcherProvider.io) {
- getQueries().deleteSearchInfo(query)
- }
+ override suspend fun deleteFromSearch(query: String): Unit =
+ withContext(dispatcherProvider.io) {
+ getQueries().deleteSearchInfo(query)
+ }
override suspend fun addToSearch(query: String) = withContext(dispatcherProvider.io) {
if (getQueries().selectByQuery(query).awaitAsOneOrNull() == null) {
diff --git a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/data/sync/SqlPendingOperationRepository.kt b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/data/sync/SqlPendingOperationRepository.kt
index 669db08..fc01f9a 100644
--- a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/data/sync/SqlPendingOperationRepository.kt
+++ b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/data/sync/SqlPendingOperationRepository.kt
@@ -28,27 +28,29 @@ class SqlPendingOperationRepository(
getQueries().deleteOperation(id)
}
- override suspend fun query(type: AggregateType): List = withContext(dispatcherProvider.io) {
- val records = getQueries().getOperationsByAggregate(type.name).awaitAsList()
- records.map { record ->
- OperationInfo(
- id = record.id,
- type = type,
- actionType = record.actionType,
- payload = record.payload?.let { json.parseToJsonElement(it) } ?: JsonNull
+ override suspend fun query(type: AggregateType): List =
+ withContext(dispatcherProvider.io) {
+ val records = getQueries().getOperationsByAggregate(type.name).awaitAsList()
+ records.map { record ->
+ OperationInfo(
+ id = record.id,
+ type = type,
+ actionType = record.actionType,
+ payload = record.payload?.let { json.parseToJsonElement(it) } ?: JsonNull
+ )
+ }
+ }
+
+ override suspend fun insert(operationInfo: OperationInfo): Unit =
+ withContext(dispatcherProvider.io) {
+ val payloadString = json.encodeToString(operationInfo.payload)
+ getQueries().insertOperation(
+ aggregateType = operationInfo.type.name,
+ actionType = operationInfo.actionType,
+ payload = payloadString,
+ createdAt = Clock.System.now().epochSeconds
)
}
- }
-
- override suspend fun insert(operationInfo: OperationInfo): Unit = withContext(dispatcherProvider.io) {
- val payloadString = json.encodeToString(operationInfo.payload)
- getQueries().insertOperation(
- aggregateType = operationInfo.type.name,
- actionType = operationInfo.actionType,
- payload = payloadString,
- createdAt = Clock.System.now().epochSeconds
- )
- }
override suspend fun clear(): Unit = withContext(dispatcherProvider.io) {
getQueries().clear()
diff --git a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/data/sync/strategy/RecentlyWatchedSyncStrategy.kt b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/data/sync/strategy/RecentlyWatchedSyncStrategy.kt
index fdabb79..df333bb 100644
--- a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/data/sync/strategy/RecentlyWatchedSyncStrategy.kt
+++ b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/data/sync/strategy/RecentlyWatchedSyncStrategy.kt
@@ -26,16 +26,20 @@ class RecentlyWatchedSyncStrategy(
val info = json.decodeFromJsonElement(payload)
remote.write(info)
}
+
SyncClientServerRecentlyWatchedRepository.ACTION_REMOVE -> {
val info = json.decodeFromJsonElement(payload)
remote.remove(info)
}
+
SyncClientServerRecentlyWatchedRepository.ACTION_WRITE_ALL -> {
val payloadObj = payload.jsonObject
- val listElement = payloadObj[SyncClientServerRecentlyWatchedRepository.LIST] ?: error("Missing list key")
+ val listElement = payloadObj[SyncClientServerRecentlyWatchedRepository.LIST]
+ ?: error("Missing list key")
val list = json.decodeFromJsonElement>(listElement)
remote.writeAll(list)
}
+
else -> error("Unknown action type: $actionType")
}
}
diff --git a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/data/sync/strategy/SavedMovieSyncStrategy.kt b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/data/sync/strategy/SavedMovieSyncStrategy.kt
index 9e7b193..867ef0b 100644
--- a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/data/sync/strategy/SavedMovieSyncStrategy.kt
+++ b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/data/sync/strategy/SavedMovieSyncStrategy.kt
@@ -29,20 +29,25 @@ class SavedMovieSyncStrategy(
val details = json.decodeFromJsonElement(payload)
remote.save(details)
}
+
SyncClientSavedMovieRepository.ACTION_REMOVE -> {
- val id = payload.jsonObject[SyncClientSavedMovieRepository.KEY_ID]?.jsonPrimitive?.long
- ?: error("Missing ${SyncClientSavedMovieRepository.KEY_ID}")
+ val id =
+ payload.jsonObject[SyncClientSavedMovieRepository.KEY_ID]?.jsonPrimitive?.long
+ ?: error("Missing ${SyncClientSavedMovieRepository.KEY_ID}")
remote.remove(id)
}
+
SyncClientSavedMovieRepository.ACTION_SAVE_ALL -> {
val listElement = payload.jsonObject[SyncClientSavedMovieRepository.KEY_LIST]
?: error("Missing ${SyncClientSavedMovieRepository.KEY_LIST}")
val list = json.decodeFromJsonElement>(listElement)
remote.saveAll(list)
}
+
SyncClientSavedMovieRepository.ACTION_CLEAR -> {
remote.clear()
}
+
else -> error("Unknown action type: $actionType")
}
}
diff --git a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/data/sync/strategy/SearchSyncStrategy.kt b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/data/sync/strategy/SearchSyncStrategy.kt
index 013c780..d56956d 100644
--- a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/data/sync/strategy/SearchSyncStrategy.kt
+++ b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/data/sync/strategy/SearchSyncStrategy.kt
@@ -20,15 +20,19 @@ class SearchSyncStrategy(
override suspend fun execute(actionType: String, payload: JsonElement) {
when (actionType) {
SyncClientServerSearchRepository.ACTION_ADD -> {
- val query = payload.jsonObject[SyncClientServerSearchRepository.KEY_QUERY]?.jsonPrimitive?.content
- ?: error("Missing ${SyncClientServerSearchRepository.KEY_QUERY}")
+ val query =
+ payload.jsonObject[SyncClientServerSearchRepository.KEY_QUERY]?.jsonPrimitive?.content
+ ?: error("Missing ${SyncClientServerSearchRepository.KEY_QUERY}")
remote.addToSearch(query)
}
+
SyncClientServerSearchRepository.ACTION_DELETE -> {
- val query = payload.jsonObject[SyncClientServerSearchRepository.KEY_QUERY]?.jsonPrimitive?.content
- ?: error("Missing ${SyncClientServerSearchRepository.KEY_QUERY}")
+ val query =
+ payload.jsonObject[SyncClientServerSearchRepository.KEY_QUERY]?.jsonPrimitive?.content
+ ?: error("Missing ${SyncClientServerSearchRepository.KEY_QUERY}")
remote.deleteFromSearch(query)
}
+
SyncClientServerSearchRepository.ACTION_CLEAR -> remote.clear()
else -> error("Unknown action type: $actionType")
}
diff --git a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/data/sync/strategy/UserTagSyncStrategy.kt b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/data/sync/strategy/UserTagSyncStrategy.kt
index f6ad073..96cebb0 100644
--- a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/data/sync/strategy/UserTagSyncStrategy.kt
+++ b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/data/sync/strategy/UserTagSyncStrategy.kt
@@ -3,7 +3,6 @@ package ru.shadowsparky.vbox.shared.data.sync.strategy
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
-import kotlinx.serialization.json.long
import org.koin.core.annotation.Factory
import org.koin.core.annotation.Named
import ru.shadowsparky.vbox.shared.data.tag.SyncUserTagRepository
@@ -26,6 +25,7 @@ class UserTagSyncStrategy(
?: error("Missing ${SyncUserTagRepository.KEY_TAG}")
remote.add(tag)
}
+
SyncUserTagRepository.ACTION_EDIT -> {
val oldTag = payloadObj[SyncUserTagRepository.KEY_OLD_TAG]?.jsonPrimitive?.content
?: error("Missing ${SyncUserTagRepository.KEY_OLD_TAG}")
@@ -33,11 +33,13 @@ class UserTagSyncStrategy(
?: error("Missing ${SyncUserTagRepository.KEY_NEW_TAG}")
remote.edit(oldTag, newTag)
}
+
SyncUserTagRepository.ACTION_DELETE -> {
val oldTag = payloadObj[SyncUserTagRepository.KEY_OLD_TAG]?.jsonPrimitive?.content
?: error("Missing ${SyncUserTagRepository.KEY_OLD_TAG}")
remote.delete(oldTag)
}
+
else -> error("Unknown action type: $actionType")
}
}
diff --git a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/data/tag/RemoteUserTagRepository.kt b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/data/tag/RemoteUserTagRepository.kt
index 8b41b0c..bce7f86 100644
--- a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/data/tag/RemoteUserTagRepository.kt
+++ b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/data/tag/RemoteUserTagRepository.kt
@@ -36,10 +36,11 @@ class RemoteUserTagRepository(
private val dispatcherProvider: DispatcherProvider
) : UserTagRepository {
- override val userTags: Flow> = userTagListener.event.filterIsInstance()
- .map { queryUserTagsSingle() }
- .onStart { emit(queryUserTagsSingle()) }
- .flowOn(dispatcherProvider.io)
+ override val userTags: Flow> =
+ userTagListener.event.filterIsInstance()
+ .map { queryUserTagsSingle() }
+ .onStart { emit(queryUserTagsSingle()) }
+ .flowOn(dispatcherProvider.io)
private suspend fun queryUserTagsSingle(): List {
return httpClient.get(
diff --git a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/data/tag/SyncMovieTagRepository.kt b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/data/tag/SyncMovieTagRepository.kt
index 3be49c9..346d820 100644
--- a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/data/tag/SyncMovieTagRepository.kt
+++ b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/data/tag/SyncMovieTagRepository.kt
@@ -48,13 +48,15 @@ class SyncMovieTagRepository(
queryRemote = {
val allTags = userTagRemote.userTags.first()
allTags.flatMap { tag ->
- remote.queryMovies(tag.tag).first().map { movieId -> MovieTagLink(movieId, tag.tag) }
+ remote.queryMovies(tag.tag).first()
+ .map { movieId -> MovieTagLink(movieId, tag.tag) }
}
},
queryLocal = {
val allTags = userTagLocal.userTags.first()
allTags.flatMap { tag ->
- local.queryMovies(tag.tag).first().map { movieId -> MovieTagLink(movieId, tag.tag) }
+ local.queryMovies(tag.tag).first()
+ .map { movieId -> MovieTagLink(movieId, tag.tag) }
}
},
writeRemote = { list ->
diff --git a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/di/SavedModule.kt b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/di/SavedModule.kt
index 82b9653..9bf06ea 100644
--- a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/di/SavedModule.kt
+++ b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/di/SavedModule.kt
@@ -34,7 +34,8 @@ class SavedModule {
serverConfigurationRepository: ServerConfigurationRepository,
@Named(EventType.SAVED) eventListener: RemoteEventListener
): SavedMovieRepository {
- val impl = RemoteSavedMovieRepository(httpClient, serverConfigurationRepository, eventListener)
+ val impl =
+ RemoteSavedMovieRepository(httpClient, serverConfigurationRepository, eventListener)
return PosterWithoutSuffixSavedMovieRepository(impl)
}
}
diff --git a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/domain/AuthTokenCacheExt.kt b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/domain/AuthTokenCacheExt.kt
index d4a8d47..87089d4 100644
--- a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/domain/AuthTokenCacheExt.kt
+++ b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/domain/AuthTokenCacheExt.kt
@@ -17,7 +17,8 @@ fun TokenStorage.queryFlow(
): Flow {
return flow {
init()
- emitAll(if (hasAccount()) {
+ emitAll(
+ if (hasAccount()) {
active.catch { emitAll(fallback) }
} else {
fallback
diff --git a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/domain/OfflineVideoInteractor.kt b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/domain/OfflineVideoInteractor.kt
index 120453a..b7f2b16 100644
--- a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/domain/OfflineVideoInteractor.kt
+++ b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/domain/OfflineVideoInteractor.kt
@@ -32,9 +32,10 @@ class OfflineVideoInteractor(
seasonId,
season.title,
seasonEntry.value.mapNotNull { episode ->
- val links = fetchVideoLinks(episode.movieId, seasonId).seasons
- ?.firstOrNull()
- ?.episodes?.firstOrNull { it.episode == episode.episodeId }
+ val links =
+ fetchVideoLinks(episode.movieId, seasonId).seasons
+ ?.firstOrNull()
+ ?.episodes?.firstOrNull { it.episode == episode.episodeId }
if (links == null) {
null
} else {
diff --git a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/MainScreen.kt b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/MainScreen.kt
index 407aa7c..dd2e775 100644
--- a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/MainScreen.kt
+++ b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/MainScreen.kt
@@ -25,7 +25,11 @@ import ru.shadowsparky.vbox.shared.presentation.settings.SettingsContent
fun MainScreen(route: Route, child: Child, rootComponent: RootComponent) {
when (child) {
is Child.SearchChild -> SearchScreen(child.component, route) { rootComponent.nav(it, true) }
- is Child.SavedMovieChild -> SavedMovieScreen(child.component, route) { rootComponent.navReplace(it) }
+ is Child.SavedMovieChild -> SavedMovieScreen(
+ child.component,
+ route
+ ) { rootComponent.navReplace(it) }
+
else -> MainScreenInternal(route, child) { rootComponent.navReplace(it) }
}
}
diff --git a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/nav/Route.kt b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/nav/Route.kt
index 14e2160..645170c 100644
--- a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/nav/Route.kt
+++ b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/nav/Route.kt
@@ -35,7 +35,11 @@ sealed class Route : ru.shadowsparky.ui.nav.Route() {
class Season(val id: Long, override val routeId: String = RouteIds.SELECT_SEASON) : Route()
@Serializable
- class Episode(val id: Long, val selectedSeasonId: Long, override val routeId: String = RouteIds.SELECT_EPISODE) : Route()
+ class Episode(
+ val id: Long,
+ val selectedSeasonId: Long,
+ override val routeId: String = RouteIds.SELECT_EPISODE
+ ) : Route()
@Serializable
class Player(
diff --git a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/offline/OfflineVideoScreen.kt b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/offline/OfflineVideoScreen.kt
index 90b8421..7e0d266 100644
--- a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/offline/OfflineVideoScreen.kt
+++ b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/offline/OfflineVideoScreen.kt
@@ -80,7 +80,7 @@ fun OfflineVideoScreen(component: OfflineVideoComponent) {
OfflineVideoInfoContent(
padding,
info ?: emptyList(),
- onVideoClick = { component.play(it) },
+ onVideoClick = { component.play(it) },
onDeleteClick = { component.remove(it) }
)
}
@@ -139,6 +139,7 @@ private fun OfflineVideoInfoContent(
}
}
}
+
@Composable
private fun MovieHeader(
title: String,
diff --git a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/overview/episode/EpisodeOverviewComponent.kt b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/overview/episode/EpisodeOverviewComponent.kt
index 5bd3150..90d9667 100644
--- a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/overview/episode/EpisodeOverviewComponent.kt
+++ b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/overview/episode/EpisodeOverviewComponent.kt
@@ -192,7 +192,12 @@ class EpisodeOverviewFactory(
api = api,
recentlyWatchedRepository = recentlyWatchedRepository,
clipboardManager = clipboardManager,
- offlineStorage = offlineStorage?.let { SessionOfflineStorage(it, addOfflineVideoUseCase) },
+ offlineStorage = offlineStorage?.let {
+ SessionOfflineStorage(
+ it,
+ addOfflineVideoUseCase
+ )
+ },
dispatcherProvider = dispatcherProvider
)
return Child.EpisodeOverviewChild(comp)
diff --git a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/overview/episode/EpisodeOverviewScreen.kt b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/overview/episode/EpisodeOverviewScreen.kt
index d5eb202..4e407b2 100644
--- a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/overview/episode/EpisodeOverviewScreen.kt
+++ b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/overview/episode/EpisodeOverviewScreen.kt
@@ -282,6 +282,7 @@ private fun RowScope.ShowOfflineIcon(offlineInfo: OfflineVideo?) {
modifier = Modifier.align(Alignment.CenterVertically).size(32.dp)
)
}
+
else -> {
when (state) {
is OfflineState.Available -> Res.drawable.download_done
diff --git a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/overview/episode/SessionOfflineStorage.kt b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/overview/episode/SessionOfflineStorage.kt
index 495c35c..0474d4e 100644
--- a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/overview/episode/SessionOfflineStorage.kt
+++ b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/overview/episode/SessionOfflineStorage.kt
@@ -68,6 +68,7 @@ class SessionOfflineStorage(
result += operation.item
}
}
+
PendingOperation.Remove -> {
result.removeAll { it.key() == key }
}
diff --git a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/player/PlayerScreen.kt b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/player/PlayerScreen.kt
index 6721b2c..0ba6eb7 100644
--- a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/player/PlayerScreen.kt
+++ b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/player/PlayerScreen.kt
@@ -54,6 +54,7 @@ fun PlayerScreen(component: PlayerComponent) {
)
}
}
+
is PlayerEvent.Ended -> component.requestNextEpisode()
is PlayerEvent.Playing -> {
Box(modifier = Modifier.fillMaxSize()) {
@@ -68,12 +69,16 @@ fun PlayerScreen(component: PlayerComponent) {
val savedMillis = (effect as? PlayerEffect.SavedTimeFound)?.millis
PlayerActionOverlay(
savedMillis != null,
- text = stringResource(Res.string.start_from, "${savedMillis?.toReadableTime()}")
+ text = stringResource(
+ Res.string.start_from,
+ "${savedMillis?.toReadableTime()}"
+ )
) { component.load(savedMillis) }
}
}
}
}
+
else -> Loading(Modifier.fillMaxSize())
}
}
diff --git a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/search/SearchScreen.kt b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/search/SearchScreen.kt
index 1d8380d..abd054f 100644
--- a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/search/SearchScreen.kt
+++ b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/search/SearchScreen.kt
@@ -137,7 +137,10 @@ fun SearchVideoContent(
},
onDeleteRequested = { component.removeFromSearch(it) })
} else {
- Box(modifier = Modifier.fillMaxSize().padding(paddingValues), contentAlignment = Alignment.Center) {
+ Box(
+ modifier = Modifier.fillMaxSize().padding(paddingValues),
+ contentAlignment = Alignment.Center
+ ) {
Text(
text = stringResource(Res.string.search_empty_now),
style = MaterialTheme.typography.titleMedium,
diff --git a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/settings/tag/TagScreen.kt b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/settings/tag/TagScreen.kt
index d3911cc..2d53798 100644
--- a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/settings/tag/TagScreen.kt
+++ b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/settings/tag/TagScreen.kt
@@ -193,7 +193,9 @@ private fun EditTagBottomSheet(
}
},
modifier = Modifier.fillMaxWidth(),
- enabled = tagId != null && newTag.isNotEmpty() && newTag != tag.tag && canSave(newTag)
+ enabled = tagId != null && newTag.isNotEmpty() && newTag != tag.tag && canSave(
+ newTag
+ )
) { Text(stringResource(Res.string.user_tags_new_tag_save)) }
OutlinedButton(
onClick = {
diff --git a/apps/vbox/client/shared/src/desktopMain/kotlin/ru/shadowsparky/vbox/shared/di/factory/DatabaseDriverFactory.kt b/apps/vbox/client/shared/src/desktopMain/kotlin/ru/shadowsparky/vbox/shared/di/factory/DatabaseDriverFactory.kt
index 720ced8..eafc7ed 100644
--- a/apps/vbox/client/shared/src/desktopMain/kotlin/ru/shadowsparky/vbox/shared/di/factory/DatabaseDriverFactory.kt
+++ b/apps/vbox/client/shared/src/desktopMain/kotlin/ru/shadowsparky/vbox/shared/di/factory/DatabaseDriverFactory.kt
@@ -6,7 +6,8 @@ import org.koin.core.annotation.Factory
import ru.shadowsparky.vbox.app.AppDatabase
@Factory
-class DesktopDatabaseDriverFactory(private val fileProvider: DesktopFileProvider) : DatabaseDriverFactory {
+class DesktopDatabaseDriverFactory(private val fileProvider: DesktopFileProvider) :
+ DatabaseDriverFactory {
override suspend fun createDriver(): SqlDriver {
val dir = fileProvider.getCacheFileDir() ?: ""
val driver = JdbcSqliteDriver(JdbcSqliteDriver.IN_MEMORY + "$dir/videobox.db")
diff --git a/apps/vbox/client/shared/src/desktopMain/kotlin/ru/shadowsparky/vbox/shared/di/factory/ImageLoaderProvider.kt b/apps/vbox/client/shared/src/desktopMain/kotlin/ru/shadowsparky/vbox/shared/di/factory/ImageLoaderProvider.kt
index 6fa0dc1..0c77d9d 100644
--- a/apps/vbox/client/shared/src/desktopMain/kotlin/ru/shadowsparky/vbox/shared/di/factory/ImageLoaderProvider.kt
+++ b/apps/vbox/client/shared/src/desktopMain/kotlin/ru/shadowsparky/vbox/shared/di/factory/ImageLoaderProvider.kt
@@ -7,7 +7,8 @@ import okio.Path.Companion.toPath
import org.koin.core.annotation.Factory
@Factory
-class DesktopImageLoaderProvider(private val fileProvider: DesktopFileProvider) : ImageLoaderProvider {
+class DesktopImageLoaderProvider(private val fileProvider: DesktopFileProvider) :
+ ImageLoaderProvider {
override fun provide(): ImageLoader {
return ImageLoader {
diff --git a/build-logic/convention/src/main/kotlin/ru/shadowsparky/plugin/AndroidApplication.kt b/build-logic/convention/src/main/kotlin/ru/shadowsparky/plugin/AndroidApplication.kt
index 5ae997b..3962322 100644
--- a/build-logic/convention/src/main/kotlin/ru/shadowsparky/plugin/AndroidApplication.kt
+++ b/build-logic/convention/src/main/kotlin/ru/shadowsparky/plugin/AndroidApplication.kt
@@ -26,7 +26,8 @@ class AndroidApplication : Plugin {
if (propertiesFile.exists()) {
val properties = keystorePropertiesProvider.get()
- val keystoreFile = rootProject.layout.projectDirectory.file("release.keystore").asFile
+ val keystoreFile =
+ rootProject.layout.projectDirectory.file("release.keystore").asFile
check(keystoreFile.exists()) { "Unable to load keystore file!" }
storeFile = keystoreFile
diff --git a/gradle.properties b/gradle.properties
index 2e46d06..e5195bc 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,17 +1,13 @@
kotlin.code.style=official
android.useAndroidX=true
-
org.gradle.jvmargs=-Xms1g -Xmx3584m -XX:+UseParallelGC -Dfile.encoding=UTF-8
-
org.jetbrains.compose.experimental.jscanvas.enabled=true
org.jetbrains.compose.experimental.uikit.enabled=true
android.nonTransitiveRClass=false
android.r8.strictFullModeForKeepRules=true
android.injected.enableStableIds=true
-
org.gradle.caching=true
org.gradle.parallel=true
org.gradle.configuration-cache=true
-
# Enabled parallel sync for Gradle 9.4+
org.gradle.tooling.parallel=true
diff --git a/libs/android-stub/build.gradle.kts b/libs/android-stub/build.gradle.kts
index 268a4c0..6c92603 100644
--- a/libs/android-stub/build.gradle.kts
+++ b/libs/android-stub/build.gradle.kts
@@ -1,4 +1,3 @@
-
import com.android.build.api.dsl.LibraryExtension
plugins {
diff --git a/libs/ui/src/commonMain/kotlin/ru/shadowsparky/ui/components/BackButton.kt b/libs/ui/src/commonMain/kotlin/ru/shadowsparky/ui/components/BackButton.kt
index f10060d..b3ca821 100644
--- a/libs/ui/src/commonMain/kotlin/ru/shadowsparky/ui/components/BackButton.kt
+++ b/libs/ui/src/commonMain/kotlin/ru/shadowsparky/ui/components/BackButton.kt
@@ -15,6 +15,10 @@ fun BackButton(doBack: () -> Unit) {
IconButton(
onClick = { doBack() },
shape = CircleShape,
- colors = IconButtonDefaults.iconButtonVibrantColors(containerColor = MaterialTheme.colorScheme.primaryContainer.copy(0.1f))
+ colors = IconButtonDefaults.iconButtonVibrantColors(
+ containerColor = MaterialTheme.colorScheme.primaryContainer.copy(
+ 0.1f
+ )
+ )
) { Icon(painterResource(Res.drawable.back), contentDescription = null) }
}
diff --git a/libs/ui/src/commonMain/kotlin/ru/shadowsparky/ui/components/Expressive.kt b/libs/ui/src/commonMain/kotlin/ru/shadowsparky/ui/components/Expressive.kt
index 57251d8..2079c47 100644
--- a/libs/ui/src/commonMain/kotlin/ru/shadowsparky/ui/components/Expressive.kt
+++ b/libs/ui/src/commonMain/kotlin/ru/shadowsparky/ui/components/Expressive.kt
@@ -17,7 +17,8 @@ import androidx.compose.ui.unit.dp
private const val ROUNDED_MAX = 24
private const val ROUNDED_MIN = 6
-@DslMarker private annotation class ExpressiveListDsl
+@DslMarker
+private annotation class ExpressiveListDsl
internal data class ExpressiveNode(
val key: Any?,
diff --git a/libs/video-player/src/androidMain/kotlin/ru/shadowsparky/videoplayer/AndroidPlayerController.kt b/libs/video-player/src/androidMain/kotlin/ru/shadowsparky/videoplayer/AndroidPlayerController.kt
index 7dd8450..a208802 100644
--- a/libs/video-player/src/androidMain/kotlin/ru/shadowsparky/videoplayer/AndroidPlayerController.kt
+++ b/libs/video-player/src/androidMain/kotlin/ru/shadowsparky/videoplayer/AndroidPlayerController.kt
@@ -79,7 +79,9 @@ class AndroidPlayerController(context: Context) : PlayerController {
}
}
- init { bindListenerIfNeeded(false) }
+ init {
+ bindListenerIfNeeded(false)
+ }
private fun bindListenerIfNeeded(allowFail: Boolean) {
try {
diff --git a/libs/video-player/src/commonMain/kotlin/ru/shadowsparky/videoplayer/ui/DefaultControls.kt b/libs/video-player/src/commonMain/kotlin/ru/shadowsparky/videoplayer/ui/DefaultControls.kt
index c49ce6f..ec6fe33 100644
--- a/libs/video-player/src/commonMain/kotlin/ru/shadowsparky/videoplayer/ui/DefaultControls.kt
+++ b/libs/video-player/src/commonMain/kotlin/ru/shadowsparky/videoplayer/ui/DefaultControls.kt
@@ -153,9 +153,11 @@ fun DefaultControls(
Key.DirectionLeft -> {
scope.launch { controller.seek(true) }
}
+
Key.DirectionRight -> {
scope.launch { controller.seek(false) }
}
+
Key.DirectionUp -> focus.play.requestFocus()
Key.DirectionDown -> focus.audioTrack.requestFocus()
else -> return@onPreviewKeyEvent false
@@ -307,6 +309,7 @@ private fun BoxScope.CenterControls(
)
}
}
+
else -> {
Row(
verticalAlignment = Alignment.CenterVertically,
diff --git a/libs/video-player/src/desktopMain/kotlin/ru/shadowsparky/videoplayer/ExternalPlayer.desktop.kt b/libs/video-player/src/desktopMain/kotlin/ru/shadowsparky/videoplayer/ExternalPlayer.desktop.kt
index 1e241f6..b98be31 100644
--- a/libs/video-player/src/desktopMain/kotlin/ru/shadowsparky/videoplayer/ExternalPlayer.desktop.kt
+++ b/libs/video-player/src/desktopMain/kotlin/ru/shadowsparky/videoplayer/ExternalPlayer.desktop.kt
@@ -4,5 +4,7 @@ import org.koin.core.annotation.Factory
@Factory
class DesktopExternalPlayer : ExternalPlayer {
- override fun play(url: String) { throw NotImplementedError() }
+ override fun play(url: String) {
+ throw NotImplementedError()
+ }
}